├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── 1-base ├── src │ └── main │ │ ├── res │ │ ├── drawable-nodpi │ │ │ └── preview_analog.png │ │ ├── values │ │ │ ├── ids.xml │ │ │ └── strings.xml │ │ └── xml │ │ │ └── watch_face.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── example │ │ └── watchface │ │ └── MyWatchFaceService.java └── build.gradle ├── 3-hands ├── src │ └── main │ │ ├── res │ │ ├── drawable-nodpi │ │ │ ├── preview_analog.png │ │ │ └── custom_background.jpg │ │ ├── values │ │ │ ├── ids.xml │ │ │ └── strings.xml │ │ └── xml │ │ │ └── watch_face.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── example │ │ └── watchface │ │ └── MyWatchFaceService.java └── build.gradle ├── 4-ambient ├── src │ └── main │ │ ├── res │ │ ├── drawable-nodpi │ │ │ ├── preview_analog.png │ │ │ └── custom_background.jpg │ │ ├── values │ │ │ ├── ids.xml │ │ │ └── strings.xml │ │ └── xml │ │ │ └── watch_face.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── example │ │ └── watchface │ │ └── MyWatchFaceService.java └── build.gradle ├── 5-palette ├── src │ └── main │ │ ├── res │ │ ├── drawable-nodpi │ │ │ ├── preview_analog.png │ │ │ ├── custom_background.jpg │ │ │ └── custom_background2.jpg │ │ ├── values │ │ │ ├── ids.xml │ │ │ └── strings.xml │ │ └── xml │ │ │ └── watch_face.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── example │ │ └── watchface │ │ └── MyWatchFaceService.java └── build.gradle ├── 2-background ├── src │ └── main │ │ ├── res │ │ ├── drawable-nodpi │ │ │ ├── preview_analog.png │ │ │ └── custom_background.jpg │ │ ├── values │ │ │ ├── ids.xml │ │ │ └── strings.xml │ │ └── xml │ │ │ └── watch_face.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── example │ │ └── watchface │ │ └── MyWatchFaceService.java └── build.gradle ├── NOTICE ├── settings.gradle ├── gradle.properties ├── CONTRIBUTING.md ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /1-base/src/main/res/drawable-nodpi/preview_analog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/1-base/src/main/res/drawable-nodpi/preview_analog.png -------------------------------------------------------------------------------- /3-hands/src/main/res/drawable-nodpi/preview_analog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/3-hands/src/main/res/drawable-nodpi/preview_analog.png -------------------------------------------------------------------------------- /4-ambient/src/main/res/drawable-nodpi/preview_analog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/4-ambient/src/main/res/drawable-nodpi/preview_analog.png -------------------------------------------------------------------------------- /5-palette/src/main/res/drawable-nodpi/preview_analog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/5-palette/src/main/res/drawable-nodpi/preview_analog.png -------------------------------------------------------------------------------- /2-background/src/main/res/drawable-nodpi/preview_analog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/2-background/src/main/res/drawable-nodpi/preview_analog.png -------------------------------------------------------------------------------- /3-hands/src/main/res/drawable-nodpi/custom_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/3-hands/src/main/res/drawable-nodpi/custom_background.jpg -------------------------------------------------------------------------------- /4-ambient/src/main/res/drawable-nodpi/custom_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/4-ambient/src/main/res/drawable-nodpi/custom_background.jpg -------------------------------------------------------------------------------- /5-palette/src/main/res/drawable-nodpi/custom_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/5-palette/src/main/res/drawable-nodpi/custom_background.jpg -------------------------------------------------------------------------------- /5-palette/src/main/res/drawable-nodpi/custom_background2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/5-palette/src/main/res/drawable-nodpi/custom_background2.jpg -------------------------------------------------------------------------------- /2-background/src/main/res/drawable-nodpi/custom_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/watchface/HEAD/2-background/src/main/res/drawable-nodpi/custom_background.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 27 14:13:32 PDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This sample uses the following software: 2 | 3 | Copyright 2016 The Android Open Source Project 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 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 | include '1-base', '2-background', '3-hands', '4-ambient', '5-palette' -------------------------------------------------------------------------------- /1-base/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /3-hands/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /1-base/src/main/res/xml/watch_face.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /2-background/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /3-hands/src/main/res/xml/watch_face.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /4-ambient/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /4-ambient/src/main/res/xml/watch_face.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /5-palette/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /5-palette/src/main/res/xml/watch_face.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /2-background/src/main/res/xml/watch_face.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /1-base/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Codelab Watchface 19 | Analog Codelab Watchface 20 | 21 | -------------------------------------------------------------------------------- /3-hands/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Codelab Watchface 19 | Analog Codelab Watchface 20 | 21 | -------------------------------------------------------------------------------- /4-ambient/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Codelab Watchface 19 | Analog Codelab Watchface 20 | 21 | -------------------------------------------------------------------------------- /5-palette/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Codelab Watchface 19 | Analog Codelab Watchface 20 | 21 | -------------------------------------------------------------------------------- /2-background/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Codelab Watchface 19 | Analog Codelab Watchface 20 | 21 | -------------------------------------------------------------------------------- /1-base/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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 | apply plugin: 'com.android.application' 18 | 19 | 20 | android { 21 | compileSdkVersion 25 22 | buildToolsVersion '25.0.2' 23 | 24 | defaultConfig { 25 | applicationId "com.android.example.watchface" 26 | minSdkVersion 21 27 | targetSdkVersion 25 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt') 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | provided 'com.google.android.wearable:wearable:2.0.0' 41 | compile 'com.google.android.support:wearable:2.0.0' 42 | compile 'com.google.android.gms:play-services-wearable:10.2.1' 43 | } 44 | -------------------------------------------------------------------------------- /3-hands/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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 | apply plugin: 'com.android.application' 18 | 19 | 20 | android { 21 | compileSdkVersion 25 22 | buildToolsVersion "25.0.2" 23 | 24 | defaultConfig { 25 | applicationId "com.android.example.watchface" 26 | minSdkVersion 21 27 | targetSdkVersion 25 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt') 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | provided 'com.google.android.wearable:wearable:2.0.0' 41 | compile 'com.google.android.support:wearable:2.0.0' 42 | compile 'com.google.android.gms:play-services-wearable:10.2.1' 43 | } 44 | -------------------------------------------------------------------------------- /4-ambient/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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 | apply plugin: 'com.android.application' 18 | 19 | 20 | android { 21 | compileSdkVersion 25 22 | buildToolsVersion "25.0.2" 23 | 24 | defaultConfig { 25 | applicationId "com.android.example.watchface" 26 | minSdkVersion 21 27 | targetSdkVersion 25 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt') 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | provided 'com.google.android.wearable:wearable:2.0.0' 41 | compile 'com.google.android.support:wearable:2.0.0' 42 | compile 'com.google.android.gms:play-services-wearable:10.2.1' 43 | } 44 | -------------------------------------------------------------------------------- /2-background/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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 | apply plugin: 'com.android.application' 18 | 19 | 20 | android { 21 | compileSdkVersion 25 22 | buildToolsVersion '25.0.2' 23 | 24 | defaultConfig { 25 | applicationId "com.android.example.watchface" 26 | minSdkVersion 21 27 | targetSdkVersion 25 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt') 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | provided 'com.google.android.wearable:wearable:2.0.0' 41 | compile 'com.google.android.support:wearable:2.0.0' 42 | compile 'com.google.android.gms:play-services-wearable:10.2.1' 43 | } 44 | -------------------------------------------------------------------------------- /5-palette/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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 | apply plugin: 'com.android.application' 18 | 19 | 20 | android { 21 | compileSdkVersion 25 22 | buildToolsVersion "25.0.2" 23 | 24 | defaultConfig { 25 | applicationId "com.android.example.watchface" 26 | minSdkVersion 21 27 | targetSdkVersion 25 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt') 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | compile 'com.android.support:palette-v7:23.3.0' 41 | provided 'com.google.android.wearable:wearable:2.0.0' 42 | compile 'com.google.android.support:wearable:2.0.0' 43 | compile 'com.google.android.gms:play-services-wearable:10.2.1' 44 | } 45 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 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 | # Project-wide Gradle settings. 18 | 19 | # IDE (e.g. Android Studio) users: 20 | # Gradle settings configured through the IDE *will override* 21 | # any settings specified in this file. 22 | 23 | # For more details on how to configure your build environment visit 24 | # http://www.gradle.org/docs/current/userguide/build_environment.html 25 | 26 | # Specifies the JVM arguments used for the daemon process. 27 | # The setting is particularly useful for tweaking memory settings. 28 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 29 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 30 | 31 | # When configured, Gradle will run in incubating parallel mode. 32 | # This option should only be used with decoupled projects. More details, visit 33 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 34 | # org.gradle.parallel=true 35 | org.gradle.daemon=true 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | ### Before you contribute 9 | Before we can use your code, you must sign the 10 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 11 | (CLA), which you can do online. The CLA is necessary mainly because you own the 12 | copyright to your changes, even after your contribution becomes part of our 13 | codebase, so we need your permission to use and distribute your code. We also 14 | need to be sure of various other things—for instance that you'll tell us if you 15 | know that your code infringes on other people's patents. You don't have to sign 16 | the CLA until after you've submitted your code for review and a member has 17 | approved it, but you must do it before we can put your code into our codebase. 18 | Before you start working on a larger contribution, you should get in touch with 19 | us first through the issue tracker with your idea so that we can help out and 20 | possibly guide you. Coordinating up front makes it much easier to avoid 21 | frustration later on. 22 | 23 | ### Code reviews 24 | All submissions, including submissions by project members, require review. We 25 | use Github pull requests for this purpose. 26 | 27 | ### The small print 28 | Contributions made by corporations are covered by a different agreement than 29 | the one above, the 30 | [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Wear watch face codelab 2 | =============================== 3 | 4 | In this codelab you'll learn how to create an analog watch face 5 | and customize it to our needs, keeping best practices for the platform in mind. 6 | 7 | This is the code part of the watch face codelab. 8 | The code lab consists of 5 steps. 9 | 10 | A [site](https://watchface-codelab.appspot.com) to guide you through 11 | the code lab is available. 12 | 13 | To get started open the site and follow the instructions. 14 | 15 | Pre-requisites 16 | -------------- 17 | 18 | - Android SDK v23 19 | - Android Build Tools v23.3.0 20 | - Android Support Repository 21 | 22 | Getting Started 23 | --------------- 24 | 25 | This code lab uses the Gradle build system. To build this project, use the 26 | "./gradlew build" command or use "Import Project" in Android Studio. 27 | 28 | Support 29 | ------- 30 | 31 | - [Stack Overflow](http://stackoverflow.com/questions/tagged/android) 32 | 33 | If you've found an error in this code lab, please file 34 | an [issue](https://github.com/googlesamples/android-codelab-watchface) 35 | 36 | Patches are encouraged, and may be submitted by forking this project and 37 | submitting a pull request through GitHub. Please see CONTRIB.md for more details. 38 | 39 | License 40 | ------- 41 | 42 | Copyright 2016 The Android Open Source Project, Inc. 43 | 44 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 45 | license agreements. See the NOTICE file distributed with this work for 46 | additional information regarding copyright ownership. The ASF licenses this 47 | file to you under the Apache License, Version 2.0 (the "License"); you may not 48 | use this file except in compliance with the License. You may obtain a copy of 49 | the License at 50 | 51 | http://www.apache.org/licenses/LICENSE-2.0 52 | 53 | Unless required by applicable law or agreed to in writing, software 54 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 55 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 56 | License for the specific language governing permissions and limitations under 57 | the License. 58 | -------------------------------------------------------------------------------- /1-base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 36 | 39 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /3-hands/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 37 | 40 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /2-background/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 37 | 40 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /4-ambient/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 37 | 40 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /5-palette/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 37 | 40 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /1-base/src/main/java/com/android/example/watchface/MyWatchFaceService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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.android.example.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.graphics.Canvas; 24 | import android.graphics.Color; 25 | import android.graphics.Paint; 26 | import android.graphics.Rect; 27 | import android.os.Handler; 28 | import android.os.Message; 29 | import android.support.wearable.watchface.CanvasWatchFaceService; 30 | import android.support.wearable.watchface.WatchFaceStyle; 31 | import android.view.SurfaceHolder; 32 | 33 | import java.util.Calendar; 34 | import java.util.TimeZone; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | /** 38 | * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On 39 | * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode. 40 | */ 41 | public class MyWatchFaceService extends CanvasWatchFaceService { 42 | 43 | /** 44 | * Update rate in milliseconds for interactive mode. We update once a second to advance the 45 | * second hand. 46 | */ 47 | private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); 48 | 49 | @Override 50 | public Engine onCreateEngine() { 51 | return new Engine(); 52 | } 53 | 54 | private class Engine extends CanvasWatchFaceService.Engine { 55 | 56 | /* Handler to update the time once a second in interactive mode. */ 57 | private final Handler mUpdateTimeHandler = new Handler() { 58 | @Override 59 | public void handleMessage(Message message) { 60 | if (R.id.message_update == message.what) { 61 | invalidate(); 62 | if (shouldTimerBeRunning()) { 63 | long timeMs = System.currentTimeMillis(); 64 | long delayMs = INTERACTIVE_UPDATE_RATE_MS 65 | - (timeMs % INTERACTIVE_UPDATE_RATE_MS); 66 | mUpdateTimeHandler.sendEmptyMessageDelayed(R.id.message_update, delayMs); 67 | } 68 | } 69 | } 70 | }; 71 | 72 | private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 73 | @Override 74 | public void onReceive(Context context, Intent intent) { 75 | mCalendar.setTimeZone(TimeZone.getDefault()); 76 | invalidate(); 77 | } 78 | }; 79 | 80 | private boolean mRegisteredTimeZoneReceiver = false; 81 | 82 | private static final float STROKE_WIDTH = 3f; 83 | 84 | private Calendar mCalendar; 85 | 86 | private Paint mBackgroundPaint; 87 | private Paint mHandPaint; 88 | 89 | private boolean mAmbient; 90 | 91 | private float mHourHandLength; 92 | private float mMinuteHandLength; 93 | private float mSecondHandLength; 94 | 95 | private int mWidth; 96 | private int mHeight; 97 | private float mCenterX; 98 | private float mCenterY; 99 | private float mScale = 1; 100 | 101 | @Override 102 | public void onCreate(SurfaceHolder holder) { 103 | super.onCreate(holder); 104 | 105 | setWatchFaceStyle(new WatchFaceStyle.Builder(MyWatchFaceService.this).build()); 106 | 107 | mBackgroundPaint = new Paint(); 108 | mBackgroundPaint.setColor(Color.BLACK); 109 | 110 | mHandPaint = new Paint(); 111 | mHandPaint.setColor(Color.WHITE); 112 | mHandPaint.setStrokeWidth(STROKE_WIDTH); 113 | mHandPaint.setAntiAlias(true); 114 | mHandPaint.setStrokeCap(Paint.Cap.ROUND); 115 | 116 | mCalendar = Calendar.getInstance(); 117 | } 118 | 119 | @Override 120 | public void onDestroy() { 121 | mUpdateTimeHandler.removeMessages(R.id.message_update); 122 | super.onDestroy(); 123 | } 124 | 125 | @Override 126 | public void onTimeTick() { 127 | super.onTimeTick(); 128 | invalidate(); 129 | } 130 | 131 | @Override 132 | public void onAmbientModeChanged(boolean inAmbientMode) { 133 | super.onAmbientModeChanged(inAmbientMode); 134 | if (mAmbient != inAmbientMode) { 135 | mAmbient = inAmbientMode; 136 | invalidate(); 137 | } 138 | 139 | /* 140 | * Whether the timer should be running depends on whether we're visible (as well as 141 | * whether we're in ambient mode), so we may need to start or stop the timer. 142 | */ 143 | updateTimer(); 144 | } 145 | 146 | @Override 147 | public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 148 | super.onSurfaceChanged(holder, format, width, height); 149 | mWidth = width; 150 | mHeight = height; 151 | /* 152 | * Find the coordinates of the center point on the screen. 153 | * Ignore the window insets so that, on round watches 154 | * with a "chin", the watch face is centered on the entire screen, 155 | * not just the usable portion. 156 | */ 157 | mCenterX = mWidth / 2f; 158 | mCenterY = mHeight / 2f; 159 | /* 160 | * Calculate the lengths of the watch hands and store them in member variables. 161 | */ 162 | mHourHandLength = mCenterX - 80; 163 | mMinuteHandLength = mCenterX - 40; 164 | mSecondHandLength = mCenterX - 20; 165 | } 166 | 167 | @Override 168 | public void onDraw(Canvas canvas, Rect bounds) { 169 | long now = System.currentTimeMillis(); 170 | mCalendar.setTimeInMillis(now); 171 | 172 | // Draw the background. 173 | canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint); 174 | 175 | /* 176 | * These calculations reflect the rotation in degrees per unit of time, e.g., 177 | * 360 / 60 = 6 and 360 / 12 = 30. 178 | */ 179 | final float seconds = 180 | (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f); 181 | final float secondsRotation = seconds * 6f; 182 | 183 | final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f; 184 | 185 | final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f; 186 | final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset; 187 | 188 | // save the canvas state before we begin to rotate it 189 | canvas.save(); 190 | 191 | canvas.rotate(hoursRotation, mCenterX, mCenterY); 192 | canvas.drawLine(mCenterX, mCenterY, mCenterX, mCenterY - mHourHandLength, mHandPaint); 193 | 194 | canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY); 195 | canvas.drawLine(mCenterX, mCenterY, mCenterX, mCenterY - mMinuteHandLength, mHandPaint); 196 | 197 | if (!mAmbient) { 198 | canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY); 199 | canvas.drawLine(mCenterX, mCenterY, mCenterX, mCenterY - mSecondHandLength, 200 | mHandPaint); 201 | } 202 | // restore the canvas' original orientation. 203 | canvas.restore(); 204 | } 205 | 206 | @Override 207 | public void onVisibilityChanged(boolean visible) { 208 | super.onVisibilityChanged(visible); 209 | 210 | if (visible) { 211 | registerReceiver(); 212 | 213 | // Update time zone in case it changed while we weren't visible. 214 | mCalendar.setTimeZone(TimeZone.getDefault()); 215 | invalidate(); 216 | } else { 217 | unregisterReceiver(); 218 | } 219 | 220 | /* 221 | * Whether the timer should be running depends on whether we're visible 222 | * (as well as whether we're in ambient mode), 223 | * so we may need to start or stop the timer. 224 | */ 225 | updateTimer(); 226 | } 227 | 228 | private void registerReceiver() { 229 | if (mRegisteredTimeZoneReceiver) { 230 | return; 231 | } 232 | mRegisteredTimeZoneReceiver = true; 233 | IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 234 | MyWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 235 | } 236 | 237 | private void unregisterReceiver() { 238 | if (!mRegisteredTimeZoneReceiver) { 239 | return; 240 | } 241 | mRegisteredTimeZoneReceiver = false; 242 | MyWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 243 | } 244 | 245 | private void updateTimer() { 246 | mUpdateTimeHandler.removeMessages(R.id.message_update); 247 | if (shouldTimerBeRunning()) { 248 | mUpdateTimeHandler.sendEmptyMessage(R.id.message_update); 249 | } 250 | } 251 | 252 | /** 253 | * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer 254 | * should only run when we're visible and in interactive mode. 255 | */ 256 | private boolean shouldTimerBeRunning() { 257 | return isVisible() && !isInAmbientMode(); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /2-background/src/main/java/com/android/example/watchface/MyWatchFaceService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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.android.example.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.graphics.Bitmap; 24 | import android.graphics.BitmapFactory; 25 | import android.graphics.Canvas; 26 | import android.graphics.Color; 27 | import android.graphics.Paint; 28 | import android.graphics.Rect; 29 | import android.os.Handler; 30 | import android.os.Message; 31 | import android.support.wearable.watchface.CanvasWatchFaceService; 32 | import android.support.wearable.watchface.WatchFaceStyle; 33 | import android.view.SurfaceHolder; 34 | 35 | import java.util.Calendar; 36 | import java.util.TimeZone; 37 | import java.util.concurrent.TimeUnit; 38 | 39 | /** 40 | * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On 41 | * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode. 42 | */ 43 | public class MyWatchFaceService extends CanvasWatchFaceService { 44 | 45 | /** 46 | * Update rate in milliseconds for interactive mode. We update once a second to advance the 47 | * second hand. 48 | */ 49 | private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); 50 | 51 | @Override 52 | public Engine onCreateEngine() { 53 | return new Engine(); 54 | } 55 | 56 | private class Engine extends CanvasWatchFaceService.Engine { 57 | 58 | /* Handler to update the time once a second in interactive mode. */ 59 | private final Handler mUpdateTimeHandler = new Handler() { 60 | @Override 61 | public void handleMessage(Message message) { 62 | if (R.id.message_update == message.what) { 63 | invalidate(); 64 | if (shouldTimerBeRunning()) { 65 | long timeMs = System.currentTimeMillis(); 66 | long delayMs = INTERACTIVE_UPDATE_RATE_MS 67 | - (timeMs % INTERACTIVE_UPDATE_RATE_MS); 68 | mUpdateTimeHandler.sendEmptyMessageDelayed(R.id.message_update, delayMs); 69 | } 70 | } 71 | } 72 | }; 73 | 74 | private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 75 | @Override 76 | public void onReceive(Context context, Intent intent) { 77 | mCalendar.setTimeZone(TimeZone.getDefault()); 78 | invalidate(); 79 | } 80 | }; 81 | 82 | private boolean mRegisteredTimeZoneReceiver = false; 83 | 84 | private static final float STROKE_WIDTH = 3f; 85 | 86 | private Calendar mCalendar; 87 | 88 | private Paint mBackgroundPaint; 89 | private Paint mHandPaint; 90 | 91 | private boolean mAmbient; 92 | 93 | private Bitmap mBackgroundBitmap; 94 | 95 | private float mHourHandLength; 96 | private float mMinuteHandLength; 97 | private float mSecondHandLength; 98 | 99 | private int mWidth; 100 | private int mHeight; 101 | private float mCenterX; 102 | private float mCenterY; 103 | private float mScale = 1; 104 | 105 | @Override 106 | public void onCreate(SurfaceHolder holder) { 107 | super.onCreate(holder); 108 | 109 | setWatchFaceStyle(new WatchFaceStyle.Builder(MyWatchFaceService.this).build()); 110 | 111 | mBackgroundPaint = new Paint(); 112 | mBackgroundPaint.setColor(Color.BLACK); 113 | 114 | final int backgroundResId = R.drawable.custom_background; 115 | 116 | mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundResId); 117 | 118 | mHandPaint = new Paint(); 119 | mHandPaint.setColor(Color.WHITE); 120 | mHandPaint.setStrokeWidth(STROKE_WIDTH); 121 | mHandPaint.setAntiAlias(true); 122 | mHandPaint.setStrokeCap(Paint.Cap.ROUND); 123 | 124 | mCalendar = Calendar.getInstance(); 125 | } 126 | 127 | @Override 128 | public void onDestroy() { 129 | mUpdateTimeHandler.removeMessages(R.id.message_update); 130 | super.onDestroy(); 131 | } 132 | 133 | @Override 134 | public void onTimeTick() { 135 | super.onTimeTick(); 136 | invalidate(); 137 | } 138 | 139 | @Override 140 | public void onAmbientModeChanged(boolean inAmbientMode) { 141 | super.onAmbientModeChanged(inAmbientMode); 142 | if (mAmbient != inAmbientMode) { 143 | mAmbient = inAmbientMode; 144 | invalidate(); 145 | } 146 | 147 | /* 148 | * Whether the timer should be running depends on whether we're visible (as well as 149 | * whether we're in ambient mode), so we may need to start or stop the timer. 150 | */ 151 | updateTimer(); 152 | } 153 | 154 | @Override 155 | public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 156 | super.onSurfaceChanged(holder, format, width, height); 157 | mWidth = width; 158 | mHeight = height; 159 | /* 160 | * Find the coordinates of the center point on the screen. 161 | * Ignore the window insets so that, on round watches 162 | * with a "chin", the watch face is centered on the entire screen, 163 | * not just the usable portion. 164 | */ 165 | mCenterX = mWidth / 2f; 166 | mCenterY = mHeight / 2f; 167 | mScale = ((float) width) / (float) mBackgroundBitmap.getWidth(); 168 | /* 169 | * Calculate the lengths of the watch hands and store them in member variables. 170 | */ 171 | mHourHandLength = mCenterX - 80; 172 | mMinuteHandLength = mCenterX - 40; 173 | mSecondHandLength = mCenterX - 20; 174 | 175 | mBackgroundBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, 176 | (int) (mBackgroundBitmap.getWidth() * mScale), 177 | (int) (mBackgroundBitmap.getHeight() * mScale), true); 178 | } 179 | 180 | @Override 181 | public void onDraw(Canvas canvas, Rect bounds) { 182 | long now = System.currentTimeMillis(); 183 | mCalendar.setTimeInMillis(now); 184 | 185 | // Draw the background. 186 | canvas.drawBitmap(mBackgroundBitmap, 0, 0, mBackgroundPaint); 187 | 188 | /* 189 | * These calculations reflect the rotation in degrees per unit of time, e.g., 190 | * 360 / 60 = 6 and 360 / 12 = 30. 191 | */ 192 | final float seconds = 193 | (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f); 194 | final float secondsRotation = seconds * 6f; 195 | 196 | final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f; 197 | 198 | final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f; 199 | final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset; 200 | 201 | // save the canvas state before we begin to rotate it 202 | canvas.save(); 203 | 204 | canvas.rotate(hoursRotation, mCenterX, mCenterY); 205 | canvas.drawLine(mCenterX, mCenterY, mCenterX, mCenterY - mHourHandLength, mHandPaint); 206 | 207 | canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY); 208 | canvas.drawLine(mCenterX, mCenterY, mCenterX, mCenterY - mMinuteHandLength, mHandPaint); 209 | 210 | if (!mAmbient) { 211 | canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY); 212 | canvas.drawLine(mCenterX, mCenterY, mCenterX, mCenterY - mSecondHandLength, 213 | mHandPaint); 214 | } 215 | // restore the canvas' original orientation. 216 | canvas.restore(); 217 | } 218 | 219 | @Override 220 | public void onVisibilityChanged(boolean visible) { 221 | super.onVisibilityChanged(visible); 222 | 223 | if (visible) { 224 | registerReceiver(); 225 | // Update time zone in case it changed while we weren't visible. 226 | mCalendar.setTimeZone(TimeZone.getDefault()); 227 | invalidate(); 228 | } else { 229 | unregisterReceiver(); 230 | } 231 | 232 | /* 233 | * Whether the timer should be running depends on whether we're visible 234 | * (as well as whether we're in ambient mode), 235 | * so we may need to start or stop the timer. 236 | */ 237 | updateTimer(); 238 | } 239 | 240 | private void registerReceiver() { 241 | if (mRegisteredTimeZoneReceiver) { 242 | return; 243 | } 244 | mRegisteredTimeZoneReceiver = true; 245 | IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 246 | MyWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 247 | } 248 | 249 | private void unregisterReceiver() { 250 | if (!mRegisteredTimeZoneReceiver) { 251 | return; 252 | } 253 | mRegisteredTimeZoneReceiver = false; 254 | MyWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 255 | } 256 | 257 | private void updateTimer() { 258 | mUpdateTimeHandler.removeMessages(R.id.message_update); 259 | if (shouldTimerBeRunning()) { 260 | mUpdateTimeHandler.sendEmptyMessage(R.id.message_update); 261 | } 262 | } 263 | 264 | /** 265 | * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer 266 | * should only run when we're visible and in interactive mode. 267 | */ 268 | private boolean shouldTimerBeRunning() { 269 | return isVisible() && !isInAmbientMode(); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /3-hands/src/main/java/com/android/example/watchface/MyWatchFaceService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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.android.example.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.graphics.Bitmap; 24 | import android.graphics.BitmapFactory; 25 | import android.graphics.Canvas; 26 | import android.graphics.Color; 27 | import android.graphics.Paint; 28 | import android.graphics.Rect; 29 | import android.os.Handler; 30 | import android.os.Message; 31 | import android.support.wearable.watchface.CanvasWatchFaceService; 32 | import android.support.wearable.watchface.WatchFaceStyle; 33 | import android.view.SurfaceHolder; 34 | 35 | import java.util.Calendar; 36 | import java.util.TimeZone; 37 | import java.util.concurrent.TimeUnit; 38 | 39 | /** 40 | * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On 41 | * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode. 42 | */ 43 | public class MyWatchFaceService extends CanvasWatchFaceService { 44 | 45 | /** 46 | * Update rate in milliseconds for interactive mode. We update once a second to advance the 47 | * second hand. 48 | */ 49 | private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); 50 | 51 | @Override 52 | public Engine onCreateEngine() { 53 | return new Engine(); 54 | } 55 | 56 | private class Engine extends CanvasWatchFaceService.Engine { 57 | 58 | /* Handler to update the time once a second in interactive mode. */ 59 | private final Handler mUpdateTimeHandler = new Handler() { 60 | @Override 61 | public void handleMessage(Message message) { 62 | if (R.id.message_update == message.what) { 63 | invalidate(); 64 | if (shouldTimerBeRunning()) { 65 | long timeMs = System.currentTimeMillis(); 66 | long delayMs = INTERACTIVE_UPDATE_RATE_MS 67 | - (timeMs % INTERACTIVE_UPDATE_RATE_MS); 68 | mUpdateTimeHandler.sendEmptyMessageDelayed(R.id.message_update, delayMs); 69 | } 70 | } 71 | } 72 | }; 73 | 74 | private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 75 | @Override 76 | public void onReceive(Context context, Intent intent) { 77 | mCalendar.setTimeZone(TimeZone.getDefault()); 78 | invalidate(); 79 | } 80 | }; 81 | 82 | private boolean mRegisteredTimeZoneReceiver = false; 83 | 84 | // Feel free to change these values and see what happens to the watch face. 85 | private static final float HAND_END_CAP_RADIUS = 4f; 86 | private static final float STROKE_WIDTH = 4f; 87 | private static final int SHADOW_RADIUS = 6; 88 | 89 | private Calendar mCalendar; 90 | 91 | private Paint mBackgroundPaint; 92 | private Paint mHandPaint; 93 | 94 | private boolean mAmbient; 95 | 96 | private Bitmap mBackgroundBitmap; 97 | 98 | private float mHourHandLength; 99 | private float mMinuteHandLength; 100 | private float mSecondHandLength; 101 | 102 | private int mWidth; 103 | private int mHeight; 104 | private float mCenterX; 105 | private float mCenterY; 106 | private float mScale = 1; 107 | 108 | @Override 109 | public void onCreate(SurfaceHolder holder) { 110 | super.onCreate(holder); 111 | 112 | setWatchFaceStyle(new WatchFaceStyle.Builder(MyWatchFaceService.this).build()); 113 | 114 | mBackgroundPaint = new Paint(); 115 | mBackgroundPaint.setColor(Color.BLACK); 116 | 117 | final int backgroundResId = R.drawable.custom_background; 118 | 119 | mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundResId); 120 | mHandPaint = new Paint(); 121 | mHandPaint.setColor(Color.WHITE); 122 | mHandPaint.setStrokeWidth(STROKE_WIDTH); 123 | mHandPaint.setAntiAlias(true); 124 | mHandPaint.setStrokeCap(Paint.Cap.ROUND); 125 | mHandPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); 126 | mHandPaint.setStyle(Paint.Style.STROKE); 127 | 128 | mCalendar = Calendar.getInstance(); 129 | } 130 | 131 | @Override 132 | public void onDestroy() { 133 | mUpdateTimeHandler.removeMessages(R.id.message_update); 134 | super.onDestroy(); 135 | } 136 | 137 | @Override 138 | public void onTimeTick() { 139 | super.onTimeTick(); 140 | invalidate(); 141 | } 142 | 143 | @Override 144 | public void onAmbientModeChanged(boolean inAmbientMode) { 145 | super.onAmbientModeChanged(inAmbientMode); 146 | if (mAmbient != inAmbientMode) { 147 | mAmbient = inAmbientMode; 148 | invalidate(); 149 | } 150 | 151 | /* 152 | * Whether the timer should be running depends on whether we're visible (as well as 153 | * whether we're in ambient mode), so we may need to start or stop the timer. 154 | */ 155 | updateTimer(); 156 | } 157 | 158 | @Override 159 | public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 160 | super.onSurfaceChanged(holder, format, width, height); 161 | mWidth = width; 162 | mHeight = height; 163 | /* 164 | * Find the coordinates of the center point on the screen. 165 | * Ignore the window insets so that, on round watches 166 | * with a "chin", the watch face is centered on the entire screen, 167 | * not just the usable portion. 168 | */ 169 | mCenterX = mWidth / 2f; 170 | mCenterY = mHeight / 2f; 171 | mScale = ((float) width) / (float) mBackgroundBitmap.getWidth(); 172 | /* 173 | * Calculate the lengths of the watch hands and store them in member variables. 174 | */ 175 | mHourHandLength = mCenterX * 0.5f; 176 | mMinuteHandLength = mCenterX * 0.7f; 177 | mSecondHandLength = mCenterX * 0.9f; 178 | 179 | mBackgroundBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, 180 | (int) (mBackgroundBitmap.getWidth() * mScale), 181 | (int) (mBackgroundBitmap.getHeight() * mScale), true); 182 | } 183 | 184 | @Override 185 | public void onDraw(Canvas canvas, Rect bounds) { 186 | long now = System.currentTimeMillis(); 187 | mCalendar.setTimeInMillis(now); 188 | 189 | // Draw the background. 190 | canvas.drawBitmap(mBackgroundBitmap, 0, 0, mBackgroundPaint); 191 | 192 | /* 193 | * These calculations reflect the rotation in degrees per unit of time, e.g., 194 | * 360 / 60 = 6 and 360 / 12 = 30. 195 | */ 196 | final float seconds = 197 | (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f); 198 | final float secondsRotation = seconds * 6f; 199 | 200 | final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f; 201 | 202 | final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f; 203 | final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset; 204 | 205 | // save the canvas state before we begin to rotate it 206 | canvas.save(); 207 | 208 | canvas.rotate(hoursRotation, mCenterX, mCenterY); 209 | drawHand(canvas, mHourHandLength); 210 | 211 | canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY); 212 | drawHand(canvas, mMinuteHandLength); 213 | 214 | canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY); 215 | canvas.drawLine(mCenterX, mCenterY - HAND_END_CAP_RADIUS, mCenterX, 216 | mCenterY - mSecondHandLength, mHandPaint); 217 | canvas.drawCircle(mCenterX, mCenterY, HAND_END_CAP_RADIUS, mHandPaint); 218 | // restore the canvas' original orientation. 219 | canvas.restore(); 220 | } 221 | 222 | private void drawHand(Canvas canvas, float handLength) { 223 | canvas.drawRoundRect(mCenterX - HAND_END_CAP_RADIUS, mCenterY - handLength, 224 | mCenterX + HAND_END_CAP_RADIUS, mCenterY + HAND_END_CAP_RADIUS, 225 | HAND_END_CAP_RADIUS, HAND_END_CAP_RADIUS, mHandPaint); 226 | } 227 | 228 | @Override 229 | public void onVisibilityChanged(boolean visible) { 230 | super.onVisibilityChanged(visible); 231 | 232 | if (visible) { 233 | registerReceiver(); 234 | // Update time zone in case it changed while we weren't visible. 235 | mCalendar.setTimeZone(TimeZone.getDefault()); 236 | invalidate(); 237 | } else { 238 | unregisterReceiver(); 239 | } 240 | 241 | /* 242 | * Whether the timer should be running depends on whether we're visible 243 | * (as well as whether we're in ambient mode), 244 | * so we may need to start or stop the timer. 245 | */ 246 | updateTimer(); 247 | } 248 | 249 | private void registerReceiver() { 250 | if (mRegisteredTimeZoneReceiver) { 251 | return; 252 | } 253 | mRegisteredTimeZoneReceiver = true; 254 | IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 255 | MyWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 256 | } 257 | 258 | private void unregisterReceiver() { 259 | if (!mRegisteredTimeZoneReceiver) { 260 | return; 261 | } 262 | mRegisteredTimeZoneReceiver = false; 263 | MyWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 264 | } 265 | 266 | private void updateTimer() { 267 | mUpdateTimeHandler.removeMessages(R.id.message_update); 268 | if (shouldTimerBeRunning()) { 269 | mUpdateTimeHandler.sendEmptyMessage(R.id.message_update); 270 | } 271 | } 272 | 273 | /** 274 | * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer 275 | * should only run when we're visible and in interactive mode. 276 | */ 277 | private boolean shouldTimerBeRunning() { 278 | return isVisible() && !isInAmbientMode(); 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /4-ambient/src/main/java/com/android/example/watchface/MyWatchFaceService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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.android.example.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.graphics.Bitmap; 24 | import android.graphics.BitmapFactory; 25 | import android.graphics.Canvas; 26 | import android.graphics.Color; 27 | import android.graphics.ColorMatrix; 28 | import android.graphics.ColorMatrixColorFilter; 29 | import android.graphics.Paint; 30 | import android.graphics.Rect; 31 | import android.os.Bundle; 32 | import android.os.Handler; 33 | import android.os.Message; 34 | import android.support.wearable.watchface.CanvasWatchFaceService; 35 | import android.support.wearable.watchface.WatchFaceStyle; 36 | import android.view.SurfaceHolder; 37 | 38 | import java.util.Calendar; 39 | import java.util.TimeZone; 40 | import java.util.concurrent.TimeUnit; 41 | 42 | /** 43 | * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On 44 | * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode. 45 | */ 46 | public class MyWatchFaceService extends CanvasWatchFaceService { 47 | 48 | /** 49 | * Update rate in milliseconds for interactive mode. We update once a second to advance the 50 | * second hand. 51 | */ 52 | private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); 53 | 54 | @Override 55 | public Engine onCreateEngine() { 56 | return new Engine(); 57 | } 58 | 59 | private class Engine extends CanvasWatchFaceService.Engine { 60 | 61 | /* Handler to update the time once a second in interactive mode. */ 62 | private final Handler mUpdateTimeHandler = new Handler() { 63 | @Override 64 | public void handleMessage(Message message) { 65 | if (R.id.message_update == message.what) { 66 | invalidate(); 67 | if (shouldTimerBeRunning()) { 68 | long timeMs = System.currentTimeMillis(); 69 | long delayMs = INTERACTIVE_UPDATE_RATE_MS 70 | - (timeMs % INTERACTIVE_UPDATE_RATE_MS); 71 | mUpdateTimeHandler.sendEmptyMessageDelayed(R.id.message_update, delayMs); 72 | } 73 | } 74 | } 75 | }; 76 | 77 | private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 78 | @Override 79 | public void onReceive(Context context, Intent intent) { 80 | mCalendar.setTimeZone(TimeZone.getDefault()); 81 | invalidate(); 82 | } 83 | }; 84 | 85 | private boolean mRegisteredTimeZoneReceiver = false; 86 | 87 | // Feel free to change these values and see what happens to the watch face. 88 | private static final float HAND_END_CAP_RADIUS = 4f; 89 | private static final float STROKE_WIDTH = 4f; 90 | private static final int SHADOW_RADIUS = 6; 91 | 92 | private Calendar mCalendar; 93 | 94 | private Paint mBackgroundPaint; 95 | private Paint mHandPaint; 96 | 97 | private boolean mAmbient; 98 | 99 | private Bitmap mBackgroundBitmap; 100 | private Bitmap mGrayBackgroundBitmap; 101 | 102 | private float mHourHandLength; 103 | private float mMinuteHandLength; 104 | private float mSecondHandLength; 105 | 106 | /** 107 | * Whether the display supports fewer bits for each color in ambient mode. 108 | * When true, we disable anti-aliasing in ambient mode. 109 | */ 110 | private boolean mLowBitAmbient; 111 | /** 112 | * Whether the display supports burn in protection in ambient mode. 113 | * When true, remove the background in ambient mode. 114 | */ 115 | private boolean mBurnInProtection; 116 | 117 | private int mWidth; 118 | private int mHeight; 119 | private float mCenterX; 120 | private float mCenterY; 121 | private float mScale = 1; 122 | 123 | @Override 124 | public void onCreate(SurfaceHolder holder) { 125 | super.onCreate(holder); 126 | 127 | setWatchFaceStyle(new WatchFaceStyle.Builder(MyWatchFaceService.this).build()); 128 | 129 | mBackgroundPaint = new Paint(); 130 | mBackgroundPaint.setColor(Color.BLACK); 131 | 132 | final int backgroundResId = R.drawable.custom_background; 133 | 134 | mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundResId); 135 | mHandPaint = new Paint(); 136 | mHandPaint.setColor(Color.WHITE); 137 | mHandPaint.setStrokeWidth(STROKE_WIDTH); 138 | mHandPaint.setAntiAlias(true); 139 | mHandPaint.setStrokeCap(Paint.Cap.ROUND); 140 | mHandPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); 141 | mHandPaint.setStyle(Paint.Style.STROKE); 142 | 143 | mCalendar = Calendar.getInstance(); 144 | } 145 | 146 | @Override 147 | public void onDestroy() { 148 | mUpdateTimeHandler.removeMessages(R.id.message_update); 149 | super.onDestroy(); 150 | } 151 | 152 | @Override 153 | public void onPropertiesChanged(Bundle properties) { 154 | super.onPropertiesChanged(properties); 155 | mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); 156 | mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false); 157 | } 158 | 159 | @Override 160 | public void onTimeTick() { 161 | super.onTimeTick(); 162 | invalidate(); 163 | } 164 | 165 | @Override 166 | public void onAmbientModeChanged(boolean inAmbientMode) { 167 | super.onAmbientModeChanged(inAmbientMode); 168 | if (mAmbient != inAmbientMode) { 169 | mAmbient = inAmbientMode; 170 | if (mLowBitAmbient || mBurnInProtection) { 171 | mHandPaint.setAntiAlias(!inAmbientMode); 172 | } 173 | invalidate(); 174 | } 175 | 176 | /* 177 | * Whether the timer should be running depends on whether we're visible (as well as 178 | * whether we're in ambient mode), so we may need to start or stop the timer. 179 | */ 180 | updateTimer(); 181 | } 182 | 183 | @Override 184 | public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 185 | super.onSurfaceChanged(holder, format, width, height); 186 | mWidth = width; 187 | mHeight = height; 188 | /* 189 | * Find the coordinates of the center point on the screen. 190 | * Ignore the window insets so that, on round watches 191 | * with a "chin", the watch face is centered on the entire screen, 192 | * not just the usable portion. 193 | */ 194 | mCenterX = mWidth / 2f; 195 | mCenterY = mHeight / 2f; 196 | mScale = ((float) width) / (float) mBackgroundBitmap.getWidth(); 197 | /* 198 | * Calculate the lengths of the watch hands and store them in member variables. 199 | */ 200 | mHourHandLength = mCenterX * 0.5f; 201 | mMinuteHandLength = mCenterX * 0.7f; 202 | mSecondHandLength = mCenterX * 0.9f; 203 | 204 | mBackgroundBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, 205 | (int) (mBackgroundBitmap.getWidth() * mScale), 206 | (int) (mBackgroundBitmap.getHeight() * mScale), true); 207 | 208 | if (!mBurnInProtection || !mLowBitAmbient) { 209 | initGrayBackgroundBitmap(); 210 | } 211 | } 212 | 213 | private void initGrayBackgroundBitmap() { 214 | mGrayBackgroundBitmap = Bitmap.createBitmap(mBackgroundBitmap.getWidth(), 215 | mBackgroundBitmap.getHeight(), Bitmap.Config.ARGB_8888); 216 | Canvas canvas = new Canvas(mGrayBackgroundBitmap); 217 | Paint grayPaint = new Paint(); 218 | ColorMatrix colorMatrix = new ColorMatrix(); 219 | colorMatrix.setSaturation(0); 220 | ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix); 221 | grayPaint.setColorFilter(filter); 222 | canvas.drawBitmap(mBackgroundBitmap, 0, 0, grayPaint); 223 | } 224 | 225 | @Override 226 | public void onDraw(Canvas canvas, Rect bounds) { 227 | long now = System.currentTimeMillis(); 228 | mCalendar.setTimeInMillis(now); 229 | 230 | if (mAmbient && (mLowBitAmbient || mBurnInProtection)) { 231 | canvas.drawColor(Color.BLACK); 232 | } else if (mAmbient) { 233 | canvas.drawBitmap(mGrayBackgroundBitmap, 0, 0, mBackgroundPaint); 234 | } else { 235 | canvas.drawBitmap(mBackgroundBitmap, 0, 0, mBackgroundPaint); 236 | } 237 | 238 | /* 239 | * These calculations reflect the rotation in degrees per unit of time, e.g., 240 | * 360 / 60 = 6 and 360 / 12 = 30. 241 | */ 242 | final float seconds = 243 | (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f); 244 | final float secondsRotation = seconds * 6f; 245 | 246 | final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f; 247 | 248 | final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f; 249 | final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset; 250 | 251 | // save the canvas state before we begin to rotate it 252 | canvas.save(); 253 | 254 | canvas.rotate(hoursRotation, mCenterX, mCenterY); 255 | drawHand(canvas, mHourHandLength); 256 | 257 | canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY); 258 | drawHand(canvas, mMinuteHandLength); 259 | 260 | /* 261 | * Make sure the "seconds" hand is drawn only when we are in interactive mode. 262 | * Otherwise we only update the watch face once a minute. 263 | */ 264 | if (!mAmbient) { 265 | canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY); 266 | canvas.drawLine(mCenterX, mCenterY - HAND_END_CAP_RADIUS, mCenterX, 267 | mCenterY - mSecondHandLength, mHandPaint); 268 | } 269 | canvas.drawCircle(mCenterX, mCenterY, HAND_END_CAP_RADIUS, mHandPaint); 270 | // restore the canvas' original orientation. 271 | canvas.restore(); 272 | } 273 | 274 | private void drawHand(Canvas canvas, float handLength) { 275 | canvas.drawRoundRect(mCenterX - HAND_END_CAP_RADIUS, mCenterY - handLength, 276 | mCenterX + HAND_END_CAP_RADIUS, mCenterY + HAND_END_CAP_RADIUS, 277 | HAND_END_CAP_RADIUS, HAND_END_CAP_RADIUS, mHandPaint); 278 | } 279 | 280 | @Override 281 | public void onVisibilityChanged(boolean visible) { 282 | super.onVisibilityChanged(visible); 283 | 284 | if (visible) { 285 | registerReceiver(); 286 | // Update time zone in case it changed while we weren't visible. 287 | mCalendar.setTimeZone(TimeZone.getDefault()); 288 | invalidate(); 289 | } else { 290 | unregisterReceiver(); 291 | } 292 | 293 | /* 294 | * Whether the timer should be running depends on whether we're visible 295 | * (as well as whether we're in ambient mode), 296 | * so we may need to start or stop the timer. 297 | */ 298 | updateTimer(); 299 | } 300 | 301 | private void registerReceiver() { 302 | if (mRegisteredTimeZoneReceiver) { 303 | return; 304 | } 305 | mRegisteredTimeZoneReceiver = true; 306 | IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 307 | MyWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 308 | } 309 | 310 | private void unregisterReceiver() { 311 | if (!mRegisteredTimeZoneReceiver) { 312 | return; 313 | } 314 | mRegisteredTimeZoneReceiver = false; 315 | MyWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 316 | } 317 | 318 | private void updateTimer() { 319 | mUpdateTimeHandler.removeMessages(R.id.message_update); 320 | if (shouldTimerBeRunning()) { 321 | mUpdateTimeHandler.sendEmptyMessage(R.id.message_update); 322 | } 323 | } 324 | 325 | /** 326 | * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer 327 | * should only run when we're visible and in interactive mode. 328 | */ 329 | private boolean shouldTimerBeRunning() { 330 | return isVisible() && !isInAmbientMode(); 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /5-palette/src/main/java/com/android/example/watchface/MyWatchFaceService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 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.android.example.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.graphics.Bitmap; 24 | import android.graphics.BitmapFactory; 25 | import android.graphics.Canvas; 26 | import android.graphics.Color; 27 | import android.graphics.ColorMatrix; 28 | import android.graphics.ColorMatrixColorFilter; 29 | import android.graphics.Paint; 30 | import android.graphics.Rect; 31 | import android.os.Bundle; 32 | import android.os.Handler; 33 | import android.os.Message; 34 | import android.support.v7.graphics.Palette; 35 | import android.support.wearable.watchface.CanvasWatchFaceService; 36 | import android.support.wearable.watchface.WatchFaceStyle; 37 | import android.util.Log; 38 | import android.view.SurfaceHolder; 39 | 40 | import java.util.Calendar; 41 | import java.util.TimeZone; 42 | import java.util.concurrent.TimeUnit; 43 | 44 | /** 45 | * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On 46 | * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode. 47 | */ 48 | public class MyWatchFaceService extends CanvasWatchFaceService { 49 | 50 | /** 51 | * Update rate in milliseconds for interactive mode. We update once a second to advance the 52 | * second hand. 53 | */ 54 | private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); 55 | 56 | @Override 57 | public Engine onCreateEngine() { 58 | return new Engine(); 59 | } 60 | 61 | private class Engine extends CanvasWatchFaceService.Engine { 62 | 63 | /* Handler to update the time once a second in interactive mode. */ 64 | private final Handler mUpdateTimeHandler = new Handler() { 65 | @Override 66 | public void handleMessage(Message message) { 67 | if (R.id.message_update == message.what) { 68 | invalidate(); 69 | if (shouldTimerBeRunning()) { 70 | long timeMs = System.currentTimeMillis(); 71 | long delayMs = INTERACTIVE_UPDATE_RATE_MS 72 | - (timeMs % INTERACTIVE_UPDATE_RATE_MS); 73 | mUpdateTimeHandler.sendEmptyMessageDelayed(R.id.message_update, delayMs); 74 | } 75 | } 76 | } 77 | }; 78 | 79 | private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 80 | @Override 81 | public void onReceive(Context context, Intent intent) { 82 | mCalendar.setTimeZone(TimeZone.getDefault()); 83 | invalidate(); 84 | } 85 | }; 86 | 87 | private boolean mRegisteredTimeZoneReceiver = false; 88 | 89 | // Feel free to change these values and see what happens to the watch face. 90 | private static final float HAND_END_CAP_RADIUS = 4f; 91 | private static final float STROKE_WIDTH = 4f; 92 | private static final int SHADOW_RADIUS = 6; 93 | 94 | private Calendar mCalendar; 95 | 96 | private Paint mBackgroundPaint; 97 | private Paint mHandPaint; 98 | 99 | private boolean mAmbient; 100 | 101 | private Bitmap mBackgroundBitmap; 102 | private Bitmap mGrayBackgroundBitmap; 103 | private int mWatchHandColor; 104 | private int mWatchHandShadowColor; 105 | 106 | private float mHourHandLength; 107 | private float mMinuteHandLength; 108 | private float mSecondHandLength; 109 | 110 | /** 111 | * Whether the display supports fewer bits for each color in ambient mode. 112 | * When true, we disable anti-aliasing in ambient mode. 113 | */ 114 | private boolean mLowBitAmbient; 115 | /** 116 | * Whether the display supports burn in protection in ambient mode. 117 | * When true, remove the background in ambient mode. 118 | */ 119 | private boolean mBurnInProtection; 120 | 121 | private int mWidth; 122 | private int mHeight; 123 | private float mCenterX; 124 | private float mCenterY; 125 | private float mScale = 1; 126 | 127 | @Override 128 | public void onCreate(SurfaceHolder holder) { 129 | super.onCreate(holder); 130 | 131 | setWatchFaceStyle(new WatchFaceStyle.Builder(MyWatchFaceService.this).build()); 132 | 133 | mBackgroundPaint = new Paint(); 134 | mBackgroundPaint.setColor(Color.BLACK); 135 | 136 | /* 137 | * Toggle the backgroundResIds to see 138 | * the change of colors due to palette doing its magic. 139 | */ 140 | final int backgroundResId = R.drawable.custom_background; 141 | //final int backgroundResId = R.drawable.custom_background2; 142 | 143 | mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundResId); 144 | mHandPaint = new Paint(); 145 | mHandPaint.setColor(Color.WHITE); 146 | mHandPaint.setStrokeWidth(STROKE_WIDTH); 147 | mHandPaint.setAntiAlias(true); 148 | mHandPaint.setStrokeCap(Paint.Cap.ROUND); 149 | mHandPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); 150 | mHandPaint.setStyle(Paint.Style.STROKE); 151 | 152 | // Asynchronous call to generate Palette 153 | Palette.from(mBackgroundBitmap).generate( 154 | new Palette.PaletteAsyncListener() { 155 | public void onGenerated(Palette palette) { 156 | /* 157 | * Sometimes, palette is unable to generate a color palette 158 | * so we need to check that we have one. 159 | */ 160 | if (palette != null) { 161 | Log.d("onGenerated", palette.toString()); 162 | mWatchHandColor = palette.getVibrantColor(Color.WHITE); 163 | mWatchHandShadowColor = palette.getDarkMutedColor(Color.BLACK); 164 | setWatchHandColor(); 165 | } 166 | } 167 | }); 168 | 169 | 170 | 171 | mCalendar = Calendar.getInstance(); 172 | } 173 | 174 | private void setWatchHandColor() { 175 | if (mAmbient) { 176 | mHandPaint.setColor(Color.WHITE); 177 | mHandPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); 178 | } else { 179 | mHandPaint.setColor(mWatchHandColor); 180 | mHandPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 181 | } 182 | } 183 | 184 | @Override 185 | public void onDestroy() { 186 | mUpdateTimeHandler.removeMessages(R.id.message_update); 187 | super.onDestroy(); 188 | } 189 | 190 | @Override 191 | public void onPropertiesChanged(Bundle properties) { 192 | super.onPropertiesChanged(properties); 193 | mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); 194 | mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false); 195 | } 196 | 197 | @Override 198 | public void onTimeTick() { 199 | super.onTimeTick(); 200 | invalidate(); 201 | } 202 | 203 | @Override 204 | public void onAmbientModeChanged(boolean inAmbientMode) { 205 | super.onAmbientModeChanged(inAmbientMode); 206 | if (mAmbient != inAmbientMode) { 207 | mAmbient = inAmbientMode; 208 | if (mLowBitAmbient || mBurnInProtection) { 209 | mHandPaint.setAntiAlias(!inAmbientMode); 210 | } 211 | setWatchHandColor(); 212 | invalidate(); 213 | } 214 | 215 | /* 216 | * Whether the timer should be running depends on whether we're visible (as well as 217 | * whether we're in ambient mode), so we may need to start or stop the timer. 218 | */ 219 | updateTimer(); 220 | } 221 | 222 | @Override 223 | public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 224 | super.onSurfaceChanged(holder, format, width, height); 225 | mWidth = width; 226 | mHeight = height; 227 | /* 228 | * Find the coordinates of the center point on the screen. 229 | * Ignore the window insets so that, on round watches 230 | * with a "chin", the watch face is centered on the entire screen, 231 | * not just the usable portion. 232 | */ 233 | mCenterX = mWidth / 2f; 234 | mCenterY = mHeight / 2f; 235 | mScale = ((float) width) / (float) mBackgroundBitmap.getWidth(); 236 | /* 237 | * Calculate the lengths of the watch hands and store them in member variables. 238 | */ 239 | mHourHandLength = mCenterX * 0.5f; 240 | mMinuteHandLength = mCenterX * 0.7f; 241 | mSecondHandLength = mCenterX * 0.9f; 242 | 243 | mBackgroundBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, 244 | (int) (mBackgroundBitmap.getWidth() * mScale), 245 | (int) (mBackgroundBitmap.getHeight() * mScale), true); 246 | 247 | if (!mBurnInProtection || !mLowBitAmbient) { 248 | initGrayBackgroundBitmap(); 249 | } 250 | } 251 | 252 | private void initGrayBackgroundBitmap() { 253 | mGrayBackgroundBitmap = Bitmap.createBitmap(mBackgroundBitmap.getWidth(), 254 | mBackgroundBitmap.getHeight(), Bitmap.Config.ARGB_8888); 255 | Canvas canvas = new Canvas(mGrayBackgroundBitmap); 256 | Paint grayPaint = new Paint(); 257 | ColorMatrix colorMatrix = new ColorMatrix(); 258 | colorMatrix.setSaturation(0); 259 | ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix); 260 | grayPaint.setColorFilter(filter); 261 | canvas.drawBitmap(mBackgroundBitmap, 0, 0, grayPaint); 262 | } 263 | 264 | @Override 265 | public void onDraw(Canvas canvas, Rect bounds) { 266 | long now = System.currentTimeMillis(); 267 | mCalendar.setTimeInMillis(now); 268 | 269 | if (mAmbient && (mLowBitAmbient || mBurnInProtection)) { 270 | canvas.drawColor(Color.BLACK); 271 | } else if (mAmbient) { 272 | canvas.drawBitmap(mGrayBackgroundBitmap, 0, 0, mBackgroundPaint); 273 | } else { 274 | canvas.drawBitmap(mBackgroundBitmap, 0, 0, mBackgroundPaint); 275 | } 276 | 277 | /* 278 | * These calculations reflect the rotation in degrees per unit of time, e.g., 279 | * 360 / 60 = 6 and 360 / 12 = 30. 280 | */ 281 | final float seconds = 282 | (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f); 283 | final float secondsRotation = seconds * 6f; 284 | 285 | final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f; 286 | 287 | final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f; 288 | final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset; 289 | 290 | // save the canvas state before we begin to rotate it 291 | canvas.save(); 292 | 293 | canvas.rotate(hoursRotation, mCenterX, mCenterY); 294 | drawHand(canvas, mHourHandLength); 295 | 296 | canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY); 297 | drawHand(canvas, mMinuteHandLength); 298 | 299 | /* 300 | * Make sure the "seconds" hand is drawn only when we are in interactive mode. 301 | * Otherwise we only update the watch face once a minute. 302 | */ 303 | if (!mAmbient) { 304 | canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY); 305 | canvas.drawLine(mCenterX, mCenterY - HAND_END_CAP_RADIUS, mCenterX, 306 | mCenterY - mSecondHandLength, mHandPaint); 307 | } 308 | canvas.drawCircle(mCenterX, mCenterY, HAND_END_CAP_RADIUS, mHandPaint); 309 | // restore the canvas' original orientation. 310 | canvas.restore(); 311 | } 312 | 313 | private void drawHand(Canvas canvas, float handLength) { 314 | canvas.drawRoundRect(mCenterX - HAND_END_CAP_RADIUS, mCenterY - handLength, 315 | mCenterX + HAND_END_CAP_RADIUS, mCenterY + HAND_END_CAP_RADIUS, 316 | HAND_END_CAP_RADIUS, HAND_END_CAP_RADIUS, mHandPaint); 317 | } 318 | 319 | @Override 320 | public void onVisibilityChanged(boolean visible) { 321 | super.onVisibilityChanged(visible); 322 | 323 | if (visible) { 324 | registerReceiver(); 325 | 326 | // Update time zone in case it changed while we weren't visible. 327 | mCalendar.setTimeZone(TimeZone.getDefault()); 328 | invalidate(); 329 | } else { 330 | unregisterReceiver(); 331 | } 332 | 333 | /* 334 | * Whether the timer should be running depends on whether we're visible 335 | * (as well as whether we're in ambient mode), 336 | * so we may need to start or stop the timer. 337 | */ 338 | updateTimer(); 339 | } 340 | 341 | private void registerReceiver() { 342 | if (mRegisteredTimeZoneReceiver) { 343 | return; 344 | } 345 | mRegisteredTimeZoneReceiver = true; 346 | IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 347 | MyWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 348 | } 349 | 350 | private void unregisterReceiver() { 351 | if (!mRegisteredTimeZoneReceiver) { 352 | return; 353 | } 354 | mRegisteredTimeZoneReceiver = false; 355 | MyWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 356 | } 357 | 358 | private void updateTimer() { 359 | mUpdateTimeHandler.removeMessages(R.id.message_update); 360 | if (shouldTimerBeRunning()) { 361 | mUpdateTimeHandler.sendEmptyMessage(R.id.message_update); 362 | } 363 | } 364 | 365 | /** 366 | * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer 367 | * should only run when we're visible and in interactive mode. 368 | */ 369 | private boolean shouldTimerBeRunning() { 370 | return isVisible() && !isInAmbientMode(); 371 | } 372 | } 373 | } 374 | --------------------------------------------------------------------------------