diff --git a/samples/browseable/ElizaChat/Shared/AndroidManifest.xml b/samples/browseable/ActionBarCompat-Basic/res/values-v21/base-colors.xml similarity index 71% rename from samples/browseable/ElizaChat/Shared/AndroidManifest.xml rename to samples/browseable/ActionBarCompat-Basic/res/values-v21/base-colors.xml index dfbb0927d..34c9cd138 100644 --- a/samples/browseable/ElizaChat/Shared/AndroidManifest.xml +++ b/samples/browseable/ActionBarCompat-Basic/res/values-v21/base-colors.xml @@ -14,12 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> + - - - - - + diff --git a/samples/browseable/RecipeAssistant/Shared/AndroidManifest.xml b/samples/browseable/ActionBarCompat-Basic/res/values-v21/base-template-styles.xml similarity index 71% rename from samples/browseable/RecipeAssistant/Shared/AndroidManifest.xml rename to samples/browseable/ActionBarCompat-Basic/res/values-v21/base-template-styles.xml index 00c86930a..0b2948f7e 100644 --- a/samples/browseable/RecipeAssistant/Shared/AndroidManifest.xml +++ b/samples/browseable/ActionBarCompat-Basic/res/values-v21/base-template-styles.xml @@ -14,12 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> + - + + - - - - + diff --git a/samples/browseable/ActionBarCompat-ListPopupMenu/res/values-v21/base-colors.xml b/samples/browseable/ActionBarCompat-ListPopupMenu/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/ActionBarCompat-ListPopupMenu/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/samples/browseable/ActionBarCompat-ListPopupMenu/res/values-v21/base-template-styles.xml b/samples/browseable/ActionBarCompat-ListPopupMenu/res/values-v21/base-template-styles.xml new file mode 100644 index 000000000..0b2948f7e --- /dev/null +++ b/samples/browseable/ActionBarCompat-ListPopupMenu/res/values-v21/base-template-styles.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/samples/browseable/ActionBarCompat-ShareActionProvider/res/values-v21/base-colors.xml b/samples/browseable/ActionBarCompat-ShareActionProvider/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/ActionBarCompat-ShareActionProvider/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/samples/browseable/ActionBarCompat-ShareActionProvider/res/values-v21/base-template-styles.xml b/samples/browseable/ActionBarCompat-ShareActionProvider/res/values-v21/base-template-styles.xml new file mode 100644 index 000000000..0b2948f7e --- /dev/null +++ b/samples/browseable/ActionBarCompat-ShareActionProvider/res/values-v21/base-template-styles.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/samples/browseable/ActionBarCompat-ShareActionProvider/src/com.example.android.actionbarcompat.shareactionprovider/MainActivity.java b/samples/browseable/ActionBarCompat-ShareActionProvider/src/com.example.android.actionbarcompat.shareactionprovider/MainActivity.java index b8cc900b5..545764cad 100644 --- a/samples/browseable/ActionBarCompat-ShareActionProvider/src/com.example.android.actionbarcompat.shareactionprovider/MainActivity.java +++ b/samples/browseable/ActionBarCompat-ShareActionProvider/src/com.example.android.actionbarcompat.shareactionprovider/MainActivity.java @@ -83,6 +83,10 @@ public class MainActivity extends ActionBarActivity { // Now get the ShareActionProvider from the item mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem); + // Get the ViewPager's current item position and set its ShareIntent. + int currentViewPagerItem = ((ViewPager) findViewById(R.id.viewpager)).getCurrentItem(); + setShareIntent(currentViewPagerItem); + return super.onCreateOptionsMenu(menu); } // END_INCLUDE(get_sap) @@ -151,6 +155,19 @@ public class MainActivity extends ActionBarActivity { } }; + private void setShareIntent(int position) { + // BEGIN_INCLUDE(update_sap) + if (mShareActionProvider != null) { + // Get the currently selected item, and retrieve it's share intent + ContentItem item = mItems.get(position); + Intent shareIntent = item.getShareIntent(MainActivity.this); + + // Now update the ShareActionProvider with the new share intent + mShareActionProvider.setShareIntent(shareIntent); + } + // END_INCLUDE(update_sap) + } + /** * A OnPageChangeListener used to update the ShareActionProvider's share intent when a new item * is selected in the ViewPager. @@ -165,16 +182,7 @@ public class MainActivity extends ActionBarActivity { @Override public void onPageSelected(int position) { - // BEGIN_INCLUDE(update_sap) - if (mShareActionProvider != null) { - // Get the currently selected item, and retrieve it's share intent - ContentItem item = mItems.get(position); - Intent shareIntent = item.getShareIntent(MainActivity.this); - - // Now update the ShareActionProvider with the new share intent - mShareActionProvider.setShareIntent(shareIntent); - } - // END_INCLUDE(update_sap) + setShareIntent(position); } @Override diff --git a/samples/browseable/ActionBarCompat-Styled/res/values-v21/base-colors.xml b/samples/browseable/ActionBarCompat-Styled/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/ActionBarCompat-Styled/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/samples/browseable/ActionBarCompat-Styled/res/values-v21/base-template-styles.xml b/samples/browseable/ActionBarCompat-Styled/res/values-v21/base-template-styles.xml new file mode 100644 index 000000000..0b2948f7e --- /dev/null +++ b/samples/browseable/ActionBarCompat-Styled/res/values-v21/base-template-styles.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/samples/browseable/ActivityInstrumentation/res/values-v21/base-colors.xml b/samples/browseable/ActivityInstrumentation/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/ActivityInstrumentation/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/samples/browseable/ActivityInstrumentation/res/values-v21/base-template-styles.xml b/samples/browseable/ActivityInstrumentation/res/values-v21/base-template-styles.xml new file mode 100644 index 000000000..0b2948f7e --- /dev/null +++ b/samples/browseable/ActivityInstrumentation/res/values-v21/base-template-styles.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/samples/browseable/ActivityInstrumentation/res/values-v21/template-styles.xml b/samples/browseable/ActivityInstrumentation/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/ActivityInstrumentation/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/ActivitySceneTransitionBasic/res/values-v21/styles.xml b/samples/browseable/ActivitySceneTransitionBasic/res/values-v21/styles.xml index fd212b395..4fee48dd8 100644 --- a/samples/browseable/ActivitySceneTransitionBasic/res/values-v21/styles.xml +++ b/samples/browseable/ActivitySceneTransitionBasic/res/values-v21/styles.xml @@ -17,9 +17,6 @@ + + diff --git a/samples/browseable/AdapterTransition/res/values-v21/template-styles.xml b/samples/browseable/AdapterTransition/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/AdapterTransition/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/AdvancedImmersiveMode/res/values-v21/template-styles.xml b/samples/browseable/AdvancedImmersiveMode/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/AdvancedImmersiveMode/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/AgendaData/Application/res/values-v21/template-styles.xml b/samples/browseable/AgendaData/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/AgendaData/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/AppRestrictionEnforcer/res/values-v21/template-styles.xml b/samples/browseable/AppRestrictionEnforcer/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/AppRestrictionEnforcer/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/AppRestrictionSchema/res/values-v21/template-styles.xml b/samples/browseable/AppRestrictionSchema/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/AppRestrictionSchema/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/AppRestrictions/res/values-v21/template-styles.xml b/samples/browseable/AppRestrictions/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/AppRestrictions/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicAccessibility/res/values-v21/template-styles.xml b/samples/browseable/BasicAccessibility/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicAccessibility/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicAndroidKeyStore/res/values-v21/template-styles.xml b/samples/browseable/BasicAndroidKeyStore/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicAndroidKeyStore/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicContactables/res/values-v21/template-styles.xml b/samples/browseable/BasicContactables/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicContactables/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicGestureDetect/res/values-v21/template-styles.xml b/samples/browseable/BasicGestureDetect/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicGestureDetect/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicImmersiveMode/res/values-v21/template-styles.xml b/samples/browseable/BasicImmersiveMode/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicImmersiveMode/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicManagedProfile/res/values-v21/template-styles.xml b/samples/browseable/BasicManagedProfile/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicManagedProfile/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicMediaDecoder/res/values-v21/template-styles.xml b/samples/browseable/BasicMediaDecoder/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicMediaDecoder/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicMediaRouter/res/values-v21/template-styles.xml b/samples/browseable/BasicMediaRouter/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicMediaRouter/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicMultitouch/res/values-v21/template-styles.xml b/samples/browseable/BasicMultitouch/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicMultitouch/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicNetworking/res/values-v21/template-styles.xml b/samples/browseable/BasicNetworking/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicNetworking/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicNotifications/res/values-v21/template-styles.xml b/samples/browseable/BasicNotifications/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicNotifications/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicRenderScript/res/values-v21/template-styles.xml b/samples/browseable/BasicRenderScript/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicRenderScript/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicSyncAdapter/res/values-v21/template-styles.xml b/samples/browseable/BasicSyncAdapter/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicSyncAdapter/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BasicTransition/res/values-v21/template-styles.xml b/samples/browseable/BasicTransition/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BasicTransition/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BatchStepSensor/res/values-v21/template-styles.xml b/samples/browseable/BatchStepSensor/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BatchStepSensor/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BluetoothChat/res/values-v21/template-styles.xml b/samples/browseable/BluetoothChat/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BluetoothChat/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BluetoothLeGatt/res/values-v21/template-styles.xml b/samples/browseable/BluetoothLeGatt/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BluetoothLeGatt/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/BorderlessButtons/res/values-v21/template-styles.xml b/samples/browseable/BorderlessButtons/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/BorderlessButtons/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Camera2Basic/res/values-v21/template-styles.xml b/samples/browseable/Camera2Basic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Camera2Basic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Camera2Video/res/values-v21/template-styles.xml b/samples/browseable/Camera2Video/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Camera2Video/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/CardEmulation/res/values-v21/template-styles.xml b/samples/browseable/CardEmulation/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/CardEmulation/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/CardReader/res/values-v21/template-styles.xml b/samples/browseable/CardReader/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/CardReader/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/CardView/res/values-v21/template-styles.xml b/samples/browseable/CardView/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/CardView/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/ClippingBasic/res/values-v21/template-styles.xml b/samples/browseable/ClippingBasic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/ClippingBasic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/CustomChoiceList/res/values-v21/template-styles.xml b/samples/browseable/CustomChoiceList/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/CustomChoiceList/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/CustomNotifications/res/values-v21/template-styles.xml b/samples/browseable/CustomNotifications/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/CustomNotifications/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/CustomTransition/res/values-v21/template-styles.xml b/samples/browseable/CustomTransition/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/CustomTransition/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DataLayer/Application/res/values-v21/template-styles.xml b/samples/browseable/DataLayer/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DataLayer/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DelayedConfirmation/Application/res/values-v21/template-styles.xml b/samples/browseable/DelayedConfirmation/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DelayedConfirmation/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DisplayingBitmaps/res/values-v21/template-styles.xml b/samples/browseable/DisplayingBitmaps/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DisplayingBitmaps/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DocumentCentricApps/res/values-v21/template-styles.xml b/samples/browseable/DocumentCentricApps/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DocumentCentricApps/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DocumentCentricRelinquishIdentity/res/values-v21/template-styles.xml b/samples/browseable/DocumentCentricRelinquishIdentity/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DocumentCentricRelinquishIdentity/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DoneBar/res/values-v21/template-styles.xml b/samples/browseable/DoneBar/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DoneBar/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/DrawableTinting/res/values-v21/template-styles.xml b/samples/browseable/DrawableTinting/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/DrawableTinting/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/ElevationBasic/res/values-v21/template-styles.xml b/samples/browseable/ElevationBasic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/ElevationBasic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/ElevationDrag/res/values-v21/template-styles.xml b/samples/browseable/ElevationDrag/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/ElevationDrag/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/ElizaChat/Application/res/values-v21/template-styles.xml b/samples/browseable/ElizaChat/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/ElizaChat/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/EmbeddedApp/Application/res/values-v21/template-styles.xml b/samples/browseable/EmbeddedApp/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/EmbeddedApp/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/FindMyPhone/Application/res/values-v21/template-styles.xml b/samples/browseable/FindMyPhone/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/FindMyPhone/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Flashlight/Application/res/values-v21/template-styles.xml b/samples/browseable/Flashlight/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Flashlight/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/FloatingActionButtonBasic/res/values-v21/template-styles.xml b/samples/browseable/FloatingActionButtonBasic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/FloatingActionButtonBasic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/FragmentTransition/res/values-v21/template-styles.xml b/samples/browseable/FragmentTransition/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/FragmentTransition/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Geofencing/Application/res/values-v21/template-styles.xml b/samples/browseable/Geofencing/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Geofencing/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/GridViewPager/Application/res/values-v21/template-styles.xml b/samples/browseable/GridViewPager/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/GridViewPager/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/HdrViewfinder/res/values-v21/template-styles.xml b/samples/browseable/HdrViewfinder/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/HdrViewfinder/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/HorizontalPaging/res/values-v21/template-styles.xml b/samples/browseable/HorizontalPaging/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/HorizontalPaging/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/ImmersiveMode/res/values-v21/template-styles.xml b/samples/browseable/ImmersiveMode/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/ImmersiveMode/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Interpolator/res/values-v21/template-styles.xml b/samples/browseable/Interpolator/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Interpolator/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/JobScheduler/res/values-v21/template-styles.xml b/samples/browseable/JobScheduler/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/JobScheduler/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/JumpingJack/Application/res/values-v21/template-styles.xml b/samples/browseable/JumpingJack/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/JumpingJack/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/LNotifications/res/values-v21/template-styles.xml b/samples/browseable/LNotifications/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/LNotifications/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/MediaEffects/res/values-v21/template-styles.xml b/samples/browseable/MediaEffects/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/MediaEffects/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/MediaRecorder/res/values-v21/template-styles.xml b/samples/browseable/MediaRecorder/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/MediaRecorder/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/MediaRouter/res/values-v21/template-styles.xml b/samples/browseable/MediaRouter/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/MediaRouter/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/NavigationDrawer/res/values-v21/template-styles.xml b/samples/browseable/NavigationDrawer/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/NavigationDrawer/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/NetworkConnect/res/values-v21/template-styles.xml b/samples/browseable/NetworkConnect/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/NetworkConnect/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Notifications/Application/res/values-v21/template-styles.xml b/samples/browseable/Notifications/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Notifications/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/PdfRendererBasic/res/values-v21/template-styles.xml b/samples/browseable/PdfRendererBasic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/PdfRendererBasic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/Quiz/Application/res/values-v21/template-styles.xml b/samples/browseable/Quiz/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/Quiz/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/RecipeAssistant/Application/res/values-v21/template-styles.xml b/samples/browseable/RecipeAssistant/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/RecipeAssistant/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/RecyclerView/res/values-v21/template-styles.xml b/samples/browseable/RecyclerView/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/RecyclerView/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/RenderScriptIntrinsic/res/values-v21/template-styles.xml b/samples/browseable/RenderScriptIntrinsic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/RenderScriptIntrinsic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/RepeatingAlarm/res/values-v21/template-styles.xml b/samples/browseable/RepeatingAlarm/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/RepeatingAlarm/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/RevealEffectBasic/res/values-v21/template-styles.xml b/samples/browseable/RevealEffectBasic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/RevealEffectBasic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/SkeletonWearableApp/Application/res/values-v21/template-styles.xml b/samples/browseable/SkeletonWearableApp/Application/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/SkeletonWearableApp/Application/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/SlidingTabsBasic/res/values-v21/template-styles.xml b/samples/browseable/SlidingTabsBasic/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/SlidingTabsBasic/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + diff --git a/samples/browseable/SlidingTabsColors/res/values-v21/template-styles.xml b/samples/browseable/SlidingTabsColors/res/values-v21/template-styles.xml deleted file mode 100644 index 134fcd9d3..000000000 --- a/samples/browseable/SlidingTabsColors/res/values-v21/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - + + 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/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/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/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/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/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/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/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/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/ActionBarCompat-Basic/res/values-v21/template-styles.xml b/samples/browseable/WatchFace/Application/res/values-v11/template-styles.xml similarity index 83% rename from samples/browseable/ActionBarCompat-Basic/res/values-v21/template-styles.xml rename to samples/browseable/WatchFace/Application/res/values-v11/template-styles.xml index 134fcd9d3..8c1ea66f2 100644 --- a/samples/browseable/ActionBarCompat-Basic/res/values-v21/template-styles.xml +++ b/samples/browseable/WatchFace/Application/res/values-v11/template-styles.xml @@ -1,5 +1,5 @@ - + + 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/ActionBarCompat-Styled/res/values-v21/template-styles.xml b/samples/browseable/WatchFace/Application/res/values/template-dimens.xml similarity index 52% rename from samples/browseable/ActionBarCompat-Styled/res/values-v21/template-styles.xml rename to samples/browseable/WatchFace/Application/res/values/template-dimens.xml index 134fcd9d3..39e710b5c 100644 --- a/samples/browseable/ActionBarCompat-Styled/res/values-v21/template-styles.xml +++ b/samples/browseable/WatchFace/Application/res/values/template-dimens.xml @@ -1,5 +1,5 @@ - + + + + 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:

    + *
  1. red + *
  2. green + *
  3. blue + *
  4. 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 @@ - - - - - -