+
+
+
+
+
diff --git a/samples/browseable/SpeedTracker/Application/res/values-v21/base-template-styles.xml b/samples/browseable/SpeedTracker/Application/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/SpeedTracker/Application/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/SpeedTracker/Application/res/values/base-strings.xml b/samples/browseable/SpeedTracker/Application/res/values/base-strings.xml
index 55a45f396..0d22a8789 100644
--- a/samples/browseable/SpeedTracker/Application/res/values/base-strings.xml
+++ b/samples/browseable/SpeedTracker/Application/res/values/base-strings.xml
@@ -20,14 +20,14 @@
+ android:targetSdkVersion="21" />
-
\ No newline at end of file
+
diff --git a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java
index 9610f227b..f3015bf87 100644
--- a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java
+++ b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.java
@@ -336,8 +336,8 @@ public class WearableMainActivity extends Activity implements GoogleApiClient.Co
private void updateRecordingIcon() {
mSaveGpsLocation = LocationSettingActivity.getGpsRecordingStatusFromPreferences(this);
- mSaveImageView.setImageResource(mSaveGpsLocation ? R.drawable.ic_file_download_googblue_24dp
- : R.drawable.ic_file_download_grey600_24dp);
+ mSaveImageView.setImageResource(mSaveGpsLocation ? R.drawable.ic_gps_saving_grey600_96dp
+ : R.drawable.ic_gps_not_saving_grey600_96dp);
}
/**
diff --git a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/SpeedPickerLayout.java b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/SpeedPickerLayout.java
index 9fd882d9a..5796c1367 100644
--- a/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/SpeedPickerLayout.java
+++ b/samples/browseable/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/ui/SpeedPickerLayout.java
@@ -30,13 +30,13 @@ import com.example.android.wearable.speedtracker.R;
* A simple extension of the {@link android.widget.LinearLayout} to represent a single item in a
* {@link android.support.wearable.view.WearableListView}.
*/
-public class SpeedPickerLayout extends LinearLayout implements WearableListView.Item {
+public class SpeedPickerLayout extends LinearLayout
+ implements WearableListView.OnCenterProximityListener {
private final float mFadedTextAlpha;
private final int mFadedCircleColor;
private final int mChosenCircleColor;
private ImageView mCircle;
- private float mScale;
private TextView mName;
public SpeedPickerLayout(Context context) {
@@ -64,41 +64,16 @@ public class SpeedPickerLayout extends LinearLayout implements WearableListView.
mName = (TextView) findViewById(R.id.name);
}
- // Provide scaling values for WearableListView animations
@Override
- public float getProximityMinValue() {
- return 1f;
- }
-
- @Override
- public float getProximityMaxValue() {
- return 1.6f;
- }
-
- @Override
- public float getCurrentProximityValue() {
- return mScale;
- }
-
- // Scale the icon for WearableListView animations
- @Override
- public void setScalingAnimatorValue(float scale) {
- mScale = scale;
- mCircle.setScaleX(scale);
- mCircle.setScaleY(scale);
- }
-
- // Change color of the icon, remove fading from the text
- @Override
- public void onScaleUpStart() {
+ public void onCenterPosition(boolean animate) {
mName.setAlpha(1f);
((GradientDrawable) mCircle.getDrawable()).setColor(mChosenCircleColor);
}
- // Change the color of the icon, fade the text
@Override
- public void onScaleDownStart() {
+ public void onNonCenterPosition(boolean animate) {
((GradientDrawable) mCircle.getDrawable()).setColor(mFadedCircleColor);
mName.setAlpha(mFadedTextAlpha);
+
}
}
diff --git a/samples/browseable/SpeedTracker/_index.jd b/samples/browseable/SpeedTracker/_index.jd
index f16522bf3..298926f1f 100644
--- a/samples/browseable/SpeedTracker/_index.jd
+++ b/samples/browseable/SpeedTracker/_index.jd
@@ -4,14 +4,14 @@ sample.group=Wearable
-This sample uses the FusedLocation APIs of GMS on those
-devices that have a hardware GPS built in. In those cases,
-this sample provides a simple screen that shows the current
-speed of the device on the watch. User can set a speed limit
-and if the speed approaches that limit, it changes the color
-to yellow and if it exceeds the limit, it turns red. User
-can also enable recording of coordinates and when it pairs
-back with the phone, this data will be synced with the phone
+This sample uses the FusedLocation APIs of Google Play Services
+on those devices that have a hardware GPS built in. In those
+cases, this sample provides a simple screen that shows the
+current speed of the device on the watch. User can set a speed
+limit and if the speed approaches that limit, it changes the
+color to yellow and if it exceeds the limit, it turns red. User
+can also enable recording of coordinates and when it pairs back
+with the phone, this data will be synced with the phone
component of the app and user can see a track made of those
coordinates on a map on the phone.
diff --git a/samples/browseable/StorageClient/res/values-v21/base-colors.xml b/samples/browseable/StorageClient/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/StorageClient/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/StorageClient/res/values-v21/base-template-styles.xml b/samples/browseable/StorageClient/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/StorageClient/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/StorageClient/res/values-v21/template-styles.xml b/samples/browseable/StorageClient/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/StorageClient/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/StorageProvider/res/drawable-hdpi/ic_launcher.png b/samples/browseable/StorageProvider/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
index c5d697220..8ab9c3c7c
Binary files a/samples/browseable/StorageProvider/res/drawable-hdpi/ic_launcher.png and b/samples/browseable/StorageProvider/res/drawable-hdpi/ic_launcher.png differ
diff --git a/samples/browseable/StorageProvider/res/drawable-mdpi/ic_launcher.png b/samples/browseable/StorageProvider/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
index 59e6bd9ea..8a2df9469
Binary files a/samples/browseable/StorageProvider/res/drawable-mdpi/ic_launcher.png and b/samples/browseable/StorageProvider/res/drawable-mdpi/ic_launcher.png differ
diff --git a/samples/browseable/StorageProvider/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/StorageProvider/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 5b8b7be20..9c196e17b
Binary files a/samples/browseable/StorageProvider/res/drawable-xhdpi/ic_launcher.png and b/samples/browseable/StorageProvider/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/samples/browseable/StorageProvider/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/StorageProvider/res/drawable-xxhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 474bbd248..558f9012b
Binary files a/samples/browseable/StorageProvider/res/drawable-xxhdpi/ic_launcher.png and b/samples/browseable/StorageProvider/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/samples/browseable/StorageProvider/res/ic_launcher.png b/samples/browseable/StorageProvider/res/ic_launcher.png
deleted file mode 100755
index c5d697220..000000000
Binary files a/samples/browseable/StorageProvider/res/ic_launcher.png and /dev/null differ
diff --git a/samples/browseable/StorageProvider/res/values-v21/base-colors.xml b/samples/browseable/StorageProvider/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/StorageProvider/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/StorageProvider/res/values-v21/base-template-styles.xml b/samples/browseable/StorageProvider/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/StorageProvider/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/StorageProvider/res/values-v21/template-styles.xml b/samples/browseable/StorageProvider/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/StorageProvider/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/base-colors.xml b/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/base-template-styles.xml b/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/template-styles.xml b/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/SwipeRefreshLayoutBasic/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/SwipeRefreshListFragment/res/values-v21/base-colors.xml b/samples/browseable/SwipeRefreshListFragment/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/SwipeRefreshListFragment/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/SwipeRefreshListFragment/res/values-v21/base-template-styles.xml b/samples/browseable/SwipeRefreshListFragment/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/SwipeRefreshListFragment/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/SwipeRefreshListFragment/res/values-v21/template-styles.xml b/samples/browseable/SwipeRefreshListFragment/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/SwipeRefreshListFragment/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/base-colors.xml b/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/base-template-styles.xml b/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/template-styles.xml b/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/SwipeRefreshMultipleViews/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml b/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml
index eb6a1495e..1737c7da0 100644
--- a/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml
+++ b/samples/browseable/SynchronizedNotifications/Application/AndroidManifest.xml
@@ -22,8 +22,8 @@
android:versionCode="1"
android:versionName="1.0">
-
+
-
-
-
-
+ android:orientation="vertical">
+
+
+
+
diff --git a/samples/browseable/SynchronizedNotifications/Application/res/layout-sw600dp/activity_main.xml b/samples/browseable/SynchronizedNotifications/Application/res/layout-sw600dp/activity_main.xml
index 11cd71b8e..f811ef7dc 100644
--- a/samples/browseable/SynchronizedNotifications/Application/res/layout-sw600dp/activity_main.xml
+++ b/samples/browseable/SynchronizedNotifications/Application/res/layout-sw600dp/activity_main.xml
@@ -13,24 +13,28 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
-
-
+ android:orientation="vertical">
+
+
+
+
diff --git a/samples/browseable/SynchronizedNotifications/Application/res/layout/activity_main.xml b/samples/browseable/SynchronizedNotifications/Application/res/layout/activity_main.xml
index 6f41369cf..d17095875 100644
--- a/samples/browseable/SynchronizedNotifications/Application/res/layout/activity_main.xml
+++ b/samples/browseable/SynchronizedNotifications/Application/res/layout/activity_main.xml
@@ -13,27 +13,31 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+
+
+
+
diff --git a/samples/browseable/SynchronizedNotifications/Application/res/values-v21/base-colors.xml b/samples/browseable/SynchronizedNotifications/Application/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/SynchronizedNotifications/Application/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/SynchronizedNotifications/Application/res/values-v21/base-template-styles.xml b/samples/browseable/SynchronizedNotifications/Application/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/SynchronizedNotifications/Application/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/SynchronizedNotifications/Application/res/values-v21/template-styles.xml b/samples/browseable/SynchronizedNotifications/Application/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/SynchronizedNotifications/Application/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/SynchronizedNotifications/Application/src/com.example.android.wearable.synchronizednotifications/MainActivity.java b/samples/browseable/SynchronizedNotifications/Application/src/com.example.android.wearable.synchronizednotifications/MainActivity.java
index 994643065..ad1b372ea 100644
--- a/samples/browseable/SynchronizedNotifications/Application/src/com.example.android.wearable.synchronizednotifications/MainActivity.java
+++ b/samples/browseable/SynchronizedNotifications/Application/src/com.example.android.wearable.synchronizednotifications/MainActivity.java
@@ -19,6 +19,8 @@ package com.example.android.wearable.synchronizednotifications;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
+import android.text.Html;
+import android.widget.TextView;
import android.view.Menu;
import com.example.android.common.activities.SampleActivityBase;
@@ -41,6 +43,8 @@ public class MainActivity extends SampleActivityBase {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ TextView sampleOutput = (TextView) findViewById(R.id.sample_output);
+ sampleOutput.setText(Html.fromHtml(getString(R.string.intro_message)));
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
SynchronizedNotificationsFragment fragment = new SynchronizedNotificationsFragment();
@@ -75,4 +79,4 @@ public class MainActivity extends SampleActivityBase {
Log.i(TAG, "Ready");
}
-}
\ No newline at end of file
+}
diff --git a/samples/browseable/SynchronizedNotifications/Wearable/AndroidManifest.xml b/samples/browseable/SynchronizedNotifications/Wearable/AndroidManifest.xml
index b8d5e8538..f9b0d9c85 100644
--- a/samples/browseable/SynchronizedNotifications/Wearable/AndroidManifest.xml
+++ b/samples/browseable/SynchronizedNotifications/Wearable/AndroidManifest.xml
@@ -20,7 +20,7 @@
package="com.example.android.wearable.synchronizednotifications">
+ android:targetSdkVersion="21" />
diff --git a/samples/browseable/TextLinkify/res/values-v21/base-colors.xml b/samples/browseable/TextLinkify/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/TextLinkify/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/TextLinkify/res/values-v21/base-template-styles.xml b/samples/browseable/TextLinkify/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/TextLinkify/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/TextLinkify/res/values-v21/template-styles.xml b/samples/browseable/TextLinkify/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/TextLinkify/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/TextSwitcher/res/values-v21/base-colors.xml b/samples/browseable/TextSwitcher/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/TextSwitcher/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/TextSwitcher/res/values-v21/base-template-styles.xml b/samples/browseable/TextSwitcher/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/TextSwitcher/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/TextSwitcher/res/values-v21/template-styles.xml b/samples/browseable/TextSwitcher/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/TextSwitcher/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/Timer/Application/AndroidManifest.xml b/samples/browseable/Timer/Application/AndroidManifest.xml
index bd610eb34..2c60ab9f7 100644
--- a/samples/browseable/Timer/Application/AndroidManifest.xml
+++ b/samples/browseable/Timer/Application/AndroidManifest.xml
@@ -18,10 +18,11 @@
-
+
diff --git a/samples/browseable/Timer/Application/res/drawable-hdpi/ic_launcher.png b/samples/browseable/Timer/Application/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 000000000..589f229d1
Binary files /dev/null and b/samples/browseable/Timer/Application/res/drawable-hdpi/ic_launcher.png differ
diff --git a/samples/browseable/Timer/Application/res/drawable-mdpi/ic_launcher.png b/samples/browseable/Timer/Application/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 000000000..77dd57139
Binary files /dev/null and b/samples/browseable/Timer/Application/res/drawable-mdpi/ic_launcher.png differ
diff --git a/samples/browseable/Timer/Application/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/Timer/Application/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 000000000..fe34ebe13
Binary files /dev/null and b/samples/browseable/Timer/Application/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/samples/browseable/Timer/Application/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/Timer/Application/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 000000000..ab80bcd13
Binary files /dev/null and b/samples/browseable/Timer/Application/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/samples/browseable/Timer/Application/res/values-v21/base-colors.xml b/samples/browseable/Timer/Application/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/Timer/Application/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/Timer/Application/res/values-v21/base-template-styles.xml b/samples/browseable/Timer/Application/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/Timer/Application/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/Timer/Application/res/values-v21/template-styles.xml b/samples/browseable/Timer/Application/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/Timer/Application/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/Timer/Wearable/AndroidManifest.xml b/samples/browseable/Timer/Wearable/AndroidManifest.xml
index 9e2794744..364fb5a68 100644
--- a/samples/browseable/Timer/Wearable/AndroidManifest.xml
+++ b/samples/browseable/Timer/Wearable/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.timer" >
+ android:targetSdkVersion="21" />
diff --git a/samples/browseable/Timer/Wearable/src/com.example.android.wearable.timer/WearableListItemLayout.java b/samples/browseable/Timer/Wearable/src/com.example.android.wearable.timer/WearableListItemLayout.java
index 0bbc58720..09591bf42 100644
--- a/samples/browseable/Timer/Wearable/src/com.example.android.wearable.timer/WearableListItemLayout.java
+++ b/samples/browseable/Timer/Wearable/src/com.example.android.wearable.timer/WearableListItemLayout.java
@@ -24,12 +24,12 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-public class WearableListItemLayout extends LinearLayout implements WearableListView.Item {
+public class WearableListItemLayout extends LinearLayout
+ implements WearableListView.OnCenterProximityListener {
private final float mFadedTextAlpha;
private final int mFadedCircleColor;
private final int mChosenCircleColor;
private ImageView mCircle;
- private float mScale;
private TextView mName;
public WearableListItemLayout(Context context) {
@@ -55,35 +55,13 @@ public class WearableListItemLayout extends LinearLayout implements WearableList
}
@Override
- public float getProximityMinValue() {
- return 1f;
- }
-
- @Override
- public float getProximityMaxValue() {
- return 1.6f;
- }
-
- @Override
- public float getCurrentProximityValue() {
- return mScale;
- }
-
- @Override
- public void setScalingAnimatorValue(float scale) {
- mScale = scale;
- mCircle.setScaleX(scale);
- mCircle.setScaleY(scale);
- }
-
- @Override
- public void onScaleUpStart() {
+ public void onCenterPosition(boolean animate) {
mName.setAlpha(1f);
((GradientDrawable) mCircle.getDrawable()).setColor(mChosenCircleColor);
}
@Override
- public void onScaleDownStart() {
+ public void onNonCenterPosition(boolean animate) {
((GradientDrawable) mCircle.getDrawable()).setColor(mFadedCircleColor);
mName.setAlpha(mFadedTextAlpha);
}
diff --git a/samples/browseable/WatchFace/Application/AndroidManifest.xml b/samples/browseable/WatchFace/Application/AndroidManifest.xml
new file mode 100644
index 000000000..732e3061b
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/AndroidManifest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/drawable-hdpi/ic_launcher.png b/samples/browseable/WatchFace/Application/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 000000000..589f229d1
Binary files /dev/null and b/samples/browseable/WatchFace/Application/res/drawable-hdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Application/res/drawable-hdpi/tile.9.png b/samples/browseable/WatchFace/Application/res/drawable-hdpi/tile.9.png
new file mode 100644
index 000000000..135862883
Binary files /dev/null and b/samples/browseable/WatchFace/Application/res/drawable-hdpi/tile.9.png differ
diff --git a/samples/browseable/WatchFace/Application/res/drawable-mdpi/ic_launcher.png b/samples/browseable/WatchFace/Application/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 000000000..77dd57139
Binary files /dev/null and b/samples/browseable/WatchFace/Application/res/drawable-mdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Application/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/WatchFace/Application/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 000000000..fe34ebe13
Binary files /dev/null and b/samples/browseable/WatchFace/Application/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Application/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/WatchFace/Application/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 000000000..ab80bcd13
Binary files /dev/null and b/samples/browseable/WatchFace/Application/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Application/res/layout/activity_analog_watch_face_config.xml b/samples/browseable/WatchFace/Application/res/layout/activity_analog_watch_face_config.xml
new file mode 100644
index 000000000..ec816c22e
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/layout/activity_analog_watch_face_config.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/layout/activity_digital_watch_face_config.xml b/samples/browseable/WatchFace/Application/res/layout/activity_digital_watch_face_config.xml
new file mode 100644
index 000000000..204d523d3
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/layout/activity_digital_watch_face_config.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/layout/activity_main.xml b/samples/browseable/WatchFace/Application/res/layout/activity_main.xml
new file mode 100755
index 000000000..be1aa49d9
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/layout/activity_tilt_watch_face_config.xml b/samples/browseable/WatchFace/Application/res/layout/activity_tilt_watch_face_config.xml
new file mode 100644
index 000000000..bda2d686e
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/layout/activity_tilt_watch_face_config.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values-sw600dp/template-dimens.xml b/samples/browseable/WatchFace/Application/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 000000000..22074a2bd
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ @dimen/margin_huge
+ @dimen/margin_medium
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values-sw600dp/template-styles.xml b/samples/browseable/WatchFace/Application/res/values-sw600dp/template-styles.xml
new file mode 100644
index 000000000..03d197418
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values-v11/template-styles.xml b/samples/browseable/WatchFace/Application/res/values-v11/template-styles.xml
new file mode 100644
index 000000000..8c1ea66f2
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values-v11/template-styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values-v21/base-colors.xml b/samples/browseable/WatchFace/Application/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values-v21/base-template-styles.xml b/samples/browseable/WatchFace/Application/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values/base-strings.xml b/samples/browseable/WatchFace/Application/res/values/base-strings.xml
new file mode 100644
index 000000000..5108f618e
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values/base-strings.xml
@@ -0,0 +1,31 @@
+
+
+
+ WatchFace
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values/strings.xml b/samples/browseable/WatchFace/Application/res/values/strings.xml
new file mode 100644
index 000000000..aacb108e5
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values/strings.xml
@@ -0,0 +1,45 @@
+
+
+
+ This is the config activity for the Analog and Card Bounds watch faces
+ Digital watch face configuration
+ Tilt watch face configuration
+ Background
+ Hours
+ Minutes
+ Seconds
+
+ No wearable device is currently connected.
+ OK
+
+ Black
+ Blue
+ Gray
+ Green
+ Navy
+ Red
+ White
+
+
+ - @string/color_black
+ - @string/color_blue
+ - @string/color_gray
+ - @string/color_green
+ - @string/color_navy
+ - @string/color_red
+ - @string/color_white
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values/template-dimens.xml b/samples/browseable/WatchFace/Application/res/values/template-dimens.xml
new file mode 100644
index 000000000..39e710b5c
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ 4dp
+ 8dp
+ 16dp
+ 32dp
+ 64dp
+
+
+
+ @dimen/margin_medium
+ @dimen/margin_medium
+
+
diff --git a/samples/browseable/WatchFace/Application/res/values/template-styles.xml b/samples/browseable/WatchFace/Application/res/values/template-styles.xml
new file mode 100644
index 000000000..6e7d593dd
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/AnalogAndCardBoundsWatchFaceConfigActivity.java b/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/AnalogAndCardBoundsWatchFaceConfigActivity.java
new file mode 100644
index 000000000..5943e6b13
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/AnalogAndCardBoundsWatchFaceConfigActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.support.wearable.companion.WatchFaceCompanion;
+import android.widget.TextView;
+
+public class AnalogAndCardBoundsWatchFaceConfigActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_analog_watch_face_config);
+
+ ComponentName name =
+ getIntent().getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
+ TextView label = (TextView) findViewById(R.id.label);
+ label.setText(label.getText() + " (" + name.getClassName() + ")");
+ }
+}
diff --git a/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/DigitalWatchFaceCompanionConfigActivity.java b/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/DigitalWatchFaceCompanionConfigActivity.java
new file mode 100644
index 000000000..b04f11ee4
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/DigitalWatchFaceCompanionConfigActivity.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.wearable.companion.WatchFaceCompanion;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataItem;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.Wearable;
+
+/**
+ * The phone-side config activity for {@code DigitalWatchFaceService}. Like the watch-side config
+ * activity ({@code DigitalWatchFaceWearableConfigActivity}), allows for setting the background
+ * color. Additionally, enables setting the color for hour, minute and second digits.
+ */
+public class DigitalWatchFaceCompanionConfigActivity extends Activity
+ implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
+ ResultCallback {
+ private static final String TAG = "DigitalWatchFaceConfig";
+
+ // TODO: use the shared constants (needs covering all the samples with Gradle build model)
+ private static final String KEY_BACKGROUND_COLOR = "BACKGROUND_COLOR";
+ private static final String KEY_HOURS_COLOR = "HOURS_COLOR";
+ private static final String KEY_MINUTES_COLOR = "MINUTES_COLOR";
+ private static final String KEY_SECONDS_COLOR = "SECONDS_COLOR";
+ private static final String PATH_WITH_FEATURE = "/watch_face_config/Digital";
+
+ private GoogleApiClient mGoogleApiClient;
+ private String mPeerId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_digital_watch_face_config);
+
+ mPeerId = getIntent().getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .addApi(Wearable.API)
+ .build();
+
+ ComponentName name = getIntent().getParcelableExtra(
+ WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
+ TextView label = (TextView)findViewById(R.id.label);
+ label.setText(label.getText() + " (" + name.getClassName() + ")");
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mGoogleApiClient.connect();
+ }
+
+ @Override
+ protected void onStop() {
+ if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+ mGoogleApiClient.disconnect();
+ }
+ super.onStop();
+ }
+
+ @Override // GoogleApiClient.ConnectionCallbacks
+ public void onConnected(Bundle connectionHint) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnected: " + connectionHint);
+ }
+
+ if (mPeerId != null) {
+ Uri.Builder builder = new Uri.Builder();
+ Uri uri = builder.scheme("wear").path(PATH_WITH_FEATURE).authority(mPeerId).build();
+ Wearable.DataApi.getDataItem(mGoogleApiClient, uri).setResultCallback(this);
+ } else {
+ displayNoConnectedDeviceDialog();
+ }
+ }
+
+ @Override // ResultCallback
+ public void onResult(DataApi.DataItemResult dataItemResult) {
+ if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) {
+ DataItem configDataItem = dataItemResult.getDataItem();
+ DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
+ DataMap config = dataMapItem.getDataMap();
+ setUpAllPickers(config);
+ } else {
+ // If DataItem with the current config can't be retrieved, select the default items on
+ // each picker.
+ setUpAllPickers(null);
+ }
+ }
+
+ @Override // GoogleApiClient.ConnectionCallbacks
+ public void onConnectionSuspended(int cause) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionSuspended: " + cause);
+ }
+ }
+
+ @Override // GoogleApiClient.OnConnectionFailedListener
+ public void onConnectionFailed(ConnectionResult result) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionFailed: " + result);
+ }
+ }
+
+ private void displayNoConnectedDeviceDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ String messageText = getResources().getString(R.string.title_no_device_connected);
+ String okText = getResources().getString(R.string.ok_no_device_connected);
+ builder.setMessage(messageText)
+ .setCancelable(false)
+ .setPositiveButton(okText, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) { }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ /**
+ * Sets up selected items for all pickers according to given {@code config} and sets up their
+ * item selection listeners.
+ *
+ * @param config the {@code DigitalWatchFaceService} config {@link DataMap}. If null, the
+ * default items are selected.
+ */
+ private void setUpAllPickers(DataMap config) {
+ setUpColorPickerSelection(R.id.background, KEY_BACKGROUND_COLOR, config,
+ R.string.color_black);
+ setUpColorPickerSelection(R.id.hours, KEY_HOURS_COLOR, config, R.string.color_white);
+ setUpColorPickerSelection(R.id.minutes, KEY_MINUTES_COLOR, config, R.string.color_white);
+ setUpColorPickerSelection(R.id.seconds, KEY_SECONDS_COLOR, config, R.string.color_gray);
+
+ setUpColorPickerListener(R.id.background, KEY_BACKGROUND_COLOR);
+ setUpColorPickerListener(R.id.hours, KEY_HOURS_COLOR);
+ setUpColorPickerListener(R.id.minutes, KEY_MINUTES_COLOR);
+ setUpColorPickerListener(R.id.seconds, KEY_SECONDS_COLOR);
+ }
+
+ private void setUpColorPickerSelection(int spinnerId, final String configKey, DataMap config,
+ int defaultColorNameResId) {
+ String defaultColorName = getString(defaultColorNameResId);
+ int defaultColor = Color.parseColor(defaultColorName);
+ int color;
+ if (config != null) {
+ color = config.getInt(configKey, defaultColor);
+ } else {
+ color = defaultColor;
+ }
+ Spinner spinner = (Spinner) findViewById(spinnerId);
+ String[] colorNames = getResources().getStringArray(R.array.color_array);
+ for (int i = 0; i < colorNames.length; i++) {
+ if (Color.parseColor(colorNames[i]) == color) {
+ spinner.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ private void setUpColorPickerListener(int spinnerId, final String configKey) {
+ Spinner spinner = (Spinner) findViewById(spinnerId);
+ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> adapterView, View view, int pos, long id) {
+ final String colorName = (String) adapterView.getItemAtPosition(pos);
+ sendConfigUpdateMessage(configKey, Color.parseColor(colorName));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> adapterView) { }
+ });
+ }
+
+ private void sendConfigUpdateMessage(String configKey, int color) {
+ if (mPeerId != null) {
+ DataMap config = new DataMap();
+ config.putInt(configKey, color);
+ byte[] rawData = config.toByteArray();
+ Wearable.MessageApi.sendMessage(mGoogleApiClient, mPeerId, PATH_WITH_FEATURE, rawData);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Sent watch face config message: " + configKey + " -> "
+ + Integer.toHexString(color));
+ }
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/TiltWatchFaceConfigActivity.java b/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/TiltWatchFaceConfigActivity.java
new file mode 100644
index 000000000..303e72ea5
--- /dev/null
+++ b/samples/browseable/WatchFace/Application/src/com.example.android.wearable.watchface/TiltWatchFaceConfigActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.support.wearable.companion.WatchFaceCompanion;
+import android.widget.TextView;
+
+public class TiltWatchFaceConfigActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_tilt_watch_face_config);
+
+ ComponentName name =
+ getIntent().getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
+ TextView label = (TextView)findViewById(R.id.label);
+ label.setText(label.getText() + " (" + name.getClassName() + ")");
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/AndroidManifest.xml b/samples/browseable/WatchFace/Wearable/AndroidManifest.xml
new file mode 100644
index 000000000..ee906b763
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/AndroidManifest.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/bg.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/bg.png
new file mode 100644
index 000000000..5199af2d8
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/bg.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/ic_launcher.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..589f229d1
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_analog.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_analog.png
new file mode 100644
index 000000000..ed6960dbf
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_analog.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_analog_circular.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_analog_circular.png
new file mode 100644
index 000000000..a3affe213
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_analog_circular.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_calendar.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_calendar.png
new file mode 100644
index 000000000..928aa1f4f
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_calendar.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_calendar_circular.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_calendar_circular.png
new file mode 100644
index 000000000..cb1d27bac
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_calendar_circular.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_card_bounds.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_card_bounds.png
new file mode 100644
index 000000000..f87b6c5d3
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_card_bounds.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_card_bounds_circular.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_card_bounds_circular.png
new file mode 100644
index 000000000..387f59e7d
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_card_bounds_circular.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_digital.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_digital.png
new file mode 100644
index 000000000..4853a6454
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_digital.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_digital_circular.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_digital_circular.png
new file mode 100644
index 000000000..efeac3405
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_digital_circular.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_tilt.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_tilt.png
new file mode 100644
index 000000000..aab5f18d6
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_tilt.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_tilt_circular.png b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_tilt_circular.png
new file mode 100644
index 000000000..31d3a1f94
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-hdpi/preview_tilt_circular.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-mdpi/ic_launcher.png b/samples/browseable/WatchFace/Wearable/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..77dd57139
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-mdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..fe34ebe13
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/WatchFace/Wearable/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ab80bcd13
Binary files /dev/null and b/samples/browseable/WatchFace/Wearable/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchFace/Wearable/res/layout/activity_digital_config.xml b/samples/browseable/WatchFace/Wearable/res/layout/activity_digital_config.xml
new file mode 100644
index 000000000..a368390d0
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/res/layout/activity_digital_config.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
diff --git a/samples/browseable/WatchFace/Wearable/res/layout/color_picker_item.xml b/samples/browseable/WatchFace/Wearable/res/layout/color_picker_item.xml
new file mode 100644
index 000000000..9b07e2aa6
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/res/layout/color_picker_item.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/browseable/WatchFace/Wearable/res/values/color.xml b/samples/browseable/WatchFace/Wearable/res/values/color.xml
new file mode 100644
index 000000000..0da08ed8b
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/res/values/color.xml
@@ -0,0 +1,23 @@
+
+
+
+ #aaaaa0
+ #aaaaa0
+ #ffffff
+ #959595
+ #000000
+ #424242
+
diff --git a/samples/browseable/WatchFace/Wearable/res/values/dimens.xml b/samples/browseable/WatchFace/Wearable/res/values/dimens.xml
new file mode 100644
index 000000000..8f04e56de
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/res/values/dimens.xml
@@ -0,0 +1,26 @@
+
+
+
+ 40dp
+ 45dp
+ 25dp
+ 30dp
+ 15dp
+ 25dp
+ 90dp
+ 32dp
+ 12dp
+
diff --git a/samples/browseable/WatchFace/Wearable/res/values/strings.xml b/samples/browseable/WatchFace/Wearable/res/values/strings.xml
new file mode 100644
index 000000000..e54591fd3
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/res/values/strings.xml
@@ -0,0 +1,51 @@
+
+
+
+ WatchFace
+ Sample Tilt
+ Sample Analog
+ Sample Sweep
+ Sample Card Bounds
+ Sample Digital
+ Background color
+ Digital watch face configuration
+ AM
+ PM
+ Sample Calendar
+
+ - <br><br><br>You have <b>%1$d</b> meeting in the next 24 hours.
+ - <br><br><br>You have <b>%1$d</b> meetings in the next 24 hours.
+
+
+
+ Black
+ Blue
+ Gray
+ Green
+ Navy
+ Red
+ White
+
+
+ - @string/color_black
+ - @string/color_blue
+ - @string/color_gray
+ - @string/color_green
+ - @string/color_navy
+ - @string/color_red
+ - @string/color_white
+
+
diff --git a/samples/browseable/WatchFace/Wearable/res/xml/watch_face.xml b/samples/browseable/WatchFace/Wearable/res/xml/watch_face.xml
new file mode 100644
index 000000000..aa2e34382
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/res/xml/watch_face.xml
@@ -0,0 +1,16 @@
+
+
+
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java
new file mode 100644
index 000000000..f0fb4f582
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample analog watch face with a ticking second hand. In ambient mode, the second hand isn't
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ * {@link SweepWatchFaceService} is similar but has a sweep second hand.
+ */
+public class AnalogWatchFaceService extends CanvasWatchFaceService {
+ private static final String TAG = "AnalogWatchFaceService";
+
+ /**
+ * Update rate in milliseconds for interactive mode. We update once a second to advance the
+ * second hand.
+ */
+ private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+ static final int MSG_UPDATE_TIME = 0;
+
+ Paint mHourPaint;
+ Paint mMinutePaint;
+ Paint mSecondPaint;
+ Paint mTickPaint;
+ boolean mMute;
+ Time mTime;
+
+ /** Handler to update the time once a second in interactive mode. */
+ final Handler mUpdateTimeHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_TIME:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updating time");
+ }
+ invalidate();
+ if (shouldTimerBeRunning()) {
+ long timeMs = System.currentTimeMillis();
+ long delayMs = INTERACTIVE_UPDATE_RATE_MS
+ - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+ }
+ break;
+ }
+ }
+ };
+
+ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+ boolean mRegisteredTimeZoneReceiver = false;
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ boolean mLowBitAmbient;
+
+ Bitmap mBackgroundBitmap;
+ Bitmap mBackgroundScaledBitmap;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(holder);
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+
+ Resources resources = AnalogWatchFaceService.this.getResources();
+ Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+ mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
+
+ mHourPaint = new Paint();
+ mHourPaint.setARGB(255, 200, 200, 200);
+ mHourPaint.setStrokeWidth(5.f);
+ mHourPaint.setAntiAlias(true);
+ mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mMinutePaint = new Paint();
+ mMinutePaint.setARGB(255, 200, 200, 200);
+ mMinutePaint.setStrokeWidth(3.f);
+ mMinutePaint.setAntiAlias(true);
+ mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mSecondPaint = new Paint();
+ mSecondPaint.setARGB(255, 255, 0, 0);
+ mSecondPaint.setStrokeWidth(2.f);
+ mSecondPaint.setAntiAlias(true);
+ mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mTickPaint = new Paint();
+ mTickPaint.setARGB(100, 255, 255, 255);
+ mTickPaint.setStrokeWidth(2.f);
+ mTickPaint.setAntiAlias(true);
+
+ mTime = new Time();
+ }
+
+ @Override
+ public void onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onPropertiesChanged(Bundle properties) {
+ super.onPropertiesChanged(properties);
+ mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
+ }
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ super.onAmbientModeChanged(inAmbientMode);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+ }
+ if (mLowBitAmbient) {
+ boolean antiAlias = !inAmbientMode;
+ mHourPaint.setAntiAlias(antiAlias);
+ mMinutePaint.setAntiAlias(antiAlias);
+ mSecondPaint.setAntiAlias(antiAlias);
+ mTickPaint.setAntiAlias(antiAlias);
+ }
+ invalidate();
+
+ // Whether the timer should be running depends on whether we're in ambient mode (as well
+ // as whether we're visible), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+ @Override
+ public void onInterruptionFilterChanged(int interruptionFilter) {
+ super.onInterruptionFilterChanged(interruptionFilter);
+ boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
+ if (mMute != inMuteMode) {
+ mMute = inMuteMode;
+ mHourPaint.setAlpha(inMuteMode ? 100 : 255);
+ mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
+ mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ mTime.setToNow();
+
+ int width = bounds.width();
+ int height = bounds.height();
+
+ // Draw the background, scaled to fit.
+ if (mBackgroundScaledBitmap == null
+ || mBackgroundScaledBitmap.getWidth() != width
+ || mBackgroundScaledBitmap.getHeight() != height) {
+ mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
+ width, height, true /* filter */);
+ }
+ canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
+
+ // Find the center. Ignore the window insets so that, on round watches with a
+ // "chin", the watch face is centered on the entire screen, not just the usable
+ // portion.
+ float centerX = width / 2f;
+ float centerY = height / 2f;
+
+ // Draw the ticks.
+ float innerTickRadius = centerX - 10;
+ float outerTickRadius = centerX;
+ for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
+ float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
+ float innerX = (float) Math.sin(tickRot) * innerTickRadius;
+ float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
+ float outerX = (float) Math.sin(tickRot) * outerTickRadius;
+ float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
+ canvas.drawLine(centerX + innerX, centerY + innerY,
+ centerX + outerX, centerY + outerY, mTickPaint);
+ }
+
+ float secRot = mTime.second / 30f * (float) Math.PI;
+ int minutes = mTime.minute;
+ float minRot = minutes / 30f * (float) Math.PI;
+ float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+
+ float secLength = centerX - 20;
+ float minLength = centerX - 40;
+ float hrLength = centerX - 80;
+
+ if (!isInAmbientMode()) {
+ float secX = (float) Math.sin(secRot) * secLength;
+ float secY = (float) -Math.cos(secRot) * secLength;
+ canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
+ }
+
+ float minX = (float) Math.sin(minRot) * minLength;
+ float minY = (float) -Math.cos(minRot) * minLength;
+ canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);
+
+ float hrX = (float) Math.sin(hrRot) * hrLength;
+ float hrY = (float) -Math.cos(hrRot) * hrLength;
+ canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onVisibilityChanged: " + visible);
+ }
+
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we weren't visible.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+ } else {
+ unregisterReceiver();
+ }
+
+ // Whether the timer should be running depends on whether we're visible (as well as
+ // whether we're in ambient mode), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+
+ /**
+ * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
+ * or stops it if it shouldn't be running but currently is.
+ */
+ private void updateTimer() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "updateTimer");
+ }
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+ }
+ }
+
+ /**
+ * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
+ * only run when we're visible and in interactive mode.
+ */
+ private boolean shouldTimerBeRunning() {
+ return isVisible() && !isInAmbientMode();
+ }
+
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/CalendarWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/CalendarWatchFaceService.java
new file mode 100644
index 000000000..a8ab95568
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/CalendarWatchFaceService.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.support.wearable.provider.WearableCalendarContract;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.DynamicLayout;
+import android.text.Editable;
+import android.text.Html;
+import android.text.Layout;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+/**
+ * Proof of concept sample watch face that demonstrates how a watch face can load calendar data.
+ */
+public class CalendarWatchFaceService extends CanvasWatchFaceService {
+ private static final String TAG = "CalendarWatchFace";
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+
+ static final int BACKGROUND_COLOR = Color.BLACK;
+ static final int FOREGROUND_COLOR = Color.WHITE;
+ static final int TEXT_SIZE = 25;
+ static final int MSG_LOAD_MEETINGS = 0;
+
+ /** Editable string containing the text to draw with the number of meetings in bold. */
+ final Editable mEditable = new SpannableStringBuilder();
+
+ /** Width specified when {@link #mLayout} was created. */
+ int mLayoutWidth;
+
+ /** Layout to wrap {@link #mEditable} onto multiple lines. */
+ DynamicLayout mLayout;
+
+ /** Paint used to draw text. */
+ final TextPaint mTextPaint = new TextPaint();
+
+ int mNumMeetings;
+
+ private AsyncTask mLoadMeetingsTask;
+
+ /** Handler to load the meetings once a minute in interactive mode. */
+ final Handler mLoadMeetingsHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_LOAD_MEETINGS:
+ cancelLoadMeetingTask();
+ mLoadMeetingsTask = new LoadMeetingsTask();
+ mLoadMeetingsTask.execute();
+ break;
+ }
+ }
+ };
+
+ private boolean mIsReceiverRegistered;
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_PROVIDER_CHANGED.equals(intent.getAction())
+ && WearableCalendarContract.CONTENT_URI.equals(intent.getData())) {
+ cancelLoadMeetingTask();
+ mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(holder);
+ setWatchFaceStyle(new WatchFaceStyle.Builder(CalendarWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+
+ mTextPaint.setColor(FOREGROUND_COLOR);
+ mTextPaint.setTextSize(TEXT_SIZE);
+
+ mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
+ }
+
+ @Override
+ public void onDestroy() {
+ mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
+ cancelLoadMeetingTask();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ // Create or update mLayout if necessary.
+ if (mLayout == null || mLayoutWidth != bounds.width()) {
+ mLayoutWidth = bounds.width();
+ mLayout = new DynamicLayout(mEditable, mTextPaint, mLayoutWidth,
+ Layout.Alignment.ALIGN_NORMAL, 1 /* spacingMult */, 0 /* spacingAdd */,
+ false /* includePad */);
+ }
+
+ // Update the contents of mEditable.
+ mEditable.clear();
+ mEditable.append(Html.fromHtml(getResources().getQuantityString(
+ R.plurals.calendar_meetings, mNumMeetings, mNumMeetings)));
+
+ // Draw the text on a solid background.
+ canvas.drawColor(BACKGROUND_COLOR);
+ mLayout.draw(canvas);
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+ if (visible) {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PROVIDER_CHANGED);
+ filter.addDataScheme("content");
+ filter.addDataAuthority(WearableCalendarContract.AUTHORITY, null);
+ registerReceiver(mBroadcastReceiver, filter);
+ mIsReceiverRegistered = true;
+
+ mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
+ } else {
+ if (mIsReceiverRegistered) {
+ unregisterReceiver(mBroadcastReceiver);
+ mIsReceiverRegistered = false;
+ }
+ mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
+ }
+ }
+
+ private void onMeetingsLoaded(Integer result) {
+ if (result != null) {
+ mNumMeetings = result;
+ invalidate();
+ }
+ }
+
+ private void cancelLoadMeetingTask() {
+ if (mLoadMeetingsTask != null) {
+ mLoadMeetingsTask.cancel(true);
+ }
+ }
+
+ /**
+ * Asynchronous task to load the meetings from the content provider and report the number of
+ * meetings back via {@link #onMeetingsLoaded}.
+ */
+ private class LoadMeetingsTask extends AsyncTask {
+ private PowerManager.WakeLock mWakeLock;
+
+ @Override
+ protected Integer doInBackground(Void... voids) {
+ PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, "CalendarWatchFaceWakeLock");
+ mWakeLock.acquire();
+
+ long begin = System.currentTimeMillis();
+ Uri.Builder builder =
+ WearableCalendarContract.Instances.CONTENT_URI.buildUpon();
+ ContentUris.appendId(builder, begin);
+ ContentUris.appendId(builder, begin + DateUtils.DAY_IN_MILLIS);
+ final Cursor cursor = getContentResolver().query(builder.build(),
+ null, null, null, null);
+ int numMeetings = cursor.getCount();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Num meetings: " + numMeetings);
+ }
+ return numMeetings;
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ releaseWakeLock();
+ onMeetingsLoaded(result);
+ }
+
+ @Override
+ protected void onCancelled() {
+ releaseWakeLock();
+ }
+
+ private void releaseWakeLock() {
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/CardBoundsWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/CardBoundsWatchFaceService.java
new file mode 100644
index 000000000..359d7af07
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/CardBoundsWatchFaceService.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+/**
+ * Proof of concept sample watch face that demonstrates how a watch face can detect where the peek
+ * card is. This watch face draws a border around the area where the peeking card is.
+ */
+public class CardBoundsWatchFaceService extends CanvasWatchFaceService {
+
+ private static final String TAG = "CardBoundsWatchFace";
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+
+ static final int BORDER_WIDTH_PX = 5;
+
+ final Rect mCardBounds = new Rect();
+ final Paint mPaint = new Paint();
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(holder);
+ setWatchFaceStyle(new WatchFaceStyle.Builder(CardBoundsWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(true)
+ .setPeekOpacityMode(WatchFaceStyle.PEEK_OPACITY_MODE_TRANSLUCENT)
+ .build());
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+ }
+ super.onAmbientModeChanged(inAmbientMode);
+ invalidate();
+ }
+
+ @Override
+ public void onPeekCardPositionUpdate(Rect bounds) {
+ super.onPeekCardPositionUpdate(bounds);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onPeekCardPositionUpdate: " + bounds);
+ }
+ super.onPeekCardPositionUpdate(bounds);
+ if (!bounds.equals(mCardBounds)) {
+ mCardBounds.set(bounds);
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ // Clear screen.
+ canvas.drawColor(isInAmbientMode() ? Color.BLACK : Color.BLUE);
+
+ // Draw border around card in interactive mode.
+ if (!isInAmbientMode()) {
+ mPaint.setColor(Color.MAGENTA);
+ canvas.drawRect(mCardBounds.left - BORDER_WIDTH_PX,
+ mCardBounds.top - BORDER_WIDTH_PX,
+ mCardBounds.right + BORDER_WIDTH_PX,
+ mCardBounds.bottom + BORDER_WIDTH_PX, mPaint);
+ }
+
+ // Fill area under card.
+ mPaint.setColor(isInAmbientMode() ? Color.RED : Color.GREEN);
+ canvas.drawRect(mCardBounds, mPaint);
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java
new file mode 100644
index 000000000..725c51aa8
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceConfigListenerService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.MessageEvent;
+import com.google.android.gms.wearable.Wearable;
+import com.google.android.gms.wearable.WearableListenerService;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link WearableListenerService} listening for {@link DigitalWatchFaceService} config messages
+ * and updating the config {@link com.google.android.gms.wearable.DataItem} accordingly.
+ */
+public class DigitalWatchFaceConfigListenerService extends WearableListenerService
+ implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+ private static final String TAG = "DigitalListenerService";
+
+ private GoogleApiClient mGoogleApiClient;
+
+ @Override // WearableListenerService
+ public void onMessageReceived(MessageEvent messageEvent) {
+ if (!messageEvent.getPath().equals(DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
+ return;
+ }
+ byte[] rawData = messageEvent.getData();
+ // It's allowed that the message carries only some of the keys used in the config DataItem
+ // and skips the ones that we don't want to change.
+ DataMap configKeysToOverwrite = DataMap.fromByteArray(rawData);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Received watch face config message: " + configKeysToOverwrite);
+ }
+
+ if (mGoogleApiClient == null) {
+ mGoogleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this).addApi(Wearable.API).build();
+ }
+ if (!mGoogleApiClient.isConnected()) {
+ ConnectionResult connectionResult =
+ mGoogleApiClient.blockingConnect(30, TimeUnit.SECONDS);
+
+ if (!connectionResult.isSuccess()) {
+ Log.e(TAG, "Failed to connect to GoogleApiClient.");
+ return;
+ }
+ }
+
+ DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite);
+ }
+
+ @Override // GoogleApiClient.ConnectionCallbacks
+ public void onConnected(Bundle connectionHint) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnected: " + connectionHint);
+ }
+ }
+
+ @Override // GoogleApiClient.ConnectionCallbacks
+ public void onConnectionSuspended(int cause) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionSuspended: " + cause);
+ }
+ }
+
+ @Override // GoogleApiClient.OnConnectionFailedListener
+ public void onConnectionFailed(ConnectionResult result) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionFailed: " + result);
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java
new file mode 100644
index 000000000..b8b1314b8
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.WindowInsets;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataEvent;
+import com.google.android.gms.wearable.DataEventBuffer;
+import com.google.android.gms.wearable.DataItem;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.Wearable;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample digital watch face with blinking colons and seconds. In ambient mode, the seconds are
+ * replaced with an AM/PM indicator and the colons don't blink. On devices with low-bit ambient
+ * mode, the text is drawn without anti-aliasing in ambient mode. On devices which require burn-in
+ * protection, the hours are drawn in normal rather than bold. The time is drawn with less contrast
+ * and without seconds in mute mode.
+ */
+public class DigitalWatchFaceService extends CanvasWatchFaceService {
+ private static final String TAG = "DigitalWatchFaceService";
+
+ private static final Typeface BOLD_TYPEFACE =
+ Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
+ private static final Typeface NORMAL_TYPEFACE =
+ Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
+
+ /**
+ * Update rate in milliseconds for normal (not ambient and not mute) mode. We update twice
+ * a second to blink the colons.
+ */
+ private static final long NORMAL_UPDATE_RATE_MS = 500;
+
+ /**
+ * Update rate in milliseconds for mute mode. We update every minute, like in ambient mode.
+ */
+ private static final long MUTE_UPDATE_RATE_MS = TimeUnit.MINUTES.toMillis(1);
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener,
+ GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+ static final String COLON_STRING = ":";
+
+ /** Alpha value for drawing time when in mute mode. */
+ static final int MUTE_ALPHA = 100;
+
+ /** Alpha value for drawing time when not in mute mode. */
+ static final int NORMAL_ALPHA = 255;
+
+ static final int MSG_UPDATE_TIME = 0;
+
+ /** How often {@link #mUpdateTimeHandler} ticks in milliseconds. */
+ long mInteractiveUpdateRateMs = NORMAL_UPDATE_RATE_MS;
+
+ /** Handler to update the time periodically in interactive mode. */
+ final Handler mUpdateTimeHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_TIME:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updating time");
+ }
+ invalidate();
+ if (shouldTimerBeRunning()) {
+ long timeMs = System.currentTimeMillis();
+ long delayMs =
+ mInteractiveUpdateRateMs - (timeMs % mInteractiveUpdateRateMs);
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+ }
+ break;
+ }
+ }
+ };
+
+ GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(DigitalWatchFaceService.this)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .addApi(Wearable.API)
+ .build();
+
+ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+ boolean mRegisteredTimeZoneReceiver = false;
+
+ Paint mBackgroundPaint;
+ Paint mHourPaint;
+ Paint mMinutePaint;
+ Paint mSecondPaint;
+ Paint mAmPmPaint;
+ Paint mColonPaint;
+ float mColonWidth;
+ boolean mMute;
+ Time mTime;
+ boolean mShouldDrawColons;
+ float mXOffset;
+ float mYOffset;
+ String mAmString;
+ String mPmString;
+ int mInteractiveBackgroundColor =
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND;
+ int mInteractiveHourDigitsColor =
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS;
+ int mInteractiveMinuteDigitsColor =
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS;
+ int mInteractiveSecondDigitsColor =
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS;
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ boolean mLowBitAmbient;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(holder);
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(DigitalWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+ Resources resources = DigitalWatchFaceService.this.getResources();
+ mYOffset = resources.getDimension(R.dimen.digital_y_offset);
+ mAmString = resources.getString(R.string.digital_am);
+ mPmString = resources.getString(R.string.digital_pm);
+
+ mBackgroundPaint = new Paint();
+ mBackgroundPaint.setColor(mInteractiveBackgroundColor);
+ mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE);
+ mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor);
+ mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor);
+ mAmPmPaint = createTextPaint(resources.getColor(R.color.digital_am_pm));
+ mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons));
+
+ mTime = new Time();
+ }
+
+ @Override
+ public void onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ super.onDestroy();
+ }
+
+ private Paint createTextPaint(int defaultInteractiveColor) {
+ return createTextPaint(defaultInteractiveColor, NORMAL_TYPEFACE);
+ }
+
+ private Paint createTextPaint(int defaultInteractiveColor, Typeface typeface) {
+ Paint paint = new Paint();
+ paint.setColor(defaultInteractiveColor);
+ paint.setTypeface(typeface);
+ paint.setAntiAlias(true);
+ return paint;
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onVisibilityChanged: " + visible);
+ }
+ super.onVisibilityChanged(visible);
+
+ if (visible) {
+ mGoogleApiClient.connect();
+
+ registerReceiver();
+
+ // Update time zone in case it changed while we weren't visible.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+ } else {
+ unregisterReceiver();
+
+ if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+ Wearable.DataApi.removeListener(mGoogleApiClient, this);
+ mGoogleApiClient.disconnect();
+ }
+ }
+
+ // Whether the timer should be running depends on whether we're visible (as well as
+ // whether we're in ambient mode), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ DigitalWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ DigitalWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+
+ @Override
+ public void onApplyWindowInsets(WindowInsets insets) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onApplyWindowInsets: " + (insets.isRound() ? "round" : "square"));
+ }
+ super.onApplyWindowInsets(insets);
+
+ // Load resources that have alternate values for round watches.
+ Resources resources = DigitalWatchFaceService.this.getResources();
+ boolean isRound = insets.isRound();
+ mXOffset = resources.getDimension(isRound
+ ? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
+ float textSize = resources.getDimension(isRound
+ ? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
+ float amPmSize = resources.getDimension(isRound
+ ? R.dimen.digital_am_pm_size_round : R.dimen.digital_am_pm_size);
+
+ mHourPaint.setTextSize(textSize);
+ mMinutePaint.setTextSize(textSize);
+ mSecondPaint.setTextSize(textSize);
+ mAmPmPaint.setTextSize(amPmSize);
+ mColonPaint.setTextSize(textSize);
+
+ mColonWidth = mColonPaint.measureText(COLON_STRING);
+ }
+
+ @Override
+ public void onPropertiesChanged(Bundle properties) {
+ super.onPropertiesChanged(properties);
+
+ boolean burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
+ mHourPaint.setTypeface(burnInProtection ? NORMAL_TYPEFACE : BOLD_TYPEFACE);
+
+ mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onPropertiesChanged: burn-in protection = " + burnInProtection
+ + ", low-bit ambient = " + mLowBitAmbient);
+ }
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ super.onAmbientModeChanged(inAmbientMode);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+ }
+ adjustPaintColorToCurrentMode(mBackgroundPaint, mInteractiveBackgroundColor,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND);
+ adjustPaintColorToCurrentMode(mHourPaint, mInteractiveHourDigitsColor,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
+ adjustPaintColorToCurrentMode(mMinutePaint, mInteractiveMinuteDigitsColor,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
+ // Actually, the seconds are not rendered in the ambient mode, so we could pass just any
+ // value as ambientColor here.
+ adjustPaintColorToCurrentMode(mSecondPaint, mInteractiveSecondDigitsColor,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
+
+ if (mLowBitAmbient) {
+ boolean antiAlias = !inAmbientMode;
+ mHourPaint.setAntiAlias(antiAlias);
+ mMinutePaint.setAntiAlias(antiAlias);
+ mSecondPaint.setAntiAlias(antiAlias);
+ mAmPmPaint.setAntiAlias(antiAlias);
+ mColonPaint.setAntiAlias(antiAlias);
+ }
+ invalidate();
+
+ // Whether the timer should be running depends on whether we're in ambient mode (as well
+ // as whether we're visible), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+ private void adjustPaintColorToCurrentMode(Paint paint, int interactiveColor,
+ int ambientColor) {
+ paint.setColor(isInAmbientMode() ? ambientColor : interactiveColor);
+ }
+
+ @Override
+ public void onInterruptionFilterChanged(int interruptionFilter) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onInterruptionFilterChanged: " + interruptionFilter);
+ }
+ super.onInterruptionFilterChanged(interruptionFilter);
+
+ boolean inMuteMode = interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE;
+ // We only need to update once a minute in mute mode.
+ setInteractiveUpdateRateMs(inMuteMode ? MUTE_UPDATE_RATE_MS : NORMAL_UPDATE_RATE_MS);
+
+ if (mMute != inMuteMode) {
+ mMute = inMuteMode;
+ int alpha = inMuteMode ? MUTE_ALPHA : NORMAL_ALPHA;
+ mHourPaint.setAlpha(alpha);
+ mMinutePaint.setAlpha(alpha);
+ mColonPaint.setAlpha(alpha);
+ mAmPmPaint.setAlpha(alpha);
+ invalidate();
+ }
+ }
+
+ public void setInteractiveUpdateRateMs(long updateRateMs) {
+ if (updateRateMs == mInteractiveUpdateRateMs) {
+ return;
+ }
+ mInteractiveUpdateRateMs = updateRateMs;
+
+ // Stop and restart the timer so the new update rate takes effect immediately.
+ if (shouldTimerBeRunning()) {
+ updateTimer();
+ }
+ }
+
+ private void updatePaintIfInteractive(Paint paint, int interactiveColor) {
+ if (!isInAmbientMode() && paint != null) {
+ paint.setColor(interactiveColor);
+ }
+ }
+
+ private void setInteractiveBackgroundColor(int color) {
+ mInteractiveBackgroundColor = color;
+ updatePaintIfInteractive(mBackgroundPaint, color);
+ }
+
+ private void setInteractiveHourDigitsColor(int color) {
+ mInteractiveHourDigitsColor = color;
+ updatePaintIfInteractive(mHourPaint, color);
+ }
+
+ private void setInteractiveMinuteDigitsColor(int color) {
+ mInteractiveMinuteDigitsColor = color;
+ updatePaintIfInteractive(mMinutePaint, color);
+ }
+
+ private void setInteractiveSecondDigitsColor(int color) {
+ mInteractiveSecondDigitsColor = color;
+ updatePaintIfInteractive(mSecondPaint, color);
+ }
+
+ private String formatTwoDigitNumber(int hour) {
+ return String.format("%02d", hour);
+ }
+
+ private int convertTo12Hour(int hour) {
+ int result = hour % 12;
+ return (result == 0) ? 12 : result;
+ }
+
+ private String getAmPmString(int hour) {
+ return (hour < 12) ? mAmString : mPmString;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ mTime.setToNow();
+
+ // Show colons for the first half of each second so the colons blink on when the time
+ // updates.
+ mShouldDrawColons = (System.currentTimeMillis() % 1000) < 500;
+
+ // Draw the background.
+ canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
+
+ // Draw the hours.
+ float x = mXOffset;
+ String hourString = String.valueOf(convertTo12Hour(mTime.hour));
+ canvas.drawText(hourString, x, mYOffset, mHourPaint);
+ x += mHourPaint.measureText(hourString);
+
+ // In ambient and mute modes, always draw the first colon. Otherwise, draw the
+ // first colon for the first half of each second.
+ if (isInAmbientMode() || mMute || mShouldDrawColons) {
+ canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+ }
+ x += mColonWidth;
+
+ // Draw the minutes.
+ String minuteString = formatTwoDigitNumber(mTime.minute);
+ canvas.drawText(minuteString, x, mYOffset, mMinutePaint);
+ x += mMinutePaint.measureText(minuteString);
+
+ // In ambient and mute modes, draw AM/PM. Otherwise, draw a second blinking
+ // colon followed by the seconds.
+ if (isInAmbientMode() || mMute) {
+ x += mColonWidth;
+ canvas.drawText(getAmPmString(mTime.hour), x, mYOffset, mAmPmPaint);
+ } else {
+ if (mShouldDrawColons) {
+ canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+ }
+ x += mColonWidth;
+ canvas.drawText(formatTwoDigitNumber(mTime.second), x, mYOffset,
+ mSecondPaint);
+ }
+ }
+
+ /**
+ * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
+ * or stops it if it shouldn't be running but currently is.
+ */
+ private void updateTimer() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "updateTimer");
+ }
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+ }
+ }
+
+ /**
+ * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
+ * only run when we're visible and in interactive mode.
+ */
+ private boolean shouldTimerBeRunning() {
+ return isVisible() && !isInAmbientMode();
+ }
+
+ private void updateConfigDataItemAndUiOnStartup() {
+ DigitalWatchFaceUtil.fetchConfigDataMap(mGoogleApiClient,
+ new DigitalWatchFaceUtil.FetchConfigDataMapCallback() {
+ @Override
+ public void onConfigDataMapFetched(DataMap startupConfig) {
+ // If the DataItem hasn't been created yet or some keys are missing,
+ // use the default values.
+ setDefaultValuesForMissingConfigKeys(startupConfig);
+ DigitalWatchFaceUtil.putConfigDataItem(mGoogleApiClient, startupConfig);
+
+ updateUiForConfigDataMap(startupConfig);
+ }
+ }
+ );
+ }
+
+ private void setDefaultValuesForMissingConfigKeys(DataMap config) {
+ addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND);
+ addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_HOURS_COLOR,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
+ addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_MINUTES_COLOR,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
+ addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_SECONDS_COLOR,
+ DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
+ }
+
+ private void addIntKeyIfMissing(DataMap config, String key, int color) {
+ if (!config.containsKey(key)) {
+ config.putInt(key, color);
+ }
+ }
+
+ @Override // DataApi.DataListener
+ public void onDataChanged(DataEventBuffer dataEvents) {
+ try {
+ for (DataEvent dataEvent : dataEvents) {
+ if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
+ continue;
+ }
+
+ DataItem dataItem = dataEvent.getDataItem();
+ if (!dataItem.getUri().getPath().equals(
+ DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
+ continue;
+ }
+
+ DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem);
+ DataMap config = dataMapItem.getDataMap();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Config DataItem updated:" + config);
+ }
+ updateUiForConfigDataMap(config);
+ }
+ } finally {
+ dataEvents.close();
+ }
+ }
+
+ private void updateUiForConfigDataMap(final DataMap config) {
+ boolean uiUpdated = false;
+ for (String configKey : config.keySet()) {
+ if (!config.containsKey(configKey)) {
+ continue;
+ }
+ int color = config.getInt(configKey);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Found watch face config key: " + configKey + " -> "
+ + Integer.toHexString(color));
+ }
+ if (updateUiForKey(configKey, color)) {
+ uiUpdated = true;
+ }
+ }
+ if (uiUpdated) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Updates the color of a UI item according to the given {@code configKey}. Does nothing if
+ * {@code configKey} isn't recognized.
+ *
+ * @return whether UI has been updated
+ */
+ private boolean updateUiForKey(String configKey, int color) {
+ if (configKey.equals(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR)) {
+ setInteractiveBackgroundColor(color);
+ } else if (configKey.equals(DigitalWatchFaceUtil.KEY_HOURS_COLOR)) {
+ setInteractiveHourDigitsColor(color);
+ } else if (configKey.equals(DigitalWatchFaceUtil.KEY_MINUTES_COLOR)) {
+ setInteractiveMinuteDigitsColor(color);
+ } else if (configKey.equals(DigitalWatchFaceUtil.KEY_SECONDS_COLOR)) {
+ setInteractiveSecondDigitsColor(color);
+ } else {
+ Log.w(TAG, "Ignoring unknown config key: " + configKey);
+ return false;
+ }
+ return true;
+ }
+
+ @Override // GoogleApiClient.ConnectionCallbacks
+ public void onConnected(Bundle connectionHint) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnected: " + connectionHint);
+ }
+ Wearable.DataApi.addListener(mGoogleApiClient, Engine.this);
+ updateConfigDataItemAndUiOnStartup();
+ }
+
+ @Override // GoogleApiClient.ConnectionCallbacks
+ public void onConnectionSuspended(int cause) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionSuspended: " + cause);
+ }
+ }
+
+ @Override // GoogleApiClient.OnConnectionFailedListener
+ public void onConnectionFailed(ConnectionResult result) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionFailed: " + result);
+ }
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceUtil.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceUtil.java
new file mode 100644
index 000000000..1c4af700b
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceUtil.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.graphics.Color;
+import android.net.Uri;
+import android.util.Log;
+
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataItem;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.NodeApi;
+import com.google.android.gms.wearable.PutDataMapRequest;
+import com.google.android.gms.wearable.Wearable;
+
+public final class DigitalWatchFaceUtil {
+ private static final String TAG = "DigitalWatchFaceUtil";
+
+ /**
+ * The {@link DataMap} key for {@link DigitalWatchFaceService} background color name.
+ * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+ */
+ public static final String KEY_BACKGROUND_COLOR = "BACKGROUND_COLOR";
+
+ /**
+ * The {@link DataMap} key for {@link DigitalWatchFaceService} hour digits color name.
+ * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+ */
+ public static final String KEY_HOURS_COLOR = "HOURS_COLOR";
+
+ /**
+ * The {@link DataMap} key for {@link DigitalWatchFaceService} minute digits color name.
+ * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+ */
+ public static final String KEY_MINUTES_COLOR = "MINUTES_COLOR";
+
+ /**
+ * The {@link DataMap} key for {@link DigitalWatchFaceService} second digits color name.
+ * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+ */
+ public static final String KEY_SECONDS_COLOR = "SECONDS_COLOR";
+
+ /**
+ * The path for the {@link DataItem} containing {@link DigitalWatchFaceService} configuration.
+ */
+ public static final String PATH_WITH_FEATURE = "/watch_face_config/Digital";
+
+ /**
+ * Name of the default interactive mode background color and the ambient mode background color.
+ */
+ public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_BACKGROUND = "Black";
+ public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND =
+ parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_BACKGROUND);
+
+ /**
+ * Name of the default interactive mode hour digits color and the ambient mode hour digits
+ * color.
+ */
+ public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_HOUR_DIGITS = "White";
+ public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS =
+ parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
+
+ /**
+ * Name of the default interactive mode minute digits color and the ambient mode minute digits
+ * color.
+ */
+ public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_MINUTE_DIGITS = "White";
+ public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS =
+ parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
+
+ /**
+ * Name of the default interactive mode second digits color and the ambient mode second digits
+ * color.
+ */
+ public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_SECOND_DIGITS = "Gray";
+ public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS =
+ parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
+
+ /**
+ * Callback interface to perform an action with the current config {@link DataMap} for
+ * {@link DigitalWatchFaceService}.
+ */
+ public interface FetchConfigDataMapCallback {
+ /**
+ * Callback invoked with the current config {@link DataMap} for
+ * {@link DigitalWatchFaceService}.
+ */
+ void onConfigDataMapFetched(DataMap config);
+ }
+
+ private static int parseColor(String colorName) {
+ return Color.parseColor(colorName.toLowerCase());
+ }
+
+ /**
+ * Asynchronously fetches the current config {@link DataMap} for {@link DigitalWatchFaceService}
+ * and passes it to the given callback.
+ *
+ * If the current config {@link DataItem} doesn't exist, it isn't created and the callback
+ * receives an empty DataMap.
+ */
+ public static void fetchConfigDataMap(final GoogleApiClient client,
+ final FetchConfigDataMapCallback callback) {
+ Wearable.NodeApi.getLocalNode(client).setResultCallback(
+ new ResultCallback() {
+ @Override
+ public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
+ String localNode = getLocalNodeResult.getNode().getId();
+ Uri uri = new Uri.Builder()
+ .scheme("wear")
+ .path(DigitalWatchFaceUtil.PATH_WITH_FEATURE)
+ .authority(localNode)
+ .build();
+ Wearable.DataApi.getDataItem(client, uri)
+ .setResultCallback(new DataItemResultCallback(callback));
+ }
+ }
+ );
+ }
+
+ /**
+ * Overwrites (or sets, if not present) the keys in the current config {@link DataItem} with
+ * the ones appearing in the given {@link DataMap}. If the config DataItem doesn't exist,
+ * it's created.
+ *
+ * It is allowed that only some of the keys used in the config DataItem appear in
+ * {@code configKeysToOverwrite}. The rest of the keys remains unmodified in this case.
+ */
+ public static void overwriteKeysInConfigDataMap(final GoogleApiClient googleApiClient,
+ final DataMap configKeysToOverwrite) {
+
+ DigitalWatchFaceUtil.fetchConfigDataMap(googleApiClient,
+ new FetchConfigDataMapCallback() {
+ @Override
+ public void onConfigDataMapFetched(DataMap currentConfig) {
+ DataMap overwrittenConfig = new DataMap();
+ overwrittenConfig.putAll(currentConfig);
+ overwrittenConfig.putAll(configKeysToOverwrite);
+ DigitalWatchFaceUtil.putConfigDataItem(googleApiClient, overwrittenConfig);
+ }
+ }
+ );
+ }
+
+ /**
+ * Overwrites the current config {@link DataItem}'s {@link DataMap} with {@code newConfig}.
+ * If the config DataItem doesn't exist, it's created.
+ */
+ public static void putConfigDataItem(GoogleApiClient googleApiClient, DataMap newConfig) {
+ PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_WITH_FEATURE);
+ DataMap configToPut = putDataMapRequest.getDataMap();
+ configToPut.putAll(newConfig);
+ Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest())
+ .setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(DataApi.DataItemResult dataItemResult) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "putDataItem result status: " + dataItemResult.getStatus());
+ }
+ }
+ });
+ }
+
+ private static class DataItemResultCallback implements ResultCallback {
+
+ private final FetchConfigDataMapCallback mCallback;
+
+ public DataItemResultCallback(FetchConfigDataMapCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(DataApi.DataItemResult dataItemResult) {
+ if (dataItemResult.getStatus().isSuccess()) {
+ if (dataItemResult.getDataItem() != null) {
+ DataItem configDataItem = dataItemResult.getDataItem();
+ DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
+ DataMap config = dataMapItem.getDataMap();
+ mCallback.onConfigDataMapFetched(config);
+ } else {
+ mCallback.onConfigDataMapFetched(new DataMap());
+ }
+ }
+ }
+ }
+
+ private DigitalWatchFaceUtil() { }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceWearableConfigActivity.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceWearableConfigActivity.java
new file mode 100644
index 000000000..4b309e6e2
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceWearableConfigActivity.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.wearable.view.BoxInsetLayout;
+import android.support.wearable.view.CircledImageView;
+import android.support.wearable.view.WearableListView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.Wearable;
+
+/**
+ * The watch-side config activity for {@link DigitalWatchFaceService}, which allows for setting the
+ * background color.
+ */
+public class DigitalWatchFaceWearableConfigActivity extends Activity implements
+ WearableListView.ClickListener, WearableListView.OnScrollListener {
+ private static final String TAG = "DigitalWatchFaceConfig";
+
+ private GoogleApiClient mGoogleApiClient;
+ private TextView mHeader;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_digital_config);
+
+ mHeader = (TextView) findViewById(R.id.header);
+ WearableListView listView = (WearableListView) findViewById(R.id.color_picker);
+ BoxInsetLayout content = (BoxInsetLayout) findViewById(R.id.content);
+ // BoxInsetLayout adds padding by default on round devices. Add some on square devices.
+ content.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ if (!insets.isRound()) {
+ v.setPaddingRelative(
+ (int) getResources().getDimensionPixelSize(R.dimen.content_padding_start),
+ v.getPaddingTop(),
+ v.getPaddingEnd(),
+ v.getPaddingBottom());
+ }
+ return v.onApplyWindowInsets(insets);
+ }
+ });
+
+ listView.setHasFixedSize(true);
+ listView.setClickListener(this);
+ listView.addOnScrollListener(this);
+
+ String[] colors = getResources().getStringArray(R.array.color_array);
+ listView.setAdapter(new ColorListAdapter(colors));
+
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnected: " + connectionHint);
+ }
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionSuspended: " + cause);
+ }
+ }
+ })
+ .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConnectionFailed: " + result);
+ }
+ }
+ })
+ .addApi(Wearable.API)
+ .build();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mGoogleApiClient.connect();
+ }
+
+ @Override
+ protected void onStop() {
+ if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+ mGoogleApiClient.disconnect();
+ }
+ super.onStop();
+ }
+
+ @Override // WearableListView.ClickListener
+ public void onClick(WearableListView.ViewHolder viewHolder) {
+ ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) viewHolder;
+ updateConfigDataItem(colorItemViewHolder.mColorItem.getColor());
+ finish();
+ }
+
+ @Override // WearableListView.ClickListener
+ public void onTopEmptyRegionClick() {}
+
+ @Override // WearableListView.OnScrollListener
+ public void onScroll(int scroll) {}
+
+ @Override // WearableListView.OnScrollListener
+ public void onAbsoluteScrollChange(int scroll) {
+ float newTranslation = Math.min(-scroll, 0);
+ mHeader.setTranslationY(newTranslation);
+ }
+
+ @Override // WearableListView.OnScrollListener
+ public void onScrollStateChanged(int scrollState) {}
+
+ @Override // WearableListView.OnScrollListener
+ public void onCentralPositionChanged(int centralPosition) {}
+
+ private void updateConfigDataItem(final int backgroundColor) {
+ DataMap configKeysToOverwrite = new DataMap();
+ configKeysToOverwrite.putInt(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR,
+ backgroundColor);
+ DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite);
+ }
+
+ private class ColorListAdapter extends WearableListView.Adapter {
+ private final String[] mColors;
+
+ public ColorListAdapter(String[] colors) {
+ mColors = colors;
+ }
+
+ @Override
+ public ColorItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new ColorItemViewHolder(new ColorItem(parent.getContext()));
+ }
+
+ @Override
+ public void onBindViewHolder(WearableListView.ViewHolder holder, int position) {
+ ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) holder;
+ String colorName = mColors[position];
+ colorItemViewHolder.mColorItem.setColor(colorName);
+
+ RecyclerView.LayoutParams layoutParams =
+ new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ int colorPickerItemMargin = (int) getResources()
+ .getDimension(R.dimen.digital_config_color_picker_item_margin);
+ // Add margins to first and last item to make it possible for user to tap on them.
+ if (position == 0) {
+ layoutParams.setMargins(0, colorPickerItemMargin, 0, 0);
+ } else if (position == mColors.length - 1) {
+ layoutParams.setMargins(0, 0, 0, colorPickerItemMargin);
+ } else {
+ layoutParams.setMargins(0, 0, 0, 0);
+ }
+ colorItemViewHolder.itemView.setLayoutParams(layoutParams);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mColors.length;
+ }
+ }
+
+ /** The layout of a color item including image and label. */
+ private static class ColorItem extends LinearLayout implements
+ WearableListView.OnCenterProximityListener {
+ /** The duration of the expand/shrink animation. */
+ private static final int ANIMATION_DURATION_MS = 150;
+ /** The ratio for the size of a circle in shrink state. */
+ private static final float SHRINK_CIRCLE_RATIO = .75f;
+
+ private static final float SHRINK_LABEL_ALPHA = .5f;
+ private static final float EXPAND_LABEL_ALPHA = 1f;
+
+ private final TextView mLabel;
+ private final CircledImageView mColor;
+
+ private final float mExpandCircleRadius;
+ private final float mShrinkCircleRadius;
+
+ private final ObjectAnimator mExpandCircleAnimator;
+ private final ObjectAnimator mExpandLabelAnimator;
+ private final AnimatorSet mExpandAnimator;
+
+ private final ObjectAnimator mShrinkCircleAnimator;
+ private final ObjectAnimator mShrinkLabelAnimator;
+ private final AnimatorSet mShrinkAnimator;
+
+ public ColorItem(Context context) {
+ super(context);
+ View.inflate(context, R.layout.color_picker_item, this);
+
+ mLabel = (TextView) findViewById(R.id.label);
+ mColor = (CircledImageView) findViewById(R.id.color);
+
+ mExpandCircleRadius = mColor.getCircleRadius();
+ mShrinkCircleRadius = mExpandCircleRadius * SHRINK_CIRCLE_RATIO;
+
+ mShrinkCircleAnimator = ObjectAnimator.ofFloat(mColor, "circleRadius",
+ mExpandCircleRadius, mShrinkCircleRadius);
+ mShrinkLabelAnimator = ObjectAnimator.ofFloat(mLabel, "alpha",
+ EXPAND_LABEL_ALPHA, SHRINK_LABEL_ALPHA);
+ mShrinkAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
+ mShrinkAnimator.playTogether(mShrinkCircleAnimator, mShrinkLabelAnimator);
+
+ mExpandCircleAnimator = ObjectAnimator.ofFloat(mColor, "circleRadius",
+ mShrinkCircleRadius, mExpandCircleRadius);
+ mExpandLabelAnimator = ObjectAnimator.ofFloat(mLabel, "alpha",
+ SHRINK_LABEL_ALPHA, EXPAND_LABEL_ALPHA);
+ mExpandAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
+ mExpandAnimator.playTogether(mExpandCircleAnimator, mExpandLabelAnimator);
+ }
+
+ @Override
+ public void onCenterPosition(boolean animate) {
+ if (animate) {
+ mShrinkAnimator.cancel();
+ if (!mExpandAnimator.isRunning()) {
+ mExpandCircleAnimator.setFloatValues(mColor.getCircleRadius(), mExpandCircleRadius);
+ mExpandLabelAnimator.setFloatValues(mLabel.getAlpha(), EXPAND_LABEL_ALPHA);
+ mExpandAnimator.start();
+ }
+ } else {
+ mExpandAnimator.cancel();
+ mColor.setCircleRadius(mExpandCircleRadius);
+ mLabel.setAlpha(EXPAND_LABEL_ALPHA);
+ }
+ }
+
+ @Override
+ public void onNonCenterPosition(boolean animate) {
+ if (animate) {
+ mExpandAnimator.cancel();
+ if (!mShrinkAnimator.isRunning()) {
+ mShrinkCircleAnimator.setFloatValues(mColor.getCircleRadius(), mShrinkCircleRadius);
+ mShrinkLabelAnimator.setFloatValues(mLabel.getAlpha(), SHRINK_LABEL_ALPHA);
+ mShrinkAnimator.start();
+ }
+ } else {
+ mShrinkAnimator.cancel();
+ mColor.setCircleRadius(mShrinkCircleRadius);
+ mLabel.setAlpha(SHRINK_LABEL_ALPHA);
+ }
+ }
+
+ private void setColor(String colorName) {
+ mLabel.setText(colorName);
+ mColor.setCircleColor(Color.parseColor(colorName));
+ }
+
+ private int getColor() {
+ return mColor.getDefaultCircleColor();
+ }
+ }
+
+ private static class ColorItemViewHolder extends WearableListView.ViewHolder {
+ private final ColorItem mColorItem;
+
+ public ColorItemViewHolder(ColorItem colorItem) {
+ super(colorItem);
+ mColorItem = colorItem;
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/Gles2ColoredTriangleList.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/Gles2ColoredTriangleList.java
new file mode 100644
index 000000000..2441c6591
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/Gles2ColoredTriangleList.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import android.opengl.GLES20;
+import android.opengl.GLU;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+/**
+ * A list of triangles drawn in a single solid color using OpenGL ES 2.0.
+ */
+public class Gles2ColoredTriangleList {
+ private static final String TAG = "GlColoredTriangleList";
+
+ /** Whether to check for GL errors. This is slow, so not appropriate for production builds. */
+ private static final boolean CHECK_GL_ERRORS = false;
+
+ /** Number of coordinates per vertex in this array: one for each of x, y, and z. */
+ private static final int COORDS_PER_VERTEX = 3;
+
+ /** Number of bytes to store a float in GL. */
+ public static final int BYTES_PER_FLOAT = 4;
+
+ /** Number of bytes per vertex. */
+ private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * BYTES_PER_FLOAT;
+
+ /** Triangles have three vertices. */
+ private static final int VERTICE_PER_TRIANGLE = 3;
+
+ /**
+ * Number of components in an OpenGL color. The components are:
+ * - red
+ *
- green
+ *
- blue
+ *
- alpha
+ *
+ */
+ private static final int NUM_COLOR_COMPONENTS = 4;
+
+ /** Shaders to render this triangle list. */
+ private final Program mProgram;
+
+ /** The VBO containing the vertex coordinates. */
+ private final FloatBuffer mVertexBuffer;
+
+ /**
+ * Color of this triangle list represented as an array of floats in the range [0, 1] in RGBA
+ * order.
+ */
+ private final float mColor[];
+
+ /** Number of coordinates in this triangle list. */
+ private final int mNumCoords;
+
+ /**
+ * Creates a Gles2ColoredTriangleList to draw a triangle list with the given vertices and color.
+ *
+ * @param program program for drawing triangles
+ * @param triangleCoords flat array of 3D coordinates of triangle vertices in counterclockwise
+ * order
+ * @param color color in RGBA order, each in the range [0, 1]
+ */
+ public Gles2ColoredTriangleList(Program program, float[] triangleCoords, float[] color) {
+ if (triangleCoords.length % (VERTICE_PER_TRIANGLE * COORDS_PER_VERTEX) != 0) {
+ throw new IllegalArgumentException("must be multiple"
+ + " of VERTICE_PER_TRIANGLE * COORDS_PER_VERTEX coordinates");
+ }
+ if (color.length != NUM_COLOR_COMPONENTS) {
+ throw new IllegalArgumentException("wrong number of color components");
+ }
+ mProgram = program;
+ mColor = color;
+
+ ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT);
+
+ // Use the device hardware's native byte order.
+ bb.order(ByteOrder.nativeOrder());
+
+ // Create a FloatBuffer that wraps the ByteBuffer.
+ mVertexBuffer = bb.asFloatBuffer();
+
+ // Add the coordinates to the FloatBuffer.
+ mVertexBuffer.put(triangleCoords);
+
+ // Go back to the start for reading.
+ mVertexBuffer.position(0);
+
+ mNumCoords = triangleCoords.length / COORDS_PER_VERTEX;
+ }
+
+ /**
+ * Draws this triangle list using OpenGL commands.
+ *
+ * @param mvpMatrix the Model View Project matrix to draw this triangle list
+ */
+ public void draw(float[] mvpMatrix) {
+ // Pass the MVP matrix, vertex data, and color to OpenGL.
+ mProgram.bind(mvpMatrix, mVertexBuffer, mColor);
+
+ // Draw the triangle list.
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mNumCoords);
+ if (CHECK_GL_ERRORS) checkGlError("glDrawArrays");
+ }
+
+ /**
+ * Checks if any of the GL calls since the last time this method was called set an error
+ * condition. Call this method immediately after calling a GL method. Pass the name of the GL
+ * operation. For example:
+ *
+ *
+ * mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
+ * MyGLRenderer.checkGlError("glGetUniformLocation");
+ *
+ * If the operation is not successful, the check throws an exception.
+ *
+ * Note This is quite slow so it's best to use it sparingly in production builds.
+ *
+ * @param glOperation name of the OpenGL call to check
+ */
+ private static void checkGlError(String glOperation) {
+ int error = GLES20.glGetError();
+ if (error != GLES20.GL_NO_ERROR) {
+ String errorString = GLU.gluErrorString(error);
+ if (errorString == null) {
+ errorString = GLUtils.getEGLErrorString(error);
+ }
+ String message = glOperation + " caused GL error 0x" + Integer.toHexString(error) +
+ ": " + errorString;
+ Log.e(TAG, message);
+ throw new RuntimeException(message);
+ }
+ }
+
+ /**
+ * Compiles an OpenGL shader.
+ *
+ * @param type {@link GLES20#GL_VERTEX_SHADER} or {@link GLES20#GL_FRAGMENT_SHADER}
+ * @param shaderCode string containing the shader source code
+ * @return ID for the shader
+ */
+ private static int loadShader(int type, String shaderCode){
+ // Create a vertex or fragment shader.
+ int shader = GLES20.glCreateShader(type);
+ if (CHECK_GL_ERRORS) checkGlError("glCreateShader");
+ if (shader == 0) {
+ throw new IllegalStateException("glCreateShader failed");
+ }
+
+ // Add the source code to the shader and compile it.
+ GLES20.glShaderSource(shader, shaderCode);
+ if (CHECK_GL_ERRORS) checkGlError("glShaderSource");
+ GLES20.glCompileShader(shader);
+ if (CHECK_GL_ERRORS) checkGlError("glCompileShader");
+
+ return shader;
+ }
+
+ /** OpenGL shaders for drawing solid colored triangle lists. */
+ public static class Program {
+ /** Trivial vertex shader that transforms the input vertex by the MVP matrix. */
+ private static final String VERTEX_SHADER_CODE = "" +
+ "uniform mat4 uMvpMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "void main() {\n" +
+ " gl_Position = uMvpMatrix * aPosition;\n" +
+ "}\n";
+
+ /** Trivial fragment shader that draws with a fixed color. */
+ private static final String FRAGMENT_SHADER_CODE = "" +
+ "precision mediump float;\n" +
+ "uniform vec4 uColor;\n" +
+ "void main() {\n" +
+ " gl_FragColor = uColor;\n" +
+ "}\n";
+
+ /** ID OpenGL uses to identify this program. */
+ private final int mProgramId;
+
+ /** Handle for uMvpMatrix uniform in vertex shader. */
+ private final int mMvpMatrixHandle;
+
+ /** Handle for aPosition attribute in vertex shader. */
+ private final int mPositionHandle;
+
+ /** Handle for uColor uniform in fragment shader. */
+ private final int mColorHandle;
+
+ /**
+ * Creates a program to draw triangle lists. For optimal drawing efficiency, one program
+ * should be used for all triangle lists being drawn.
+ */
+ public Program() {
+ // Prepare shaders.
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
+ int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);
+
+ // Create empty OpenGL Program.
+ mProgramId = GLES20.glCreateProgram();
+ if (CHECK_GL_ERRORS) checkGlError("glCreateProgram");
+ if (mProgramId == 0) {
+ throw new IllegalStateException("glCreateProgram failed");
+ }
+
+ // Add the shaders to the program.
+ GLES20.glAttachShader(mProgramId, vertexShader);
+ if (CHECK_GL_ERRORS) checkGlError("glAttachShader");
+ GLES20.glAttachShader(mProgramId, fragmentShader);
+ if (CHECK_GL_ERRORS) checkGlError("glAttachShader");
+
+ // Link the program so it can be executed.
+ GLES20.glLinkProgram(mProgramId);
+ if (CHECK_GL_ERRORS) checkGlError("glLinkProgram");
+
+ // Get a handle to the uMvpMatrix uniform in the vertex shader.
+ mMvpMatrixHandle = GLES20.glGetUniformLocation(mProgramId, "uMvpMatrix");
+ if (CHECK_GL_ERRORS) checkGlError("glGetUniformLocation");
+
+ // Get a handle to the vertex shader's aPosition attribute.
+ mPositionHandle = GLES20.glGetAttribLocation(mProgramId, "aPosition");
+ if (CHECK_GL_ERRORS) checkGlError("glGetAttribLocation");
+
+ // Enable vertex array (VBO).
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ if (CHECK_GL_ERRORS) checkGlError("glEnableVertexAttribArray");
+
+ // Get a handle to fragment shader's uColor uniform.
+ mColorHandle = GLES20.glGetUniformLocation(mProgramId, "uColor");
+ if (CHECK_GL_ERRORS) checkGlError("glGetUniformLocation");
+ }
+
+ /**
+ * Tells OpenGL to use this program. Call this method before drawing a sequence of
+ * triangle lists.
+ */
+ public void use() {
+ GLES20.glUseProgram(mProgramId);
+ if (CHECK_GL_ERRORS) checkGlError("glUseProgram");
+ }
+
+ /** Sends the given MVP matrix, vertex data, and color to OpenGL. */
+ public void bind(float[] mvpMatrix, FloatBuffer vertexBuffer, float[] color) {
+ // Pass the MVP matrix to OpenGL.
+ GLES20.glUniformMatrix4fv(mMvpMatrixHandle, 1 /* count */, false /* transpose */,
+ mvpMatrix, 0 /* offset */);
+ if (CHECK_GL_ERRORS) checkGlError("glUniformMatrix4fv");
+
+ // Pass the VBO with the triangle list's vertices to OpenGL.
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ if (CHECK_GL_ERRORS) checkGlError("glEnableVertexAttribArray");
+ GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+ false /* normalized */, VERTEX_STRIDE, vertexBuffer);
+ if (CHECK_GL_ERRORS) checkGlError("glVertexAttribPointer");
+
+ // Pass the triangle list's color to OpenGL.
+ GLES20.glUniform4fv(mColorHandle, 1 /* count */, color, 0 /* offset */);
+ if (CHECK_GL_ERRORS) checkGlError("glUniform4fv");
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java
new file mode 100644
index 000000000..44e9569f5
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+
+/**
+ * Sample analog watch face with a sweep second hand. In ambient mode, the second hand isn't shown.
+ * On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode.
+ * The watch face is drawn with less contrast in mute mode.
+ *
+ * {@link AnalogWatchFaceService} is similar but has a ticking second hand.
+ */
+public class SweepWatchFaceService extends CanvasWatchFaceService {
+ private static final String TAG = "SweepWatchFaceService";
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+ Paint mHourPaint;
+ Paint mMinutePaint;
+ Paint mSecondPaint;
+ Paint mTickPaint;
+ boolean mMute;
+ Time mTime;
+
+ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+ boolean mRegisteredTimeZoneReceiver = false;
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ boolean mLowBitAmbient;
+
+ Bitmap mBackgroundBitmap;
+ Bitmap mBackgroundScaledBitmap;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(holder);
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(SweepWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+
+ Resources resources = SweepWatchFaceService.this.getResources();
+ Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+ mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
+
+ mHourPaint = new Paint();
+ mHourPaint.setARGB(255, 200, 200, 200);
+ mHourPaint.setStrokeWidth(5.f);
+ mHourPaint.setAntiAlias(true);
+ mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mMinutePaint = new Paint();
+ mMinutePaint.setARGB(255, 200, 200, 200);
+ mMinutePaint.setStrokeWidth(3.f);
+ mMinutePaint.setAntiAlias(true);
+ mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mSecondPaint = new Paint();
+ mSecondPaint.setARGB(255, 255, 0, 0);
+ mSecondPaint.setStrokeWidth(2.f);
+ mSecondPaint.setAntiAlias(true);
+ mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mTickPaint = new Paint();
+ mTickPaint.setARGB(100, 255, 255, 255);
+ mTickPaint.setStrokeWidth(2.f);
+ mTickPaint.setAntiAlias(true);
+
+ mTime = new Time();
+ }
+
+ @Override
+ public void onPropertiesChanged(Bundle properties) {
+ super.onPropertiesChanged(properties);
+ mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
+ }
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ super.onAmbientModeChanged(inAmbientMode);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+ }
+ if (mLowBitAmbient) {
+ boolean antiAlias = !inAmbientMode;
+ mHourPaint.setAntiAlias(antiAlias);
+ mMinutePaint.setAntiAlias(antiAlias);
+ mSecondPaint.setAntiAlias(antiAlias);
+ mTickPaint.setAntiAlias(antiAlias);
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onInterruptionFilterChanged(int interruptionFilter) {
+ super.onInterruptionFilterChanged(interruptionFilter);
+ boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
+ if (mMute != inMuteMode) {
+ mMute = inMuteMode;
+ mHourPaint.setAlpha(inMuteMode ? 100 : 255);
+ mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
+ mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "onDraw");
+ }
+ long now = System.currentTimeMillis();
+ mTime.set(now);
+ int milliseconds = (int) (now % 1000);
+
+ int width = bounds.width();
+ int height = bounds.height();
+
+ // Draw the background, scaled to fit.
+ if (mBackgroundScaledBitmap == null
+ || mBackgroundScaledBitmap.getWidth() != width
+ || mBackgroundScaledBitmap.getHeight() != height) {
+ mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
+ width, height, true /* filter */);
+ }
+ canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
+
+ // Find the center. Ignore the window insets so that, on round watches with a
+ // "chin", the watch face is centered on the entire screen, not just the usable
+ // portion.
+ float centerX = width / 2f;
+ float centerY = height / 2f;
+
+ // Draw the ticks.
+ float innerTickRadius = centerX - 10;
+ float outerTickRadius = centerX;
+ for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
+ float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
+ float innerX = (float) Math.sin(tickRot) * innerTickRadius;
+ float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
+ float outerX = (float) Math.sin(tickRot) * outerTickRadius;
+ float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
+ canvas.drawLine(centerX + innerX, centerY + innerY,
+ centerX + outerX, centerY + outerY, mTickPaint);
+ }
+
+ float seconds = mTime.second + milliseconds / 1000f;
+ float secRot = seconds / 30f * (float) Math.PI;
+ int minutes = mTime.minute;
+ float minRot = minutes / 30f * (float) Math.PI;
+ float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+
+ float secLength = centerX - 20;
+ float minLength = centerX - 40;
+ float hrLength = centerX - 80;
+
+ if (!isInAmbientMode()) {
+ float secX = (float) Math.sin(secRot) * secLength;
+ float secY = (float) -Math.cos(secRot) * secLength;
+ canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
+ }
+
+ float minX = (float) Math.sin(minRot) * minLength;
+ float minY = (float) -Math.cos(minRot) * minLength;
+ canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);
+
+ float hrX = (float) Math.sin(hrRot) * hrLength;
+ float hrY = (float) -Math.cos(hrRot) * hrLength;
+ canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
+
+ // Draw every frame as long as we're visible and in interactive mode.
+ if (isVisible() && !isInAmbientMode()) {
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we weren't visible.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+
+ invalidate();
+ } else {
+ unregisterReceiver();
+ }
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ SweepWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ SweepWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java
new file mode 100644
index 000000000..6dd01b000
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.support.wearable.watchface.Gles2WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample watch face using OpenGL. The watch face is rendered using
+ * {@link Gles2ColoredTriangleList}s. The camera moves around in interactive mode and stops moving
+ * when the watch enters ambient mode.
+ */
+public class TiltWatchFaceService extends Gles2WatchFaceService {
+
+ private static final String TAG = "TiltWatchFaceService";
+
+ /** Expected frame rate in interactive mode. */
+ private static final long FPS = 60;
+
+ /** How long each frame is displayed at expected frame rate. */
+ private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends Gles2WatchFaceService.Engine {
+ /** Cycle time before the camera motion repeats. */
+ private static final long CYCLE_PERIOD_SECONDS = 5;
+
+ /** Number of camera angles to precompute. */
+ private final int mNumCameraAngles = (int) (CYCLE_PERIOD_SECONDS * FPS);
+
+ /** Projection transformation matrix. Converts from 3D to 2D. */
+ private final float[] mProjectionMatrix = new float[16];
+
+ /**
+ * View transformation matrices to use in interactive mode. Converts from world to camera-
+ * relative coordinates. One matrix per camera position.
+ */
+ private final float[][] mViewMatrices = new float[mNumCameraAngles][16];
+
+ /** The view transformation matrix to use in ambient mode */
+ private final float[] mAmbientViewMatrix = new float[16];
+
+ /**
+ * Model transformation matrices. Converts from model-relative coordinates to world
+ * coordinates. One matrix per degree of rotation.
+ */
+ private final float[][] mModelMatrices = new float[360][16];
+
+ /**
+ * Products of {@link #mViewMatrices} and {@link #mProjectionMatrix}. One matrix per camera
+ * position.
+ */
+ private final float[][] mVpMatrices = new float[mNumCameraAngles][16];
+
+ /** The product of {@link #mAmbientViewMatrix} and {@link #mProjectionMatrix} */
+ private final float[] mAmbientVpMatrix = new float[16];
+
+ /**
+ * Product of {@link #mModelMatrices}, {@link #mViewMatrices}, and
+ * {@link #mProjectionMatrix}.
+ */
+ private final float[] mMvpMatrix = new float[16];
+
+ /** Triangles for the 4 major ticks. These are grouped together to speed up rendering. */
+ private Gles2ColoredTriangleList mMajorTickTriangles;
+
+ /** Triangles for the 8 minor ticks. These are grouped together to speed up rendering. */
+ private Gles2ColoredTriangleList mMinorTickTriangles;
+
+ /** Triangle for the second hand. */
+ private Gles2ColoredTriangleList mSecondHandTriangle;
+
+ /** Triangle for the minute hand. */
+ private Gles2ColoredTriangleList mMinuteHandTriangle;
+
+ /** Triangle for the hour hand. */
+ private Gles2ColoredTriangleList mHourHandTriangle;
+
+ private Time mTime = new Time();
+
+ /** Whether we've registered {@link #mTimeZoneReceiver}. */
+ private boolean mRegisteredTimeZoneReceiver;
+
+ private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+
+ @Override
+ public void onCreate(SurfaceHolder surfaceHolder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(surfaceHolder);
+ setWatchFaceStyle(new WatchFaceStyle.Builder(TiltWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setStatusBarGravity(Gravity.RIGHT | Gravity.TOP)
+ .setHotwordIndicatorGravity(Gravity.LEFT | Gravity.TOP)
+ .setShowSystemUiTime(false)
+ .build());
+ }
+
+ @Override
+ public void onGlContextCreated() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onGlContextCreated");
+ }
+ super.onGlContextCreated();
+
+ // Create program for drawing triangles.
+ Gles2ColoredTriangleList.Program triangleProgram =
+ new Gles2ColoredTriangleList.Program();
+
+ // We only draw triangles which all use the same program so we don't need to switch
+ // programs mid-frame. This means we can tell OpenGL to use this program only once
+ // rather than having to do so for each frame. This makes OpenGL draw faster.
+ triangleProgram.use();
+
+ // Create triangles for the ticks.
+ mMajorTickTriangles = createMajorTicks(triangleProgram);
+ mMinorTickTriangles = createMinorTicks(triangleProgram);
+
+ // Create triangles for the hands.
+ mSecondHandTriangle = createHand(
+ triangleProgram,
+ 0.02f /* width */,
+ 1.0f /* height */,
+ new float[]{
+ 1.0f /* red */,
+ 0.0f /* green */,
+ 0.0f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ mMinuteHandTriangle = createHand(
+ triangleProgram,
+ 0.06f /* width */,
+ 0.8f /* height */,
+ new float[]{
+ 0.7f /* red */,
+ 0.7f /* green */,
+ 0.7f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ mHourHandTriangle = createHand(
+ triangleProgram,
+ 0.1f /* width */,
+ 0.5f /* height */,
+ new float[]{
+ 0.9f /* red */,
+ 0.9f /* green */,
+ 0.9f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+
+ // Precompute the clock angles.
+ for (int i = 0; i < mModelMatrices.length; ++i) {
+ Matrix.setRotateM(mModelMatrices[i], 0, i, 0, 0, 1);
+ }
+
+ // Precompute the camera angles.
+ for (int i = 0; i < mNumCameraAngles; ++i) {
+ // Set the camera position (View matrix). When active, move the eye around to show
+ // off that this is 3D.
+ final float cameraAngle = (float) (((float) i) / mNumCameraAngles * 2 * Math.PI);
+ final float eyeX = (float) Math.cos(cameraAngle);
+ final float eyeY = (float) Math.sin(cameraAngle);
+ Matrix.setLookAtM(mViewMatrices[i],
+ 0, // dest index
+ eyeX, eyeY, -3, // eye
+ 0, 0, 0, // center
+ 0, 1, 0); // up vector
+ }
+
+ Matrix.setLookAtM(mAmbientViewMatrix,
+ 0, // dest index
+ 0, 0, -3, // eye
+ 0, 0, 0, // center
+ 0, 1, 0); // up vector
+ }
+
+ @Override
+ public void onGlSurfaceCreated(int width, int height) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onGlSurfaceCreated: " + width + " x " + height);
+ }
+ super.onGlSurfaceCreated(width, height);
+
+ // Update the projection matrix based on the new aspect ratio.
+ final float aspectRatio = (float) width / height;
+ Matrix.frustumM(mProjectionMatrix,
+ 0 /* offset */,
+ -aspectRatio /* left */,
+ aspectRatio /* right */,
+ -1 /* bottom */,
+ 1 /* top */,
+ 2 /* near */,
+ 7 /* far */);
+
+ // Precompute the products of Projection and View matrices for each camera angle.
+ for (int i = 0; i < mNumCameraAngles; ++i) {
+ Matrix.multiplyMM(mVpMatrices[i], 0, mProjectionMatrix, 0, mViewMatrices[i], 0);
+ }
+
+ Matrix.multiplyMM(mAmbientVpMatrix, 0, mProjectionMatrix, 0, mAmbientViewMatrix, 0);
+ }
+
+ /**
+ * Creates a triangle for a hand on the watch face.
+ *
+ * @param program program for drawing triangles
+ * @param width width of base of triangle
+ * @param length length of triangle
+ * @param color color in RGBA order, each in the range [0, 1]
+ */
+ private Gles2ColoredTriangleList createHand(Gles2ColoredTriangleList.Program program,
+ float width, float length, float[] color) {
+ // Create the data for the VBO.
+ float[] triangleCoords = new float[]{
+ // in counterclockwise order:
+ 0, length, 0, // top
+ -width / 2, 0, 0, // bottom left
+ width / 2, 0, 0 // bottom right
+ };
+ return new Gles2ColoredTriangleList(program, triangleCoords, color);
+ }
+
+ /**
+ * Creates a triangle list for the major ticks on the watch face.
+ *
+ * @param program program for drawing triangles
+ */
+ private Gles2ColoredTriangleList createMajorTicks(
+ Gles2ColoredTriangleList.Program program) {
+ // Create the data for the VBO.
+ float[] trianglesCoords = new float[9 * 4];
+ for (int i = 0; i < 4; i++) {
+ float[] triangleCoords = getMajorTickTriangleCoords(i);
+ System.arraycopy(triangleCoords, 0, trianglesCoords, i * 9, triangleCoords.length);
+ }
+
+ return new Gles2ColoredTriangleList(program, trianglesCoords,
+ new float[]{
+ 1.0f /* red */,
+ 1.0f /* green */,
+ 1.0f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ }
+
+ /**
+ * Creates a triangle list for the minor ticks on the watch face.
+ *
+ * @param program program for drawing triangles
+ */
+ private Gles2ColoredTriangleList createMinorTicks(
+ Gles2ColoredTriangleList.Program program) {
+ // Create the data for the VBO.
+ float[] trianglesCoords = new float[9 * (12 - 4)];
+ int index = 0;
+ for (int i = 0; i < 12; i++) {
+ if (i % 3 == 0) {
+ // This is where a major tick goes, so skip it.
+ continue;
+ }
+ float[] triangleCoords = getMinorTickTriangleCoords(i);
+ System.arraycopy(triangleCoords, 0, trianglesCoords, index, triangleCoords.length);
+ index += 9;
+ }
+
+ return new Gles2ColoredTriangleList(program, trianglesCoords,
+ new float[]{
+ 0.5f /* red */,
+ 0.5f /* green */,
+ 0.5f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ }
+
+ private float[] getMajorTickTriangleCoords(int index) {
+ return getTickTriangleCoords(0.03f /* width */, 0.09f /* length */,
+ index * 360 / 4 /* angleDegrees */);
+ }
+
+ private float[] getMinorTickTriangleCoords(int index) {
+ return getTickTriangleCoords(0.02f /* width */, 0.06f /* length */,
+ index * 360 / 12 /* angleDegrees */);
+ }
+
+ private float[] getTickTriangleCoords(float width, float length, int angleDegrees) {
+ // Create the data for the VBO.
+ float[] coords = new float[]{
+ // in counterclockwise order:
+ 0, 1, 0, // top
+ width / 2, length + 1, 0, // bottom left
+ -width / 2, length + 1, 0 // bottom right
+ };
+
+ rotateCoords(coords, angleDegrees);
+ return coords;
+ }
+
+ /**
+ * Destructively rotates the given coordinates in the XY plane about the origin by the given
+ * angle.
+ *
+ * @param coords flattened 3D coordinates
+ * @param angleDegrees angle in degrees clockwise when viewed from negative infinity on the
+ * Z axis
+ */
+ private void rotateCoords(float[] coords, int angleDegrees) {
+ double angleRadians = Math.toRadians(angleDegrees);
+ double cos = Math.cos(angleRadians);
+ double sin = Math.sin(angleRadians);
+ for (int i = 0; i < coords.length; i += 3) {
+ float x = coords[i];
+ float y = coords[i + 1];
+ coords[i] = (float) (cos * x - sin * y);
+ coords[i + 1] = (float) (sin * x + cos * y);
+ }
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+ }
+ super.onAmbientModeChanged(inAmbientMode);
+ invalidate();
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onVisibilityChanged: " + visible);
+ }
+ super.onVisibilityChanged(visible);
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we were detached.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+
+ invalidate();
+ } else {
+ unregisterReceiver();
+ }
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ TiltWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ TiltWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onDraw() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "onDraw");
+ }
+ super.onDraw();
+ final float[] vpMatrix;
+
+ // Draw background color and select the appropriate view projection matrix. The
+ // background should always be black in ambient mode. The view projection matrix used is
+ // overhead in ambient. In interactive mode, it's tilted depending on the current time.
+ if (isInAmbientMode()) {
+ GLES20.glClearColor(0, 0, 0, 1);
+ vpMatrix = mAmbientVpMatrix;
+ } else {
+ GLES20.glClearColor(0.5f, 0.2f, 0.2f, 1);
+ final int cameraIndex =
+ (int) ((System.currentTimeMillis() / FRAME_PERIOD_MS) % mNumCameraAngles);
+ vpMatrix = mVpMatrices[cameraIndex];
+ }
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+
+ // Compute angle indices for the three hands.
+ mTime.setToNow();
+ final int secIndex = mTime.second * 360 / 60;
+ final int minIndex = mTime.minute * 360 / 60;
+ final int hoursIndex = (mTime.hour % 12) * 360 / 12 + mTime.minute * 360 / 60 / 12;
+
+ // Draw triangles from back to front. Don't draw the second hand in ambient mode.
+ {
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[hoursIndex], 0);
+
+ // Draw the triangle.
+ mHourHandTriangle.draw(mMvpMatrix);
+ }
+ {
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[minIndex], 0);
+
+ // Draw the triangle.
+ mMinuteHandTriangle.draw(mMvpMatrix);
+ }
+ if (!isInAmbientMode()) {
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[secIndex], 0);
+
+ // Draw the triangle.
+ mSecondHandTriangle.draw(mMvpMatrix);
+ }
+ {
+ // Draw the major and minor ticks.
+ mMajorTickTriangles.draw(vpMatrix);
+ mMinorTickTriangles.draw(vpMatrix);
+ }
+
+ // Draw every frame as long as we're visible and in interactive mode.
+ if (isVisible() && !isInAmbientMode()) {
+ invalidate();
+ }
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/_index.jd b/samples/browseable/WatchFace/_index.jd
new file mode 100644
index 000000000..fcba857b8
--- /dev/null
+++ b/samples/browseable/WatchFace/_index.jd
@@ -0,0 +1,12 @@
+page.tags="WatchFace"
+sample.group=Wearable
+@jd:body
+
+
+
+This sample demonstrates how to create watch faces for android wear and includes a phone app
+and a wearable app. The wearable app has a variety of watch faces including analog, digital,
+opengl, calendar, etc. It also includes a watch-side configuration example. The phone app
+includes a phone-side configuration example.
+
+
diff --git a/samples/browseable/WatchViewStub/Application/AndroidManifest.xml b/samples/browseable/WatchViewStub/Application/AndroidManifest.xml
index e98a62f12..b1b7103dc 100644
--- a/samples/browseable/WatchViewStub/Application/AndroidManifest.xml
+++ b/samples/browseable/WatchViewStub/Application/AndroidManifest.xml
@@ -18,10 +18,11 @@
-
+
diff --git a/samples/browseable/WatchViewStub/Application/res/drawable-hdpi/ic_launcher.png b/samples/browseable/WatchViewStub/Application/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 000000000..589f229d1
Binary files /dev/null and b/samples/browseable/WatchViewStub/Application/res/drawable-hdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchViewStub/Application/res/drawable-mdpi/ic_launcher.png b/samples/browseable/WatchViewStub/Application/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 000000000..77dd57139
Binary files /dev/null and b/samples/browseable/WatchViewStub/Application/res/drawable-mdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchViewStub/Application/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/WatchViewStub/Application/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 000000000..fe34ebe13
Binary files /dev/null and b/samples/browseable/WatchViewStub/Application/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchViewStub/Application/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/WatchViewStub/Application/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 000000000..ab80bcd13
Binary files /dev/null and b/samples/browseable/WatchViewStub/Application/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/samples/browseable/WatchViewStub/Application/res/values-v21/base-colors.xml b/samples/browseable/WatchViewStub/Application/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/WatchViewStub/Application/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/browseable/WatchViewStub/Application/res/values-v21/base-template-styles.xml b/samples/browseable/WatchViewStub/Application/res/values-v21/base-template-styles.xml
new file mode 100644
index 000000000..0b2948f7e
--- /dev/null
+++ b/samples/browseable/WatchViewStub/Application/res/values-v21/base-template-styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/WatchViewStub/Application/res/values-v21/template-styles.xml b/samples/browseable/WatchViewStub/Application/res/values-v21/template-styles.xml
deleted file mode 100644
index 134fcd9d3..000000000
--- a/samples/browseable/WatchViewStub/Application/res/values-v21/template-styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/samples/browseable/WatchViewStub/Wearable/AndroidManifest.xml b/samples/browseable/WatchViewStub/Wearable/AndroidManifest.xml
index baa3f7853..33a266d63 100644
--- a/samples/browseable/WatchViewStub/Wearable/AndroidManifest.xml
+++ b/samples/browseable/WatchViewStub/Wearable/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.google.wearable.watchviewstub" >
+ android:targetSdkVersion="21" />
--
cgit v1.2.3