aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--admin/AppRestrictionEnforcer/Application/src/main/java/com/example/android/apprestrictionenforcer/AppRestrictionEnforcerFragment.java28
-rw-r--r--admin/AppRestrictionEnforcer/Application/src/main/res/layout/fragment_app_restriction_enforcer.xml2
-rw-r--r--admin/AppRestrictionEnforcer/template-params.xml2
-rw-r--r--admin/AppRestrictionSchema/Application/src/main/java/com/example/android/apprestrictionschema/AppRestrictionSchemaFragment.java22
-rw-r--r--admin/AppRestrictionSchema/Application/src/main/res/layout/fragment_app_restriction_schema.xml4
-rw-r--r--admin/AppRestrictionSchema/template-params.xml2
-rw-r--r--build.gradle1
-rw-r--r--content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java2
-rw-r--r--content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/StorageProviderFragment.java (renamed from content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudFragment.java)4
-rw-r--r--content/documentsUi/StorageProvider/README.md4
-rw-r--r--media/Camera2Basic/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java100
-rw-r--r--media/Camera2Basic/README.md2
-rw-r--r--media/Camera2Raw/Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java70
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java118
-rw-r--r--security/AsymmetricFingerprintDialog/template-params.xml10
-rw-r--r--security/ConfirmCredential/template-params.xml8
-rw-r--r--security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java119
-rw-r--r--security/FingerprintDialog/template-params.xml14
-rw-r--r--security/keystore/BasicAndroidKeyStore/Application/src/main/java/com/example/android/basicandroidkeystore/BasicAndroidKeyStoreFragment.java5
-rw-r--r--wearable/wear/AgendaData/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/AgendaData/template-params.xml3
-rw-r--r--wearable/wear/AlwaysOn/template-params.xml8
-rw-r--r--wearable/wear/DataLayer/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/DataLayer/Wearable/src/main/java/com/example/android/wearable/datalayer/MainActivity.java113
-rw-r--r--wearable/wear/DataLayer/template-params.xml3
-rw-r--r--wearable/wear/DelayedConfirmation/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/DelayedConfirmation/template-params.xml3
-rw-r--r--wearable/wear/ElizaChat/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/ElizaChat/Application/src/main/java/com/example/android/wearable/elizachat/ResponderService.java2
-rw-r--r--wearable/wear/ElizaChat/template-params.xml2
-rw-r--r--wearable/wear/EmbeddedApp/LICENSE452
-rw-r--r--wearable/wear/EmbeddedApp/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/EmbeddedApp/template-params.xml3
-rw-r--r--wearable/wear/FindMyPhone/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/FindMyPhone/template-params.xml3
-rw-r--r--wearable/wear/Flashlight/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Flashlight/template-params.xml6
-rw-r--r--wearable/wear/Geofencing/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Geofencing/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Geofencing/template-params.xml3
-rw-r--r--wearable/wear/GridViewPager/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/GridViewPager/template-params.xml3
-rw-r--r--wearable/wear/JumpingJack/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/JumpingJack/template-params.xml5
-rw-r--r--wearable/wear/Notifications/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Notifications/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Notifications/template-params.xml3
-rw-r--r--wearable/wear/Quiz/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Quiz/template-params.xml3
-rw-r--r--wearable/wear/RecipeAssistant/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/RecipeAssistant/template-params.xml2
-rw-r--r--wearable/wear/SkeletonWearableApp/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/SkeletonWearableApp/template-params.xml3
-rw-r--r--wearable/wear/SpeedTracker/Application/src/main/AndroidManifest.xml24
-rw-r--r--wearable/wear/SpeedTracker/Application/src/main/java/com/example/android/wearable/speedtracker/PhoneMainActivity.java5
-rw-r--r--wearable/wear/SpeedTracker/Application/src/main/res/layout/main_activity.xml3
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/AndroidManifest.xml15
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/SpeedPickerActivity.java16
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/WearableMainActivity.java447
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/LocationSettingActivity.java75
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/SpeedPickerListAdapter.java3
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/res/layout/main_activity.xml18
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/res/layout/saving_activity.xml63
-rw-r--r--wearable/wear/SpeedTracker/Wearable/src/main/res/values/strings.xml11
-rw-r--r--wearable/wear/SpeedTracker/template-params.xml8
-rw-r--r--wearable/wear/SynchronizedNotifications/Application/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/SynchronizedNotifications/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/SynchronizedNotifications/template-params.xml3
-rw-r--r--wearable/wear/Timer/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/Timer/template-params.xml3
-rw-r--r--wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml20
-rw-r--r--wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceConfigActivity.java255
-rw-r--r--wearable/wear/WatchFace/Application/src/main/res/layout/activity_fit_watch_face_config.xml33
-rw-r--r--wearable/wear/WatchFace/Application/src/main/res/values-w820dp/dimens.xml6
-rw-r--r--wearable/wear/WatchFace/Application/src/main/res/values/dimens.xml8
-rw-r--r--wearable/wear/WatchFace/Application/src/main/res/values/strings.xml2
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml245
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFacePermissionActivity.java56
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java96
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceService.java533
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitStepsWatchFaceService.java542
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.pngbin0 -> 306 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance.pngbin0 -> 7021 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance_circular.pngbin0 -> 6052 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit.pngbin0 -> 12725 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit_circular.pngbin0 -> 12623 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.pngbin0 -> 200 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.pngbin0 -> 354 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.pngbin0 -> 512 bytes
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_calendar_watch_face_permission.xml58
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml11
-rw-r--r--wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml11
-rw-r--r--wearable/wear/WatchFace/template-params.xml14
-rw-r--r--wearable/wear/WatchViewStub/Wearable/src/main/AndroidManifest.xml2
-rw-r--r--wearable/wear/WatchViewStub/template-params.xml3
-rw-r--r--wearable/wear/WearSpeakerSample/CONTRIBUTING34
-rw-r--r--wearable/wear/WearSpeakerSample/build.gradle50
-rw-r--r--wearable/wear/WearSpeakerSample/buildSrc/build.gradle15
-rw-r--r--wearable/wear/WearSpeakerSample/gradle.properties18
-rw-r--r--wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.jarbin0 -> 49896 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xwearable/wear/WearSpeakerSample/gradlew164
-rw-r--r--wearable/wear/WearSpeakerSample/gradlew.bat90
-rw-r--r--wearable/wear/WearSpeakerSample/screenshots/1.pngbin0 -> 56562 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/screenshots/2.pngbin0 -> 57770 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/screenshots/3.pngbin0 -> 56603 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/screenshots/4.pngbin0 -> 56457 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/settings.gradle17
-rw-r--r--wearable/wear/WearSpeakerSample/template-params.xml84
-rw-r--r--wearable/wear/WearSpeakerSample/wear/.gitignore1
-rw-r--r--wearable/wear/WearSpeakerSample/wear/build.gradle44
-rw-r--r--wearable/wear/WearSpeakerSample/wear/proguard-rules.pro17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/AndroidManifest.xml41
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java293
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java263
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/UIAnimation.java220
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/circle.xml21
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_120dp.xml17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_32dp.xml17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_120dp.xml17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_32dp.xml17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_120dp.xml17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_32dp.xml17
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/layout/main_activity.xml89
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3418 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2206 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4842 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 7718 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/raw/sound.mp3bin0 -> 366464 bytes
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/values/colors.xml21
-rw-r--r--wearable/wear/WearSpeakerSample/wear/src/main/res/values/strings.xml18
-rw-r--r--wearable/wear/XYZTouristAttractions/template-params.xml1
132 files changed, 4633 insertions, 740 deletions
diff --git a/admin/AppRestrictionEnforcer/Application/src/main/java/com/example/android/apprestrictionenforcer/AppRestrictionEnforcerFragment.java b/admin/AppRestrictionEnforcer/Application/src/main/java/com/example/android/apprestrictionenforcer/AppRestrictionEnforcerFragment.java
index e30a9a46..361c4ac3 100644
--- a/admin/AppRestrictionEnforcer/Application/src/main/java/com/example/android/apprestrictionenforcer/AppRestrictionEnforcerFragment.java
+++ b/admin/AppRestrictionEnforcer/Application/src/main/java/com/example/android/apprestrictionenforcer/AppRestrictionEnforcerFragment.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.RestrictionEntry;
import android.content.RestrictionsManager;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
@@ -105,6 +106,8 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
private static final String DELIMETER = ",";
private static final String SEPARATOR = ":";
+ private static final boolean BUNDLE_SUPPORTED = Build.VERSION.SDK_INT >= 23;
+
/**
* Current status of the restrictions.
*/
@@ -138,6 +141,15 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
mEditProfileAge = (EditText) view.findViewById(R.id.profile_age);
mLayoutItems = (LinearLayout) view.findViewById(R.id.items);
view.findViewById(R.id.item_add).setOnClickListener(this);
+ View bundleLayout = view.findViewById(R.id.bundle_layout);
+ View bundleArrayLayout = view.findViewById(R.id.bundle_array_layout);
+ if (BUNDLE_SUPPORTED) {
+ bundleLayout.setVisibility(View.VISIBLE);
+ bundleArrayLayout.setVisibility(View.VISIBLE);
+ } else {
+ bundleLayout.setVisibility(View.GONE);
+ bundleArrayLayout.setVisibility(View.GONE);
+ }
}
@Override
@@ -280,7 +292,7 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
TextUtils.join(DELIMETER,
restriction.getAllSelectedStrings())),
DELIMETER));
- } else if (RESTRICTION_KEY_PROFILE.equals(key)) {
+ } else if (BUNDLE_SUPPORTED && RESTRICTION_KEY_PROFILE.equals(key)) {
String name = null;
int age = 0;
for (RestrictionEntry entry : restriction.getRestrictions()) {
@@ -294,7 +306,7 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
name = prefs.getString(RESTRICTION_KEY_PROFILE_NAME, name);
age = prefs.getInt(RESTRICTION_KEY_PROFILE_AGE, age);
updateProfile(name, age);
- } else if (RESTRICTION_KEY_ITEMS.equals(key)) {
+ } else if (BUNDLE_SUPPORTED && RESTRICTION_KEY_ITEMS.equals(key)) {
String itemsString = prefs.getString(RESTRICTION_KEY_ITEMS, "");
HashMap<String, String> items = new HashMap<>();
for (String itemString : TextUtils.split(itemsString, DELIMETER)) {
@@ -351,6 +363,9 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
}
private void updateProfile(String name, int age) {
+ if (!BUNDLE_SUPPORTED) {
+ return;
+ }
Bundle profile = new Bundle();
profile.putString(RESTRICTION_KEY_PROFILE_NAME, name);
profile.putInt(RESTRICTION_KEY_PROFILE_AGE, age);
@@ -364,6 +379,9 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
}
private void updateItems(Context context, Map<String, String> items) {
+ if (!BUNDLE_SUPPORTED) {
+ return;
+ }
mCurrentRestrictions.putParcelableArray(RESTRICTION_KEY_ITEMS, convertToBundles(items));
LayoutInflater inflater = LayoutInflater.from(context);
mLayoutItems.removeAllViews();
@@ -500,6 +518,9 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
* @param age The value to be set for the "age" field.
*/
private void saveProfile(Activity activity, String name, int age) {
+ if (!BUNDLE_SUPPORTED) {
+ return;
+ }
Bundle profile = new Bundle();
profile.putString(RESTRICTION_KEY_PROFILE_NAME, name);
profile.putInt(RESTRICTION_KEY_PROFILE_AGE, age);
@@ -515,6 +536,9 @@ public class AppRestrictionEnforcerFragment extends Fragment implements
* @param items The values.
*/
private void saveItems(Activity activity, Map<String, String> items) {
+ if (!BUNDLE_SUPPORTED) {
+ return;
+ }
mCurrentRestrictions.putParcelableArray(RESTRICTION_KEY_ITEMS, convertToBundles(items));
saveRestrictions(activity);
StringBuilder builder = new StringBuilder();
diff --git a/admin/AppRestrictionEnforcer/Application/src/main/res/layout/fragment_app_restriction_enforcer.xml b/admin/AppRestrictionEnforcer/Application/src/main/res/layout/fragment_app_restriction_enforcer.xml
index 56c9133a..b6839897 100644
--- a/admin/AppRestrictionEnforcer/Application/src/main/res/layout/fragment_app_restriction_enforcer.xml
+++ b/admin/AppRestrictionEnforcer/Application/src/main/res/layout/fragment_app_restriction_enforcer.xml
@@ -120,6 +120,7 @@
</LinearLayout>
<LinearLayout
+ android:id="@+id/bundle_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
@@ -147,6 +148,7 @@
</LinearLayout>
<RelativeLayout
+ android:id="@+id/bundle_array_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
diff --git a/admin/AppRestrictionEnforcer/template-params.xml b/admin/AppRestrictionEnforcer/template-params.xml
index 3fe913d8..eee3d7ec 100644
--- a/admin/AppRestrictionEnforcer/template-params.xml
+++ b/admin/AppRestrictionEnforcer/template-params.xml
@@ -22,7 +22,7 @@
<group>Admin</group>
<package>com.example.android.apprestrictionenforcer</package>
- <minSdk>23</minSdk>
+ <minSdk>21</minSdk>
<strings>
<intro>
diff --git a/admin/AppRestrictionSchema/Application/src/main/java/com/example/android/apprestrictionschema/AppRestrictionSchemaFragment.java b/admin/AppRestrictionSchema/Application/src/main/java/com/example/android/apprestrictionschema/AppRestrictionSchemaFragment.java
index ea1aad85..bbb1ef86 100644
--- a/admin/AppRestrictionSchema/Application/src/main/java/com/example/android/apprestrictionschema/AppRestrictionSchemaFragment.java
+++ b/admin/AppRestrictionSchema/Application/src/main/java/com/example/android/apprestrictionschema/AppRestrictionSchemaFragment.java
@@ -19,6 +19,7 @@ package com.example.android.apprestrictionschema;
import android.content.Context;
import android.content.RestrictionEntry;
import android.content.RestrictionsManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
@@ -57,6 +58,8 @@ public class AppRestrictionSchemaFragment extends Fragment implements View.OnCli
private static final String KEY_ITEM_KEY = "key";
private static final String KEY_ITEM_VALUE = "value";
+ private static final boolean BUNDLE_SUPPORTED = Build.VERSION.SDK_INT >= 23;
+
// Message to show when the button is clicked (String restriction)
private String mMessage;
@@ -82,9 +85,22 @@ public class AppRestrictionSchemaFragment extends Fragment implements View.OnCli
mTextNumber = (TextView) view.findViewById(R.id.your_number);
mTextRank = (TextView) view.findViewById(R.id.your_rank);
mTextApprovals = (TextView) view.findViewById(R.id.approvals_you_have);
+ View bundleSeparator = view.findViewById(R.id.bundle_separator);
mTextProfile = (TextView) view.findViewById(R.id.your_profile);
+ View bundleArraySeparator = view.findViewById(R.id.bundle_array_separator);
mTextItems = (TextView) view.findViewById(R.id.your_items);
mButtonSayHello.setOnClickListener(this);
+ if (BUNDLE_SUPPORTED) {
+ bundleSeparator.setVisibility(View.VISIBLE);
+ mTextProfile.setVisibility(View.VISIBLE);
+ bundleArraySeparator.setVisibility(View.VISIBLE);
+ mTextItems.setVisibility(View.VISIBLE);
+ } else {
+ bundleSeparator.setVisibility(View.GONE);
+ mTextProfile.setVisibility(View.GONE);
+ bundleArraySeparator.setVisibility(View.GONE);
+ mTextItems.setVisibility(View.GONE);
+ }
}
@Override
@@ -178,6 +194,9 @@ public class AppRestrictionSchemaFragment extends Fragment implements View.OnCli
}
private void updateProfile(RestrictionEntry entry, Bundle restrictions) {
+ if (!BUNDLE_SUPPORTED) {
+ return;
+ }
String name = null;
int age = 0;
if (restrictions == null || !restrictions.containsKey(KEY_PROFILE)) {
@@ -201,6 +220,9 @@ public class AppRestrictionSchemaFragment extends Fragment implements View.OnCli
}
private void updateItems(RestrictionEntry entry, Bundle restrictions) {
+ if (!BUNDLE_SUPPORTED) {
+ return;
+ }
StringBuilder builder = new StringBuilder();
if (restrictions != null) {
Parcelable[] parcelables = restrictions.getParcelableArray(KEY_ITEMS);
diff --git a/admin/AppRestrictionSchema/Application/src/main/res/layout/fragment_app_restriction_schema.xml b/admin/AppRestrictionSchema/Application/src/main/res/layout/fragment_app_restriction_schema.xml
index 85708691..02d83e61 100644
--- a/admin/AppRestrictionSchema/Application/src/main/res/layout/fragment_app_restriction_schema.xml
+++ b/admin/AppRestrictionSchema/Application/src/main/res/layout/fragment_app_restriction_schema.xml
@@ -59,7 +59,7 @@ limitations under the License.
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="@string/your_rank"/>
- <include layout="@layout/separator"/>
+ <include layout="@layout/separator" android:id="@+id/bundle_separator"/>
<TextView
android:id="@+id/approvals_you_have"
@@ -77,7 +77,7 @@ limitations under the License.
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="@string/your_profile"/>
- <include layout="@layout/separator"/>
+ <include layout="@layout/separator" android:id="@+id/bundle_array_separator" />
<TextView
android:id="@+id/your_items"
diff --git a/admin/AppRestrictionSchema/template-params.xml b/admin/AppRestrictionSchema/template-params.xml
index 3e7a2024..508393ec 100644
--- a/admin/AppRestrictionSchema/template-params.xml
+++ b/admin/AppRestrictionSchema/template-params.xml
@@ -20,7 +20,7 @@
<group>Admin</group>
<package>com.example.android.apprestrictionschema</package>
- <minSdk>23</minSdk>
+ <minSdk>21</minSdk>
<strings>
<intro>
diff --git a/build.gradle b/build.gradle
index a4be013b..7735d233 100644
--- a/build.gradle
+++ b/build.gradle
@@ -110,6 +110,7 @@ List<String> samples = [
"media/MidiScope",
"media/MidiSynth",
"security/AsymmetricFingerprintDialog",
+"wearable/wear/WearSpeakerSample",
]
List<String> taskNames = [
diff --git a/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java b/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java
index d8be8138..9f9249a3 100644
--- a/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java
+++ b/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java
@@ -51,7 +51,7 @@ import java.util.Set;
* Manages documents and exposes them to the Android system for sharing.
*/
public class MyCloudProvider extends DocumentsProvider {
- private static final String TAG = MyCloudProvider.class.getSimpleName();
+ private static final String TAG = "MyCloudProvider";
// Use these as the default columns to return information about a root if no specific
// columns are requested in a query.
diff --git a/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudFragment.java b/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/StorageProviderFragment.java
index f624e908..80d0296d 100644
--- a/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudFragment.java
+++ b/content/documentsUi/StorageProvider/Application/src/main/java/com/example/android/storageprovider/StorageProviderFragment.java
@@ -31,9 +31,9 @@ import com.example.android.common.logger.Log;
* Toggles the user's login status via a login menu option, and enables/disables the cloud storage
* content provider.
*/
-public class MyCloudFragment extends Fragment {
+public class StorageProviderFragment extends Fragment {
- private static final String TAG = "MyCloudFragment";
+ private static final String TAG = "StorageProviderFragment";
private static final String AUTHORITY = "com.example.android.storageprovider.documents";
private boolean mLoggedIn = false;
diff --git a/content/documentsUi/StorageProvider/README.md b/content/documentsUi/StorageProvider/README.md
index 9040e7b3..bc343bcd 100644
--- a/content/documentsUi/StorageProvider/README.md
+++ b/content/documentsUi/StorageProvider/README.md
@@ -1,5 +1,5 @@
-Android MyCloud Sample
+Android StorageProvider Sample
===================================
This sample shows how to implement a simple documents provider using the storage access
@@ -42,7 +42,7 @@ Support
- Stack Overflow: http://stackoverflow.com/questions/tagged/android
If you've found an error in this sample, please file an issue:
-https://github.com/googlesamples/android-MyCloud
+https://github.com/googlesamples/android-StorageProvider
Patches are encouraged, and may be submitted by forking this project and
submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details.
diff --git a/media/Camera2Basic/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java b/media/Camera2Basic/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java
index 4bf85343..c2b99bc4 100644
--- a/media/Camera2Basic/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java
+++ b/media/Camera2Basic/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java
@@ -28,6 +28,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
@@ -117,6 +118,16 @@ public class Camera2BasicFragment extends Fragment
private static final int STATE_PICTURE_TAKEN = 4;
/**
+ * Max preview width that is guaranteed by Camera2 API
+ */
+ private static final int MAX_PREVIEW_WIDTH = 1920;
+
+ /**
+ * Max preview height that is guaranteed by Camera2 API
+ */
+ private static final int MAX_PREVIEW_HEIGHT = 1080;
+
+ /**
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {@link TextureView}.
*/
@@ -344,31 +355,48 @@ public class Camera2BasicFragment extends Fragment
}
/**
- * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
- * width and height are at least as large as the respective requested values, and whose aspect
- * ratio matches with the specified value.
+ * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
+ * is at least as large as the respective texture view size, and that is at most as large as the
+ * respective max size, and whose aspect ratio matches with the specified value. If such size
+ * doesn't exist, choose the largest one that is at most as large as the respective max size,
+ * and whose aspect ratio matches with the specified value.
*
- * @param choices The list of sizes that the camera supports for the intended output class
- * @param width The minimum desired width
- * @param height The minimum desired height
- * @param aspectRatio The aspect ratio
+ * @param choices The list of sizes that the camera supports for the intended output
+ * class
+ * @param textureViewWidth The width of the texture view relative to sensor coordinate
+ * @param textureViewHeight The height of the texture view relative to sensor coordinate
+ * @param maxWidth The maximum width that can be chosen
+ * @param maxHeight The maximum height that can be chosen
+ * @param aspectRatio The aspect ratio
* @return The optimal {@code Size}, or an arbitrary one if none were big enough
*/
- private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
+ private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
+ int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
+
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
+ // Collect the supported resolutions that are smaller than the preview Surface
+ List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
- if (option.getHeight() == option.getWidth() * h / w &&
- option.getWidth() >= width && option.getHeight() >= height) {
- bigEnough.add(option);
+ if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
+ option.getHeight() == option.getWidth() * h / w) {
+ if (option.getWidth() >= textureViewWidth &&
+ option.getHeight() >= textureViewHeight) {
+ bigEnough.add(option);
+ } else {
+ notBigEnough.add(option);
+ }
}
}
- // Pick the smallest of those, assuming we found any
+ // Pick the smallest of those big enough. If there is no one big enough, pick the
+ // largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
+ } else if (notBigEnough.size() > 0) {
+ return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
@@ -478,11 +506,57 @@ public class Camera2BasicFragment extends Fragment
mImageReader.setOnImageAvailableListener(
mOnImageAvailableListener, mBackgroundHandler);
+ // Find out if we need to swap dimension to get the preview size relative to sensor
+ // coordinate.
+ int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+ int sensorOrientation =
+ characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ boolean swappedDimensions = false;
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ if (sensorOrientation == 90 || sensorOrientation == 270) {
+ swappedDimensions = true;
+ }
+ break;
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ if (sensorOrientation == 0 || sensorOrientation == 180) {
+ swappedDimensions = true;
+ }
+ break;
+ default:
+ Log.e(TAG, "Display rotation is invalid: " + displayRotation);
+ }
+
+ Point displaySize = new Point();
+ activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
+ int rotatedPreviewWidth = width;
+ int rotatedPreviewHeight = height;
+ int maxPreviewWidth = displaySize.x;
+ int maxPreviewHeight = displaySize.y;
+
+ if (swappedDimensions) {
+ rotatedPreviewWidth = height;
+ rotatedPreviewHeight = width;
+ maxPreviewWidth = displaySize.y;
+ maxPreviewHeight = displaySize.x;
+ }
+
+ if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
+ maxPreviewWidth = MAX_PREVIEW_WIDTH;
+ }
+
+ if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
+ maxPreviewHeight = MAX_PREVIEW_HEIGHT;
+ }
+
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
- width, height, largest);
+ rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
+ maxPreviewHeight, largest);
// We fit the aspect ratio of TextureView to the size of preview we picked.
int orientation = getResources().getConfiguration().orientation;
diff --git a/media/Camera2Basic/README.md b/media/Camera2Basic/README.md
index a77df09d..cee71783 100644
--- a/media/Camera2Basic/README.md
+++ b/media/Camera2Basic/README.md
@@ -43,7 +43,7 @@ Pre-requisites
--------------
- Android SDK v23
-- Android Build Tools v23.0.0
+- Android Build Tools v23.0.1
- Android Support Repository
Screenshots
diff --git a/media/Camera2Raw/Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java b/media/Camera2Raw/Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java
index 47cce388..bf5efe58 100644
--- a/media/Camera2Raw/Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java
+++ b/media/Camera2Raw/Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java
@@ -27,6 +27,7 @@ import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.SensorManager;
@@ -157,6 +158,16 @@ public class Camera2RawFragment extends Fragment
private static final double ASPECT_RATIO_TOLERANCE = 0.005;
/**
+ * Max preview width that is guaranteed by Camera2 API
+ */
+ private static final int MAX_PREVIEW_WIDTH = 1920;
+
+ /**
+ * Max preview height that is guaranteed by Camera2 API
+ */
+ private static final int MAX_PREVIEW_HEIGHT = 1080;
+
+ /**
* Tag for the {@link Log}.
*/
private static final String TAG = "Camera2RawFragment";
@@ -1033,6 +1044,8 @@ public class Camera2RawFragment extends Fragment
// Find the rotation of the device relative to the native device orientation.
int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+ Point displaySize = new Point();
+ activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
// Find the rotation of the device relative to the camera sensor's orientation.
int totalRotation = sensorToDeviceRotation(mCharacteristics, deviceRotation);
@@ -1042,14 +1055,29 @@ public class Camera2RawFragment extends Fragment
boolean swappedDimensions = totalRotation == 90 || totalRotation == 270;
int rotatedViewWidth = viewWidth;
int rotatedViewHeight = viewHeight;
+ int maxPreviewWidth = displaySize.x;
+ int maxPreviewHeight = displaySize.y;
+
if (swappedDimensions) {
rotatedViewWidth = viewHeight;
rotatedViewHeight = viewWidth;
+ maxPreviewWidth = displaySize.y;
+ maxPreviewHeight = displaySize.x;
+ }
+
+ // Preview should not be larger than display size and 1080p.
+ if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
+ maxPreviewWidth = MAX_PREVIEW_WIDTH;
+ }
+
+ if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
+ maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
// Find the best preview size for these view dimensions and configured JPEG size.
Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
- rotatedViewWidth, rotatedViewHeight, largestJpeg);
+ rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight,
+ largestJpeg);
if (swappedDimensions) {
mTextureView.setAspectRatio(
@@ -1580,31 +1608,47 @@ public class Camera2RawFragment extends Fragment
}
/**
- * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
- * width and height are at least as large as the respective requested values, and whose aspect
- * ratio matches with the specified value.
+ * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
+ * is at least as large as the respective texture view size, and that is at most as large as the
+ * respective max size, and whose aspect ratio matches with the specified value. If such size
+ * doesn't exist, choose the largest one that is at most as large as the respective max size,
+ * and whose aspect ratio matches with the specified value.
*
- * @param choices The list of sizes that the camera supports for the intended output class
- * @param width The minimum desired width
- * @param height The minimum desired height
- * @param aspectRatio The aspect ratio
+ * @param choices The list of sizes that the camera supports for the intended output
+ * class
+ * @param textureViewWidth The width of the texture view relative to sensor coordinate
+ * @param textureViewHeight The height of the texture view relative to sensor coordinate
+ * @param maxWidth The maximum width that can be chosen
+ * @param maxHeight The maximum height that can be chosen
+ * @param aspectRatio The aspect ratio
* @return The optimal {@code Size}, or an arbitrary one if none were big enough
*/
- private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
+ private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
+ int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
+ // Collect the supported resolutions that are smaller than the preview Surface
+ List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
- if (option.getHeight() == option.getWidth() * h / w &&
- option.getWidth() >= width && option.getHeight() >= height) {
- bigEnough.add(option);
+ if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
+ option.getHeight() == option.getWidth() * h / w) {
+ if (option.getWidth() >= textureViewWidth &&
+ option.getHeight() >= textureViewHeight) {
+ bigEnough.add(option);
+ } else {
+ notBigEnough.add(option);
+ }
}
}
- // Pick the smallest of those, assuming we found any
+ // Pick the smallest of those big enough. If there is no one big enough, pick the
+ // largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
+ } else if (notBigEnough.size() > 0) {
+ return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java
index 5086a173..26832f2c 100644
--- a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java
@@ -16,12 +16,10 @@
package com.example.android.asymmetricfingerprintdialog;
-import android.Manifest;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
@@ -59,8 +57,6 @@ public class MainActivity extends Activity {
/** Alias for our key in the Android Key Store */
public static final String KEY_NAME = "my_key";
- private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
-
@Inject KeyguardManager mKeyguardManager;
@Inject FingerprintManager mFingerprintManager;
@Inject FingerprintAuthenticationDialogFragment mFragment;
@@ -74,71 +70,63 @@ public class MainActivity extends Activity {
super.onCreate(savedInstanceState);
((InjectedApplication) getApplication()).inject(this);
- requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
- FINGERPRINT_PERMISSION_REQUEST_CODE);
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
- if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE
- && state[0] == PackageManager.PERMISSION_GRANTED) {
- setContentView(R.layout.activity_main);
- Button purchaseButton = (Button) findViewById(R.id.purchase_button);
- if (!mKeyguardManager.isKeyguardSecure()) {
- // Show a message that the user hasn't set up a fingerprint or lock screen.
- Toast.makeText(this,
- "Secure lock screen hasn't set up.\n"
- + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
- Toast.LENGTH_LONG).show();
- purchaseButton.setEnabled(false);
- return;
- }
- if (!mFingerprintManager.hasEnrolledFingerprints()) {
- purchaseButton.setEnabled(false);
- // This happens when no fingerprints are registered.
- Toast.makeText(this,
- "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint",
- Toast.LENGTH_LONG).show();
- return;
- }
- createKeyPair();
- purchaseButton.setEnabled(true);
- purchaseButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- findViewById(R.id.confirmation_message).setVisibility(View.GONE);
- findViewById(R.id.encrypted_message).setVisibility(View.GONE);
-
- // Set up the crypto object for later. The object will be authenticated by use
- // of the fingerprint.
- if (initSignature()) {
-
- // Show the fingerprint dialog. The user has the option to use the fingerprint with
- // crypto, or you can fall back to using a server-side verified password.
- mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mSignature));
- boolean useFingerprintPreference = mSharedPreferences
- .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
- true);
- if (useFingerprintPreference) {
- mFragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
- } else {
- mFragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
- }
- mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ setContentView(R.layout.activity_main);
+ Button purchaseButton = (Button) findViewById(R.id.purchase_button);
+ if (!mKeyguardManager.isKeyguardSecure()) {
+ // Show a message that the user hasn't set up a fingerprint or lock screen.
+ Toast.makeText(this,
+ "Secure lock screen hasn't set up.\n"
+ + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
+ Toast.LENGTH_LONG).show();
+ purchaseButton.setEnabled(false);
+ return;
+ }
+ //noinspection ResourceType
+ if (!mFingerprintManager.hasEnrolledFingerprints()) {
+ purchaseButton.setEnabled(false);
+ // This happens when no fingerprints are registered.
+ Toast.makeText(this,
+ "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ createKeyPair();
+ purchaseButton.setEnabled(true);
+ purchaseButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ findViewById(R.id.confirmation_message).setVisibility(View.GONE);
+ findViewById(R.id.encrypted_message).setVisibility(View.GONE);
+
+ // Set up the crypto object for later. The object will be authenticated by use
+ // of the fingerprint.
+ if (initSignature()) {
+
+ // Show the fingerprint dialog. The user has the option to use the fingerprint with
+ // crypto, or you can fall back to using a server-side verified password.
+ mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mSignature));
+ boolean useFingerprintPreference = mSharedPreferences
+ .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
+ true);
+ if (useFingerprintPreference) {
+ mFragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
} else {
- // This happens if the lock screen has been disabled or or a fingerprint got
- // enrolled. Thus show the dialog to authenticate with their password first
- // and ask the user if they want to authenticate with fingerprints in the
- // future
mFragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
- mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
}
+ mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ } else {
+ // This happens if the lock screen has been disabled or or a fingerprint got
+ // enrolled. Thus show the dialog to authenticate with their password first
+ // and ask the user if they want to authenticate with fingerprints in the
+ // future
+ mFragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
+ mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
}
- });
- }
+ }
+ });
}
/**
diff --git a/security/AsymmetricFingerprintDialog/template-params.xml b/security/AsymmetricFingerprintDialog/template-params.xml
index dfe47749..019a132c 100644
--- a/security/AsymmetricFingerprintDialog/template-params.xml
+++ b/security/AsymmetricFingerprintDialog/template-params.xml
@@ -20,7 +20,7 @@
E.g. Skipping device 'Nexus 5 - MNC', due to different API preview 'MNC' and 'android-MNC'
-->
<sample>
- <name>Asymmetric Fingerprint Dialog Sample</name>
+ <name>AsymmetricFingerprintDialog</name>
<group>Security</group>
<package>com.example.android.asymmetricfingerprintdialog</package>
@@ -90,7 +90,7 @@ in the way that its private key can only be used after the user has authenticate
and transmit the public key to your backend with the user verified password (In a real world, the
app should show proper UIs).
-By setting [KeyGeneratorSpec.Builder.setUserAuthenticationRequired][2] to true, you can permit the
+By setting [KeyGenParameterSpec.Builder.setUserAuthenticationRequired][2] to true, you can permit the
use of the key only after the user authenticate it including when authenticated with the user's
fingerprint.
@@ -105,10 +105,10 @@ Then you can verify the purchase transaction on server side with the public key
client, by verifying the piece of data signed by the Signature.
[1]: https://developer.android.com/reference/java/security/KeyPairGenerator.html
-[2]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.Builder#setUserAuthenticationRequired().html
-[3]: https://developer.android.com/reference/android/hardware/FingerprintManager#authenticate().html
+[2]: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder.html#setUserAuthenticationRequired%28boolean%29
+[3]: https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.html#authenticate%28android.hardware.fingerprint.FingerprintManager.CryptoObject,%20android.os.CancellationSignal,%20int,%20android.hardware.fingerprint.FingerprintManager.AuthenticationCallback,%20android.os.Handler%29
[4]: https://developer.android.com/reference/java/security/Signature.html
-[5]: https://developer.android.com/reference/android/hardware/FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded().html
+[5]: https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.AuthenticationCallback.html#onAuthenticationSucceeded%28android.hardware.fingerprint.FingerprintManager.AuthenticationResult%29
]]>
</intro>
</metadata>
diff --git a/security/ConfirmCredential/template-params.xml b/security/ConfirmCredential/template-params.xml
index 9877634b..d803a891 100644
--- a/security/ConfirmCredential/template-params.xml
+++ b/security/ConfirmCredential/template-params.xml
@@ -81,7 +81,7 @@ which can be only be used after the user has authenticated after the user is aut
with their device credentials and pass [KeyGenParameterSpec][2].
By setting an integer value to the
-[KeyGeneratorSpec.Builder.setUserAuthenticationValidityDurationSeconds][3], you can consider the
+[KeyGenParameterSpec.Builder.setUserAuthenticationValidityDurationSeconds][3], you can consider the
user as authenticated if the user has been authenticated with the device credentials
within the last x seconds.
@@ -89,9 +89,9 @@ Then by calling [KeyguardManager.createConfirmDeviceCredentialIntent][4], you ca
to confirm device credentials to the user.
[1]: https://developer.android.com/reference/javax/crypto/KeyGenerator.html
-[2]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.html
-[3]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds().html
-[4]: https://developer.android.com/reference/android/app/KeyguardManager.createConfirmDeviceCredentialIntent().html
+[2]: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
+[3]: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder.html#setUserAuthenticationValidityDurationSeconds%28int%29
+[4]: https://developer.android.com/reference/android/app/KeyguardManager.html#createConfirmDeviceCredentialIntent%28java.lang.CharSequence,%20java.lang.CharSequence%29
]]>
</intro>
</metadata>
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
index c954bfa7..7caf9e69 100644
--- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
@@ -16,12 +16,10 @@
package com.example.android.fingerprintdialog;
-import android.Manifest;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
@@ -64,8 +62,6 @@ public class MainActivity extends Activity {
/** Alias for our key in the Android Key Store */
private static final String KEY_NAME = "my_key";
- private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
-
@Inject KeyguardManager mKeyguardManager;
@Inject FingerprintManager mFingerprintManager;
@Inject FingerprintAuthenticationDialogFragment mFragment;
@@ -79,72 +75,65 @@ public class MainActivity extends Activity {
super.onCreate(savedInstanceState);
((InjectedApplication) getApplication()).inject(this);
- requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
- FINGERPRINT_PERMISSION_REQUEST_CODE);
- }
+ setContentView(R.layout.activity_main);
+ Button purchaseButton = (Button) findViewById(R.id.purchase_button);
+ if (!mKeyguardManager.isKeyguardSecure()) {
+ // Show a message that the user hasn't set up a fingerprint or lock screen.
+ Toast.makeText(this,
+ "Secure lock screen hasn't set up.\n"
+ + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
+ Toast.LENGTH_LONG).show();
+ purchaseButton.setEnabled(false);
+ return;
+ }
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
- if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE
- && state[0] == PackageManager.PERMISSION_GRANTED) {
- setContentView(R.layout.activity_main);
- Button purchaseButton = (Button) findViewById(R.id.purchase_button);
- if (!mKeyguardManager.isKeyguardSecure()) {
- // Show a message that the user hasn't set up a fingerprint or lock screen.
- Toast.makeText(this,
- "Secure lock screen hasn't set up.\n"
- + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
- Toast.LENGTH_LONG).show();
- purchaseButton.setEnabled(false);
- return;
- }
- if (!mFingerprintManager.hasEnrolledFingerprints()) {
- purchaseButton.setEnabled(false);
- // This happens when no fingerprints are registered.
- Toast.makeText(this,
- "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint",
- Toast.LENGTH_LONG).show();
- return;
- }
- createKey();
- purchaseButton.setEnabled(true);
- purchaseButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- findViewById(R.id.confirmation_message).setVisibility(View.GONE);
- findViewById(R.id.encrypted_message).setVisibility(View.GONE);
-
- // Set up the crypto object for later. The object will be authenticated by use
- // of the fingerprint.
- if (initCipher()) {
-
- // Show the fingerprint dialog. The user has the option to use the fingerprint with
- // crypto, or you can fall back to using a server-side verified password.
- mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
- boolean useFingerprintPreference = mSharedPreferences
- .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
- true);
- if (useFingerprintPreference) {
- mFragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
- } else {
- mFragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
- }
- mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ //noinspection ResourceType
+ if (!mFingerprintManager.hasEnrolledFingerprints()) {
+ purchaseButton.setEnabled(false);
+ // This happens when no fingerprints are registered.
+ Toast.makeText(this,
+ "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ createKey();
+ purchaseButton.setEnabled(true);
+ purchaseButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ findViewById(R.id.confirmation_message).setVisibility(View.GONE);
+ findViewById(R.id.encrypted_message).setVisibility(View.GONE);
+
+ // Set up the crypto object for later. The object will be authenticated by use
+ // of the fingerprint.
+ if (initCipher()) {
+
+ // Show the fingerprint dialog. The user has the option to use the fingerprint with
+ // crypto, or you can fall back to using a server-side verified password.
+ mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ boolean useFingerprintPreference = mSharedPreferences
+ .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
+ true);
+ if (useFingerprintPreference) {
+ mFragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
} else {
- // This happens if the lock screen has been disabled or or a fingerprint got
- // enrolled. Thus show the dialog to authenticate with their password first
- // and ask the user if they want to authenticate with fingerprints in the
- // future
- mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
mFragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
- mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
}
+ mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ } else {
+ // This happens if the lock screen has been disabled or or a fingerprint got
+ // enrolled. Thus show the dialog to authenticate with their password first
+ // and ask the user if they want to authenticate with fingerprints in the
+ // future
+ mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ mFragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
+ mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
}
- });
- }
+ }
+ });
}
/**
diff --git a/security/FingerprintDialog/template-params.xml b/security/FingerprintDialog/template-params.xml
index 0fa320c1..33db81c3 100644
--- a/security/FingerprintDialog/template-params.xml
+++ b/security/FingerprintDialog/template-params.xml
@@ -20,7 +20,7 @@
E.g. Skipping device 'Nexus 5 - MNC', due to different API preview 'MNC' and 'android-MNC'
-->
<sample>
- <name>Fingerprint Dialog Sample</name>
+ <name>FingerprintDialog</name>
<group>Security</group>
<package>com.example.android.fingerprintdialog</package>
@@ -87,9 +87,9 @@ before proceeding some actions such as purchasing an item.
First you need to create a symmetric key in the Android Key Store using [KeyGenerator][1]
which can be only be used after the user has authenticated with fingerprint and pass
-a [KeyGeneratorSpec][2].
+a [KeyGenParameterSpec][2].
-By setting [KeyGeneratorSpec.Builder.setUserAuthenticationRequired][3] to true, you can permit the
+By setting [KeyGenParameterSpec.Builder.setUserAuthenticationRequired][3] to true, you can permit the
use of the key only after the user authenticate it including when authenticated with the user's
fingerprint.
@@ -101,11 +101,11 @@ Once the fingerprint (or password) is verified, the
[FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded()][6] callback is called.
[1]: https://developer.android.com/reference/javax/crypto/KeyGenerator.html
-[2]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.html
-[3]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.Builder#setUserAuthenticationRequired().html
-[4]: https://developer.android.com/reference/android/hardware/FingerprintManager#authenticate().html
+[2]: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
+[3]: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder.html#setUserAuthenticationRequired%28boolean%29
+[4]: https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.html#authenticate%28android.hardware.fingerprint.FingerprintManager.CryptoObject,%20android.os.CancellationSignal,%20int,%20android.hardware.fingerprint.FingerprintManager.AuthenticationCallback,%20android.os.Handler%29
[5]: https://developer.android.com/reference/javax/crypto/Cipher.html
-[6]: https://developer.android.com/reference/android/hardware/FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded().html
+[6]: https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.AuthenticationCallback.html#onAuthenticationSucceeded%28android.hardware.fingerprint.FingerprintManager.AuthenticationResult%29
]]>
</intro>
</metadata>
diff --git a/security/keystore/BasicAndroidKeyStore/Application/src/main/java/com/example/android/basicandroidkeystore/BasicAndroidKeyStoreFragment.java b/security/keystore/BasicAndroidKeyStore/Application/src/main/java/com/example/android/basicandroidkeystore/BasicAndroidKeyStoreFragment.java
index 12873e84..e6244bfb 100644
--- a/security/keystore/BasicAndroidKeyStore/Application/src/main/java/com/example/android/basicandroidkeystore/BasicAndroidKeyStoreFragment.java
+++ b/security/keystore/BasicAndroidKeyStore/Application/src/main/java/com/example/android/basicandroidkeystore/BasicAndroidKeyStoreFragment.java
@@ -156,7 +156,7 @@ public class BasicAndroidKeyStoreFragment extends Fragment {
// generated.
Calendar start = new GregorianCalendar();
Calendar end = new GregorianCalendar();
- end.add(1, Calendar.YEAR);
+ end.add(Calendar.YEAR, 1);
//END_INCLUDE(create_valid_dates)
@@ -316,8 +316,7 @@ public class BasicAndroidKeyStoreFragment extends Fragment {
// Verify the data.
s.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate());
s.update(data);
- boolean valid = s.verify(signature);
- return valid;
+ return s.verify(signature);
// END_INCLUDE(verify_data)
}
diff --git a/wearable/wear/AgendaData/Wearable/src/main/AndroidManifest.xml b/wearable/wear/AgendaData/Wearable/src/main/AndroidManifest.xml
index dcab6227..e6dbab70 100644
--- a/wearable/wear/AgendaData/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/AgendaData/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.agendadata" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/AgendaData/template-params.xml b/wearable/wear/AgendaData/template-params.xml
index 5c996f90..e5bdd228 100644
--- a/wearable/wear/AgendaData/template-params.xml
+++ b/wearable/wear/AgendaData/template-params.xml
@@ -24,8 +24,9 @@
<minSdk>18</minSdk>
<targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
- <dependency>com.android.support:design:23.0.0</dependency>
+ <dependency>com.android.support:design:23.1.0</dependency>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/AlwaysOn/template-params.xml b/wearable/wear/AlwaysOn/template-params.xml
index e5794ee4..b3aac9a3 100644
--- a/wearable/wear/AlwaysOn/template-params.xml
+++ b/wearable/wear/AlwaysOn/template-params.xml
@@ -19,11 +19,11 @@
<group>Wearable</group>
<package>com.example.android.wearable.wear.alwayson</package>
- <dependency_wearable>com.google.android.support:wearable:1.2.0</dependency_wearable>
+ <dependency_wearable>com.google.android.support:wearable:1.3.0</dependency_wearable>
+
<provided_dependency_wearable>com.google.android.wearable:wearable:1.0.0</provided_dependency_wearable>
- <minSdk>20</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
@@ -79,4 +79,4 @@ As always, you will still want to apply the [performance guidelines][3] outlined
]]>
</intro>
</metadata>
-</sample> \ No newline at end of file
+</sample>
diff --git a/wearable/wear/DataLayer/Application/src/main/AndroidManifest.xml b/wearable/wear/DataLayer/Application/src/main/AndroidManifest.xml
index e80846de..ed1cec34 100644
--- a/wearable/wear/DataLayer/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/DataLayer/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.datalayer" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="22" />
+ android:targetSdkVersion="23" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
diff --git a/wearable/wear/DataLayer/Wearable/src/main/java/com/example/android/wearable/datalayer/MainActivity.java b/wearable/wear/DataLayer/Wearable/src/main/java/com/example/android/wearable/datalayer/MainActivity.java
index 678e428c..b3cb2530 100644
--- a/wearable/wear/DataLayer/Wearable/src/main/java/com/example/android/wearable/datalayer/MainActivity.java
+++ b/wearable/wear/DataLayer/Wearable/src/main/java/com/example/android/wearable/datalayer/MainActivity.java
@@ -23,6 +23,7 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.wearable.view.DotsPageIndicator;
@@ -41,6 +42,7 @@ import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
+import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.CapabilityApi;
@@ -85,7 +87,6 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
private static final String CAPABILITY_2_NAME = "capability_2";
private GoogleApiClient mGoogleApiClient;
- private Handler mHandler;
private GridViewPager mPager;
private DataFragment mDataFragment;
private AssetFragment mAssetFragment;
@@ -93,7 +94,6 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
- mHandler = new Handler();
setContentView(R.layout.main_activity);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setupViews();
@@ -137,15 +137,6 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
Log.e(TAG, "onConnectionFailed(): Failed to connect, with result: " + result);
}
- private void generateEvent(final String title, final String text) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mDataFragment.appendItem(title, text);
- }
- });
- }
-
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
LOGD(TAG, "onDataChanged(): " + dataEvents);
@@ -155,29 +146,22 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
String path = event.getDataItem().getUri().getPath();
if (DataLayerListenerService.IMAGE_PATH.equals(path)) {
DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
- Asset photo = dataMapItem.getDataMap()
+ Asset photoAsset = dataMapItem.getDataMap()
.getAsset(DataLayerListenerService.IMAGE_KEY);
- final Bitmap bitmap = loadBitmapFromAsset(mGoogleApiClient, photo);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Setting background image on second page..");
- moveToPage(1);
- mAssetFragment.setBackgroundImage(bitmap);
- }
- });
+ // Loads image on background thread.
+ new LoadBitmapAsyncTask().execute(photoAsset);
} else if (DataLayerListenerService.COUNT_PATH.equals(path)) {
LOGD(TAG, "Data Changed for COUNT_PATH");
- generateEvent("DataItem Changed", event.getDataItem().toString());
+ mDataFragment.appendItem("DataItem Changed", event.getDataItem().toString());
} else {
LOGD(TAG, "Unrecognized path: " + path);
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
- generateEvent("DataItem Deleted", event.getDataItem().toString());
+ mDataFragment.appendItem("DataItem Deleted", event.getDataItem().toString());
} else {
- generateEvent("Unknown data event type", "Type = " + event.getType());
+ mDataFragment.appendItem("Unknown data event type", "Type = " + event.getType());
}
}
}
@@ -199,20 +183,27 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
* Find the connected nodes that provide at least one of the given capabilities
*/
private void showNodes(final String... capabilityNames) {
- Wearable.CapabilityApi.getAllCapabilities(mGoogleApiClient,
- CapabilityApi.FILTER_REACHABLE).setResultCallback(
+ PendingResult<CapabilityApi.GetAllCapabilitiesResult> pendingCapabilityResult =
+ Wearable.CapabilityApi.getAllCapabilities(
+ mGoogleApiClient,
+ CapabilityApi.FILTER_REACHABLE);
+
+ pendingCapabilityResult.setResultCallback(
new ResultCallback<CapabilityApi.GetAllCapabilitiesResult>() {
@Override
public void onResult(
CapabilityApi.GetAllCapabilitiesResult getAllCapabilitiesResult) {
+
if (!getAllCapabilitiesResult.getStatus().isSuccess()) {
Log.e(TAG, "Failed to get capabilities");
return;
}
- Map<String, CapabilityInfo>
- capabilitiesMap = getAllCapabilitiesResult.getAllCapabilities();
+
+ Map<String, CapabilityInfo> capabilitiesMap =
+ getAllCapabilitiesResult.getAllCapabilities();
Set<Node> nodes = new HashSet<>();
+
if (capabilitiesMap.isEmpty()) {
showDiscoveredNodes(nodes);
return;
@@ -231,7 +222,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
for (Node node : nodes) {
nodesList.add(node.getDisplayName());
}
- Log.d(TAG, "Connected Nodes: " + (nodesList.isEmpty()
+ LOGD(TAG, "Connected Nodes: " + (nodesList.isEmpty()
? "No connected device was found for the given capabilities"
: TextUtils.join(",", nodesList)));
String msg;
@@ -246,39 +237,20 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
});
}
- /**
- * Extracts {@link android.graphics.Bitmap} data from the
- * {@link com.google.android.gms.wearable.Asset}
- */
- private Bitmap loadBitmapFromAsset(GoogleApiClient apiClient, Asset asset) {
- if (asset == null) {
- throw new IllegalArgumentException("Asset must be non-null");
- }
-
- InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
- apiClient, asset).await().getInputStream();
-
- if (assetInputStream == null) {
- Log.w(TAG, "Requested an unknown Asset.");
- return null;
- }
- return BitmapFactory.decodeStream(assetInputStream);
- }
-
@Override
public void onMessageReceived(MessageEvent event) {
LOGD(TAG, "onMessageReceived: " + event);
- generateEvent("Message", event.toString());
+ mDataFragment.appendItem("Message", event.toString());
}
@Override
public void onPeerConnected(Node node) {
- generateEvent("Node Connected", node.getId());
+ mDataFragment.appendItem("Node Connected", node.getId());
}
@Override
public void onPeerDisconnected(Node node) {
- generateEvent("Node Disconnected", node.getId());
+ mDataFragment.appendItem("Node Disconnected", node.getId());
}
private void setupViews() {
@@ -330,4 +302,43 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
}
}
+
+ /*
+ * Extracts {@link android.graphics.Bitmap} data from the
+ * {@link com.google.android.gms.wearable.Asset}
+ */
+ private class LoadBitmapAsyncTask extends AsyncTask<Asset, Void, Bitmap> {
+
+ @Override
+ protected Bitmap doInBackground(Asset... params) {
+
+ if(params.length > 0) {
+
+ Asset asset = params[0];
+
+ InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
+ mGoogleApiClient, asset).await().getInputStream();
+
+ if (assetInputStream == null) {
+ Log.w(TAG, "Requested an unknown Asset.");
+ return null;
+ }
+ return BitmapFactory.decodeStream(assetInputStream);
+
+ } else {
+ Log.e(TAG, "Asset must be non-null");
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+
+ if(bitmap != null) {
+ LOGD(TAG, "Setting background image on second page..");
+ moveToPage(1);
+ mAssetFragment.setBackgroundImage(bitmap);
+ }
+ }
+ }
}
diff --git a/wearable/wear/DataLayer/template-params.xml b/wearable/wear/DataLayer/template-params.xml
index d9bfc070..6df31f5b 100644
--- a/wearable/wear/DataLayer/template-params.xml
+++ b/wearable/wear/DataLayer/template-params.xml
@@ -20,7 +20,8 @@
<package>com.example.android.wearable.datalayer</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/DelayedConfirmation/Application/src/main/AndroidManifest.xml b/wearable/wear/DelayedConfirmation/Application/src/main/AndroidManifest.xml
index d8060a8d..e3e6de17 100644
--- a/wearable/wear/DelayedConfirmation/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/DelayedConfirmation/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.delayedconfirmation" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="22" />
+ android:targetSdkVersion="23" />
<application
android:allowBackup="true"
diff --git a/wearable/wear/DelayedConfirmation/template-params.xml b/wearable/wear/DelayedConfirmation/template-params.xml
index 5f77d655..239a0eab 100644
--- a/wearable/wear/DelayedConfirmation/template-params.xml
+++ b/wearable/wear/DelayedConfirmation/template-params.xml
@@ -23,7 +23,8 @@
<package>com.example.android.wearable.delayedconfirmation</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/ElizaChat/Application/src/main/AndroidManifest.xml b/wearable/wear/ElizaChat/Application/src/main/AndroidManifest.xml
index 8f35c565..b544ed05 100644
--- a/wearable/wear/ElizaChat/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/ElizaChat/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.elizachat" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="23" />
<application
android:allowBackup="true"
diff --git a/wearable/wear/ElizaChat/Application/src/main/java/com/example/android/wearable/elizachat/ResponderService.java b/wearable/wear/ElizaChat/Application/src/main/java/com/example/android/wearable/elizachat/ResponderService.java
index 3bef19c6..2406668c 100644
--- a/wearable/wear/ElizaChat/Application/src/main/java/com/example/android/wearable/elizachat/ResponderService.java
+++ b/wearable/wear/ElizaChat/Application/src/main/java/com/example/android/wearable/elizachat/ResponderService.java
@@ -96,7 +96,7 @@ public class ResponderService extends Service {
.setContentText(mLastResponse)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.bg_eliza))
.setSmallIcon(R.drawable.bg_eliza)
- .setPriority(NotificationCompat.PRIORITY_MIN);
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT);
Intent intent = new Intent(ACTION_RESPONSE);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent,
diff --git a/wearable/wear/ElizaChat/template-params.xml b/wearable/wear/ElizaChat/template-params.xml
index ea26b5cc..ff762424 100644
--- a/wearable/wear/ElizaChat/template-params.xml
+++ b/wearable/wear/ElizaChat/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.wearable.elizachat</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
<strings>
<intro>
diff --git a/wearable/wear/EmbeddedApp/LICENSE b/wearable/wear/EmbeddedApp/LICENSE
index 1af981f5..4f229463 100644
--- a/wearable/wear/EmbeddedApp/LICENSE
+++ b/wearable/wear/EmbeddedApp/LICENSE
@@ -1,4 +1,6 @@
- Apache License
+Apache License
+--------------
+
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -178,7 +180,7 @@
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
+ boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@@ -186,7 +188,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2014 The Android Open Source Project
+ Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -199,3 +201,447 @@
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.
+
+All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav
+and *.ogg) are licensed under the CC-BY-NC license. All other files are
+licensed under the Apache 2 license.
+
+CC-BY-NC License
+----------------
+
+Attribution-NonCommercial-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More_considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
+Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-NonCommercial-ShareAlike 4.0 International Public License
+("Public License"). To the extent this Public License may be
+interpreted as a contract, You are granted the Licensed Rights in
+consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the
+Licensor receives from making the Licensed Material available under
+these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-NC-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution, NonCommercial, and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. NonCommercial means not primarily intended for or directed towards
+ commercial advantage or monetary compensation. For purposes of
+ this Public License, the exchange of the Licensed Material for
+ other material subject to Copyright and Similar Rights by digital
+ file-sharing or similar means is NonCommercial provided there is
+ no payment of monetary compensation in connection with the
+ exchange.
+
+ l. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ m. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ n. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part, for NonCommercial purposes only; and
+
+ b. produce, reproduce, and Share Adapted Material for
+ NonCommercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties, including when
+ the Licensed Material is used other than for NonCommercial
+ purposes.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-NC-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database for NonCommercial purposes
+ only;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+=======================================================================
+
+Creative Commons is not a party to its public licenses.
+Notwithstanding, Creative Commons may elect to apply one of its public
+licenses to material it publishes and in those instances will be
+considered the "Licensor." Except for the limited purpose of indicating
+that material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the public
+licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/wearable/wear/EmbeddedApp/Wearable/src/main/AndroidManifest.xml b/wearable/wear/EmbeddedApp/Wearable/src/main/AndroidManifest.xml
index 4863d66e..aab1348a 100644
--- a/wearable/wear/EmbeddedApp/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/EmbeddedApp/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.embeddedapp" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/EmbeddedApp/template-params.xml b/wearable/wear/EmbeddedApp/template-params.xml
index 13186e52..424585da 100644
--- a/wearable/wear/EmbeddedApp/template-params.xml
+++ b/wearable/wear/EmbeddedApp/template-params.xml
@@ -23,7 +23,8 @@
<package>com.example.android.wearable.embeddedapp</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/FindMyPhone/Application/src/main/AndroidManifest.xml b/wearable/wear/FindMyPhone/Application/src/main/AndroidManifest.xml
index af108af4..a59cd7d9 100644
--- a/wearable/wear/FindMyPhone/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/FindMyPhone/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.findphone">
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="22" />
+ android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
diff --git a/wearable/wear/FindMyPhone/template-params.xml b/wearable/wear/FindMyPhone/template-params.xml
index e8d71c63..ff13793c 100644
--- a/wearable/wear/FindMyPhone/template-params.xml
+++ b/wearable/wear/FindMyPhone/template-params.xml
@@ -23,7 +23,8 @@
<package>com.example.android.wearable.findphone</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/Flashlight/Wearable/src/main/AndroidManifest.xml b/wearable/wear/Flashlight/Wearable/src/main/AndroidManifest.xml
index 738ba9d3..1eb15d07 100644
--- a/wearable/wear/Flashlight/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/Flashlight/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.flashlight" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/Flashlight/template-params.xml b/wearable/wear/Flashlight/template-params.xml
index e1ec8698..3b9f2111 100644
--- a/wearable/wear/Flashlight/template-params.xml
+++ b/wearable/wear/Flashlight/template-params.xml
@@ -22,15 +22,15 @@
<group>Wearable</group>
<package>com.example.android.wearable.flashlight</package>
- <!-- change minSdk if needed-->
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
<![CDATA[
Wearable activity that uses your wearable screen as a flashlight. There is also
- a party-mode option, if you want to make things interesting.
+ a party-mode option (swipe left), if you want to make things interesting.
]]>
</intro>
</strings>
diff --git a/wearable/wear/Geofencing/Application/src/main/AndroidManifest.xml b/wearable/wear/Geofencing/Application/src/main/AndroidManifest.xml
index d07a2659..d1eabc3d 100644
--- a/wearable/wear/Geofencing/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/Geofencing/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.geofencing">
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
diff --git a/wearable/wear/Geofencing/Wearable/src/main/AndroidManifest.xml b/wearable/wear/Geofencing/Wearable/src/main/AndroidManifest.xml
index 082f396b..f25cc447 100644
--- a/wearable/wear/Geofencing/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/Geofencing/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.geofencing" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/Geofencing/template-params.xml b/wearable/wear/Geofencing/template-params.xml
index 00fd3b36..a69c82c6 100644
--- a/wearable/wear/Geofencing/template-params.xml
+++ b/wearable/wear/Geofencing/template-params.xml
@@ -24,8 +24,9 @@
<minSdk>18</minSdk>
<targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
- <dependency>com.google.android.gms:play-services-location:7.3.0</dependency>
+ <dependency>com.google.android.gms:play-services-location:8.1.0</dependency>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/GridViewPager/Wearable/src/main/AndroidManifest.xml b/wearable/wear/GridViewPager/Wearable/src/main/AndroidManifest.xml
index 5c362dcb..e25cd637 100644
--- a/wearable/wear/GridViewPager/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/GridViewPager/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.gridviewpager" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/GridViewPager/template-params.xml b/wearable/wear/GridViewPager/template-params.xml
index f6dc567c..66e4bf4a 100644
--- a/wearable/wear/GridViewPager/template-params.xml
+++ b/wearable/wear/GridViewPager/template-params.xml
@@ -22,8 +22,7 @@
<group>Wearable</group>
<package>com.example.android.wearable.gridviewpager</package>
- <minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
diff --git a/wearable/wear/JumpingJack/Wearable/src/main/AndroidManifest.xml b/wearable/wear/JumpingJack/Wearable/src/main/AndroidManifest.xml
index 02b7a4ff..f6cf2204 100644
--- a/wearable/wear/JumpingJack/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/JumpingJack/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.jumpingjack">
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/JumpingJack/template-params.xml b/wearable/wear/JumpingJack/template-params.xml
index 692a6a5e..9512f32e 100644
--- a/wearable/wear/JumpingJack/template-params.xml
+++ b/wearable/wear/JumpingJack/template-params.xml
@@ -22,8 +22,7 @@
<group>Wearable</group>
<package>com.example.android.wearable.jumpingjack</package>
- <minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
@@ -66,7 +65,7 @@ by counting how many jumping jacks you have performed.
[SensorEventListener][1] offers you methods used for receiving notifications from the
[SensorManager][2] when sensor values have changed.
-This example counts how many times Jumping Jakcs are performed by detecting the value
+This example counts how many times Jumping Jacks are performed by detecting the value
of the Gravity sensor by the following code:
```java
diff --git a/wearable/wear/Notifications/Application/src/main/AndroidManifest.xml b/wearable/wear/Notifications/Application/src/main/AndroidManifest.xml
index 3f1274d8..6a17ad8c 100644
--- a/wearable/wear/Notifications/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/Notifications/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.support.wearable.notifications" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.VIBRATE" />
diff --git a/wearable/wear/Notifications/Wearable/src/main/AndroidManifest.xml b/wearable/wear/Notifications/Wearable/src/main/AndroidManifest.xml
index 34a29ff6..a446fd9b 100644
--- a/wearable/wear/Notifications/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/Notifications/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.support.wearable.notifications" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/Notifications/template-params.xml b/wearable/wear/Notifications/template-params.xml
index c4936ea6..64d2e5ba 100644
--- a/wearable/wear/Notifications/template-params.xml
+++ b/wearable/wear/Notifications/template-params.xml
@@ -23,7 +23,8 @@
<package>com.example.android.support.wearable.notifications</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/Quiz/Application/src/main/AndroidManifest.xml b/wearable/wear/Quiz/Application/src/main/AndroidManifest.xml
index 801a4732..8fabd42d 100644
--- a/wearable/wear/Quiz/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/Quiz/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.quiz" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="22" />
+ android:targetSdkVersion="23" />
<application
android:allowBackup="true"
diff --git a/wearable/wear/Quiz/template-params.xml b/wearable/wear/Quiz/template-params.xml
index 297bf190..4a920bc3 100644
--- a/wearable/wear/Quiz/template-params.xml
+++ b/wearable/wear/Quiz/template-params.xml
@@ -23,7 +23,8 @@
<package>com.example.android.wearable.quiz</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/RecipeAssistant/Application/src/main/AndroidManifest.xml b/wearable/wear/RecipeAssistant/Application/src/main/AndroidManifest.xml
index 1786d278..141da9a0 100644
--- a/wearable/wear/RecipeAssistant/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/RecipeAssistant/Application/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.recipeassistant" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="23" />
<application
android:allowBackup="true"
diff --git a/wearable/wear/RecipeAssistant/template-params.xml b/wearable/wear/RecipeAssistant/template-params.xml
index 943b368c..f1fa1021 100644
--- a/wearable/wear/RecipeAssistant/template-params.xml
+++ b/wearable/wear/RecipeAssistant/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.wearable.recipeassistant</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
<strings>
<intro>
diff --git a/wearable/wear/SkeletonWearableApp/Wearable/src/main/AndroidManifest.xml b/wearable/wear/SkeletonWearableApp/Wearable/src/main/AndroidManifest.xml
index f99d785c..f9e89782 100644
--- a/wearable/wear/SkeletonWearableApp/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/SkeletonWearableApp/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.google.wearable.app" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/SkeletonWearableApp/template-params.xml b/wearable/wear/SkeletonWearableApp/template-params.xml
index b0f4b366..f25c0351 100644
--- a/wearable/wear/SkeletonWearableApp/template-params.xml
+++ b/wearable/wear/SkeletonWearableApp/template-params.xml
@@ -19,8 +19,7 @@
<group>Wearable</group>
<package>com.example.android.google.wearable.app</package>
- <minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
diff --git a/wearable/wear/SpeedTracker/Application/src/main/AndroidManifest.xml b/wearable/wear/SpeedTracker/Application/src/main/AndroidManifest.xml
index 44284d4f..be88f6d6 100644
--- a/wearable/wear/SpeedTracker/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/SpeedTracker/Application/src/main/AndroidManifest.xml
@@ -2,25 +2,35 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.wearable.speedtracker" >
+
+ <uses-sdk
+ android:minSdkVersion="18"
+ android:targetSdkVersion="23" />
+
+ <!-- BEGIN_INCLUDE(manifest) -->
+
+ <!-- Note that all required permissions are declared here in the Android manifest.
+ On Android M and above, use of permissions not in the normal permission group are
+ requested at run time. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="18"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
+ <!-- END_INCLUDE(manifest) -->
+
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
<uses-feature
android:glEsVersion="0x00020000" android:required="true"/>
- <uses-sdk
- android:minSdkVersion="18"
- android:targetSdkVersion="21" />
<application
android:name=".PhoneApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
- android:theme="@style/AppTheme" >
+ android:theme="@style/Theme.AppCompat.Light" >
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="@string/map_v2_api_key"/>
diff --git a/wearable/wear/SpeedTracker/Application/src/main/java/com/example/android/wearable/speedtracker/PhoneMainActivity.java b/wearable/wear/SpeedTracker/Application/src/main/java/com/example/android/wearable/speedtracker/PhoneMainActivity.java
index 76f609b1..c645bdd6 100644
--- a/wearable/wear/SpeedTracker/Application/src/main/java/com/example/android/wearable/speedtracker/PhoneMainActivity.java
+++ b/wearable/wear/SpeedTracker/Application/src/main/java/com/example/android/wearable/speedtracker/PhoneMainActivity.java
@@ -23,10 +23,10 @@ import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.PolylineOptions;
-import android.app.Activity;
import android.app.DatePickerDialog;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
@@ -45,7 +45,8 @@ import java.util.List;
* a map. This data is then saved into an internal database and the corresponding data items are
* deleted.
*/
-public class PhoneMainActivity extends Activity implements DatePickerDialog.OnDateSetListener {
+public class PhoneMainActivity extends AppCompatActivity implements
+ DatePickerDialog.OnDateSetListener {
private static final String TAG = "PhoneMainActivity";
private static final int BOUNDING_BOX_PADDING_PX = 50;
diff --git a/wearable/wear/SpeedTracker/Application/src/main/res/layout/main_activity.xml b/wearable/wear/SpeedTracker/Application/src/main/res/layout/main_activity.xml
index a18c6448..17a8f6a9 100644
--- a/wearable/wear/SpeedTracker/Application/src/main/res/layout/main_activity.xml
+++ b/wearable/wear/SpeedTracker/Application/src/main/res/layout/main_activity.xml
@@ -21,7 +21,8 @@
<RelativeLayout
android:id="@+id/top_container"
android:layout_width="fill_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp">
<Button
android:id="@+id/date_picker"
android:layout_width="wrap_content"
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/AndroidManifest.xml b/wearable/wear/SpeedTracker/Wearable/src/main/AndroidManifest.xml
index ab19d5e6..c9cbad18 100644
--- a/wearable/wear/SpeedTracker/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/SpeedTracker/Wearable/src/main/AndroidManifest.xml
@@ -19,18 +19,22 @@
<uses-feature android:name="android.hardware.type.watch"/>
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>\
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk
android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="23" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault">
+
+ <!--If you want your app to run on pre-22, then set required to false -->
+ <uses-library android:name="com.google.android.wearable" android:required="false" />
+
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<activity
@@ -38,7 +42,6 @@
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
-
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@@ -48,12 +51,6 @@
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
- <activity
- android:name=".ui.LocationSettingActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- </intent-filter>
- </activity>
</application>
</manifest>
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/SpeedPickerActivity.java b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/SpeedPickerActivity.java
index d55d7dfb..d178891f 100644
--- a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/SpeedPickerActivity.java
+++ b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/SpeedPickerActivity.java
@@ -17,9 +17,8 @@
package com.example.android.wearable.speedtracker;
import android.app.Activity;
-import android.content.SharedPreferences;
+import android.content.Intent;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.support.wearable.view.WearableListView;
import android.widget.TextView;
@@ -31,6 +30,9 @@ import com.example.android.wearable.speedtracker.ui.SpeedPickerListAdapter;
*/
public class SpeedPickerActivity extends Activity implements WearableListView.ClickListener {
+ public static final String EXTRA_NEW_SPEED_LIMIT =
+ "com.example.android.wearable.speedtracker.extra.NEW_SPEED_LIMIT";
+
/* Speeds, in mph, that will be shown on the list */
private int[] speeds = {25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75};
@@ -75,9 +77,13 @@ public class SpeedPickerActivity extends Activity implements WearableListView.Cl
@Override
public void onClick(WearableListView.ViewHolder viewHolder) {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
- pref.edit().putInt(WearableMainActivity.PREFS_SPEED_LIMIT_KEY,
- speeds[viewHolder.getPosition()]).apply();
+
+ int newSpeedLimit = speeds[viewHolder.getPosition()];
+
+ Intent resultIntent = new Intent(Intent.ACTION_PICK);
+ resultIntent.putExtra(EXTRA_NEW_SPEED_LIMIT, newSpeedLimit);
+ setResult(RESULT_OK, resultIntent);
+
finish();
}
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/WearableMainActivity.java b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/WearableMainActivity.java
index f3015bf8..ee3c3ef9 100644
--- a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/WearableMainActivity.java
+++ b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/WearableMainActivity.java
@@ -28,7 +28,7 @@ import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
-import android.app.Activity;
+import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
@@ -38,18 +38,19 @@ import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.wearable.activity.WearableActivity;
import android.util.Log;
import android.view.View;
-import android.view.WindowManager;
-import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.android.wearable.speedtracker.common.Constants;
import com.example.android.wearable.speedtracker.common.LocationEntry;
-import com.example.android.wearable.speedtracker.ui.LocationSettingActivity;
import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
/**
* The main activity for the wearable app. User can pick a speed limit, and after this activity
@@ -58,33 +59,54 @@ import java.util.Calendar;
* and if the user exceeds the speed limit, it will turn red. In order to show the user that GPS
* location data is coming in, a small green dot keeps on blinking while GPS data is available.
*/
-public class WearableMainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
- GoogleApiClient.OnConnectionFailedListener, LocationListener {
+public class WearableMainActivity extends WearableActivity implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener,
+ ActivityCompat.OnRequestPermissionsResultCallback,
+ LocationListener {
private static final String TAG = "WearableActivity";
- private static final long UPDATE_INTERVAL_MS = 5 * 1000;
- private static final long FASTEST_INTERVAL_MS = 5 * 1000;
+ private static final long UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
+ private static final long FASTEST_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
- public static final float MPH_IN_METERS_PER_SECOND = 2.23694f;
+ private static final float MPH_IN_METERS_PER_SECOND = 2.23694f;
+
+ private static final int SPEED_LIMIT_DEFAULT_MPH = 45;
- public static final String PREFS_SPEED_LIMIT_KEY = "speed_limit";
- public static final int SPEED_LIMIT_DEFAULT_MPH = 45;
private static final long INDICATOR_DOT_FADE_AWAY_MS = 500L;
+ // Request codes for changing speed limit and location permissions.
+ private static final int REQUEST_PICK_SPEED_LIMIT = 0;
+
+ // Id to identify Location permission request.
+ private static final int REQUEST_GPS_PERMISSION = 1;
+
+ // Shared Preferences for saving speed limit and location permission between app launches.
+ private static final String PREFS_SPEED_LIMIT_KEY = "SpeedLimit";
+
+ private Calendar mCalendar;
+
+ private TextView mSpeedLimitTextView;
+ private TextView mSpeedTextView;
+ private ImageView mGpsPermissionImageView;
+ private TextView mCurrentSpeedMphTextView;
+ private TextView mGpsIssueTextView;
+ private View mBlinkingGpsStatusDotView;
+
+ private String mGpsPermissionNeededMessage;
+ private String mAcquiringGpsMessage;
+
+ private int mSpeedLimit;
+ private float mSpeed;
+
+ private boolean mGpsPermissionApproved;
+
+ private boolean mWaitingForGpsSignal;
+
private GoogleApiClient mGoogleApiClient;
- private TextView mSpeedLimitText;
- private TextView mCurrentSpeedText;
- private ImageView mSaveImageView;
- private TextView mAcquiringGps;
- private TextView mCurrentSpeedMphText;
-
- private int mCurrentSpeedLimit;
- private float mCurrentSpeed;
- private View mDot;
+
private Handler mHandler = new Handler();
- private Calendar mCalendar;
- private boolean mSaveGpsLocation;
private enum SpeedState {
BELOW(R.color.speed_below), CLOSE(R.color.speed_close), ABOVE(R.color.speed_above);
@@ -104,20 +126,53 @@ public class WearableMainActivity extends Activity implements GoogleApiClient.Co
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate()");
+
+
setContentView(R.layout.main_activity);
+
+ /*
+ * Enables Always-on, so our app doesn't shut down when the watch goes into ambient mode.
+ * Best practice is to override onEnterAmbient(), onUpdateAmbient(), and onExitAmbient() to
+ * optimize the display for ambient mode. However, for brevity, we aren't doing that here
+ * to focus on learning location and permissions. For more information on best practices
+ * in ambient mode, check this page:
+ * https://developer.android.com/training/wearables/apps/always-on.html
+ */
+ setAmbientEnabled();
+
+ mCalendar = Calendar.getInstance();
+
+ // Enables app to handle 23+ (M+) style permissions.
+ mGpsPermissionApproved =
+ ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED;
+
+ mGpsPermissionNeededMessage = getString(R.string.permission_rationale);
+ mAcquiringGpsMessage = getString(R.string.acquiring_gps);
+
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ mSpeedLimit = sharedPreferences.getInt(PREFS_SPEED_LIMIT_KEY, SPEED_LIMIT_DEFAULT_MPH);
+
+ mSpeed = 0;
+
+ mWaitingForGpsSignal = true;
+
+
+ /*
+ * If this hardware doesn't support GPS, we warn the user. Note that when such device is
+ * connected to a phone with GPS capabilities, the framework automatically routes the
+ * location requests from the phone. However, if the phone becomes disconnected and the
+ * wearable doesn't support GPS, no location is recorded until the phone is reconnected.
+ */
if (!hasGps()) {
- // If this hardware doesn't support GPS, we prefer to exit.
- // Note that when such device is connected to a phone with GPS capabilities, the
- // framework automatically routes the location requests to the phone. For this
- // application, this would not be desirable so we exit the app but for some other
- // applications, that might be a valid scenario.
- Log.w(TAG, "This hardware doesn't have GPS, so we exit");
+ Log.w(TAG, "This hardware doesn't have GPS, so we warn user.");
new AlertDialog.Builder(this)
.setMessage(getString(R.string.gps_not_available))
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
- finish();
dialog.cancel();
}
})
@@ -125,7 +180,6 @@ public class WearableMainActivity extends Activity implements GoogleApiClient.Co
@Override
public void onDismiss(DialogInterface dialog) {
dialog.cancel();
- finish();
}
})
.setCancelable(false)
@@ -133,164 +187,216 @@ public class WearableMainActivity extends Activity implements GoogleApiClient.Co
.show();
}
+
setupViews();
- updateSpeedVisibility(false);
- setSpeedLimit();
+
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
- mGoogleApiClient.connect();
}
- private void setupViews() {
- mSpeedLimitText = (TextView) findViewById(R.id.max_speed_text);
- mCurrentSpeedText = (TextView) findViewById(R.id.current_speed_text);
- mSaveImageView = (ImageView) findViewById(R.id.saving);
- ImageButton settingButton = (ImageButton) findViewById(R.id.settings);
- mAcquiringGps = (TextView) findViewById(R.id.acquiring_gps);
- mCurrentSpeedMphText = (TextView) findViewById(R.id.current_speed_mph);
- mDot = findViewById(R.id.dot);
-
- settingButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent speedIntent = new Intent(WearableMainActivity.this,
- SpeedPickerActivity.class);
- startActivity(speedIntent);
- }
- });
-
- mSaveImageView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent savingIntent = new Intent(WearableMainActivity.this,
- LocationSettingActivity.class);
- startActivity(savingIntent);
- }
- });
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if ((mGoogleApiClient != null) && (mGoogleApiClient.isConnected()) &&
+ (mGoogleApiClient.isConnecting())) {
+ LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
+ mGoogleApiClient.disconnect();
+ }
+
}
- private void setSpeedLimit(int speedLimit) {
- mSpeedLimitText.setText(getString(R.string.speed_limit, speedLimit));
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mGoogleApiClient != null) {
+ mGoogleApiClient.connect();
+ }
}
- private void setSpeedLimit() {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
- mCurrentSpeedLimit = pref.getInt(PREFS_SPEED_LIMIT_KEY, SPEED_LIMIT_DEFAULT_MPH);
- setSpeedLimit(mCurrentSpeedLimit);
+ private void setupViews() {
+ mSpeedLimitTextView = (TextView) findViewById(R.id.max_speed_text);
+ mSpeedTextView = (TextView) findViewById(R.id.current_speed_text);
+ mCurrentSpeedMphTextView = (TextView) findViewById(R.id.current_speed_mph);
+
+ mGpsPermissionImageView = (ImageView) findViewById(R.id.gps_permission);
+ mGpsIssueTextView = (TextView) findViewById(R.id.gps_issue_text);
+ mBlinkingGpsStatusDotView = findViewById(R.id.dot);
+
+ updateActivityViewsBasedOnLocationPermissions();
}
- private void setCurrentSpeed(float speed) {
- mCurrentSpeed = speed;
- mCurrentSpeedText.setText(String.format(getString(R.string.speed_format), speed));
- adjustColor();
+ public void onSpeedLimitClick(View view) {
+ Intent speedIntent = new Intent(WearableMainActivity.this,
+ SpeedPickerActivity.class);
+ startActivityForResult(speedIntent, REQUEST_PICK_SPEED_LIMIT);
+ }
+
+ public void onGpsPermissionClick(View view) {
+
+ if (!mGpsPermissionApproved) {
+
+ Log.i(TAG, "Location permission has NOT been granted. Requesting permission.");
+
+ // On 23+ (M+) devices, GPS permission not granted. Request permission.
+ ActivityCompat.requestPermissions(
+ this,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ REQUEST_GPS_PERMISSION);
+ }
}
/**
- * Adjusts the color of the speed based on its value relative to the speed limit.
+ * Adjusts the visibility of views based on location permissions.
*/
- private void adjustColor() {
- SpeedState state = SpeedState.ABOVE;
- if (mCurrentSpeed <= mCurrentSpeedLimit - 5) {
- state = SpeedState.BELOW;
- } else if (mCurrentSpeed <= mCurrentSpeedLimit) {
- state = SpeedState.CLOSE;
+ private void updateActivityViewsBasedOnLocationPermissions() {
+
+ /*
+ * If the user has approved location but we don't have a signal yet, we let the user know
+ * we are waiting on the GPS signal (this sometimes takes a little while). Otherwise, the
+ * user might think something is wrong.
+ */
+ if (mGpsPermissionApproved && mWaitingForGpsSignal) {
+
+ // We are getting a GPS signal w/ user permission.
+ mGpsIssueTextView.setText(mAcquiringGpsMessage);
+ mGpsIssueTextView.setVisibility(View.VISIBLE);
+ mGpsPermissionImageView.setImageResource(R.drawable.ic_gps_saving_grey600_96dp);
+
+ mSpeedTextView.setVisibility(View.GONE);
+ mSpeedLimitTextView.setVisibility(View.GONE);
+ mCurrentSpeedMphTextView.setVisibility(View.GONE);
+
+ } else if (mGpsPermissionApproved) {
+
+ mGpsIssueTextView.setVisibility(View.GONE);
+
+ mSpeedTextView.setVisibility(View.VISIBLE);
+ mSpeedLimitTextView.setVisibility(View.VISIBLE);
+ mCurrentSpeedMphTextView.setVisibility(View.VISIBLE);
+ mGpsPermissionImageView.setImageResource(R.drawable.ic_gps_saving_grey600_96dp);
+
+ } else {
+
+ // User needs to enable location for the app to work.
+ mGpsIssueTextView.setVisibility(View.VISIBLE);
+ mGpsIssueTextView.setText(mGpsPermissionNeededMessage);
+ mGpsPermissionImageView.setImageResource(R.drawable.ic_gps_not_saving_grey600_96dp);
+
+ mSpeedTextView.setVisibility(View.GONE);
+ mSpeedLimitTextView.setVisibility(View.GONE);
+ mCurrentSpeedMphTextView.setVisibility(View.GONE);
}
+ }
+
+ private void updateSpeedInViews() {
+
+ if (mGpsPermissionApproved) {
+
+ mSpeedLimitTextView.setText(getString(R.string.speed_limit, mSpeedLimit));
+ mSpeedTextView.setText(String.format(getString(R.string.speed_format), mSpeed));
- mCurrentSpeedText.setTextColor(getResources().getColor(state.getColor()));
+ // Adjusts the color of the speed based on its value relative to the speed limit.
+ SpeedState state = SpeedState.ABOVE;
+ if (mSpeed <= mSpeedLimit - 5) {
+ state = SpeedState.BELOW;
+ } else if (mSpeed <= mSpeedLimit) {
+ state = SpeedState.CLOSE;
+ }
+
+ mSpeedTextView.setTextColor(getResources().getColor(state.getColor()));
+
+ // Causes the (green) dot blinks when new GPS location data is acquired.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mBlinkingGpsStatusDotView.setVisibility(View.VISIBLE);
+ }
+ });
+ mBlinkingGpsStatusDotView.setVisibility(View.VISIBLE);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mBlinkingGpsStatusDotView.setVisibility(View.INVISIBLE);
+ }
+ }, INDICATOR_DOT_FADE_AWAY_MS);
+ }
}
@Override
public void onConnected(Bundle bundle) {
- LocationRequest locationRequest = LocationRequest.create()
- .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
- .setInterval(UPDATE_INTERVAL_MS)
- .setFastestInterval(FASTEST_INTERVAL_MS);
- LocationServices.FusedLocationApi
- .requestLocationUpdates(mGoogleApiClient, locationRequest, this)
- .setResultCallback(new ResultCallback<Status>() {
+ Log.d(TAG, "onConnected()");
- @Override
- public void onResult(Status status) {
- if (status.getStatus().isSuccess()) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Successfully requested location updates");
+ /*
+ * mGpsPermissionApproved covers 23+ (M+) style permissions. If that is already approved or
+ * the device is pre-23, the app uses mSaveGpsLocation to save the user's location
+ * preference.
+ */
+ if (mGpsPermissionApproved) {
+
+ LocationRequest locationRequest = LocationRequest.create()
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+ .setInterval(UPDATE_INTERVAL_MS)
+ .setFastestInterval(FASTEST_INTERVAL_MS);
+
+ LocationServices.FusedLocationApi
+ .requestLocationUpdates(mGoogleApiClient, locationRequest, this)
+ .setResultCallback(new ResultCallback<Status>() {
+
+ @Override
+ public void onResult(Status status) {
+ if (status.getStatus().isSuccess()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Successfully requested location updates");
+ }
+ } else {
+ Log.e(TAG,
+ "Failed in requesting location updates, "
+ + "status code: "
+ + status.getStatusCode() + ", message: " + status
+ .getStatusMessage());
}
- } else {
- Log.e(TAG,
- "Failed in requesting location updates, "
- + "status code: "
- + status.getStatusCode() + ", message: " + status
- .getStatusMessage());
}
- }
- });
+ });
+ }
}
@Override
public void onConnectionSuspended(int i) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
- }
+ Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
+
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
- Log.e(TAG, "onConnectionFailed(): connection to location client failed");
+ Log.e(TAG, "onConnectionFailed(): " + connectionResult.getErrorMessage());
}
@Override
public void onLocationChanged(Location location) {
- updateSpeedVisibility(true);
- setCurrentSpeed(location.getSpeed() * MPH_IN_METERS_PER_SECOND);
- flashDot();
- addLocationEntry(location.getLatitude(), location.getLongitude());
- }
+ Log.d(TAG, "onLocationChanged() : " + location);
- /**
- * Causes the (green) dot blinks when new GPS location data is acquired.
- */
- private void flashDot() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mDot.setVisibility(View.VISIBLE);
- }
- });
- mDot.setVisibility(View.VISIBLE);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mDot.setVisibility(View.INVISIBLE);
- }
- }, INDICATOR_DOT_FADE_AWAY_MS);
- }
- /**
- * Adjusts the visibility of speed indicator based on the arrival of GPS data.
- */
- private void updateSpeedVisibility(boolean speedVisible) {
- if (speedVisible) {
- mAcquiringGps.setVisibility(View.GONE);
- mCurrentSpeedText.setVisibility(View.VISIBLE);
- mCurrentSpeedMphText.setVisibility(View.VISIBLE);
- } else {
- mAcquiringGps.setVisibility(View.VISIBLE);
- mCurrentSpeedText.setVisibility(View.GONE);
- mCurrentSpeedMphText.setVisibility(View.GONE);
+ if (mWaitingForGpsSignal) {
+ mWaitingForGpsSignal = false;
+ updateActivityViewsBasedOnLocationPermissions();
}
+
+ mSpeed = location.getSpeed() * MPH_IN_METERS_PER_SECOND;
+ updateSpeedInViews();
+ addLocationEntry(location.getLatitude(), location.getLongitude());
}
- /**
- * Adds a data item to the data Layer storage
+ /*
+ * Adds a data item to the data Layer storage.
*/
private void addLocationEntry(double latitude, double longitude) {
- if (!mSaveGpsLocation || !mGoogleApiClient.isConnected()) {
+ if (!mGpsPermissionApproved || !mGoogleApiClient.isConnected()) {
return;
}
mCalendar.setTimeInMillis(System.currentTimeMillis());
@@ -315,29 +421,56 @@ public class WearableMainActivity extends Activity implements GoogleApiClient.Co
});
}
+ /**
+ * Handles user choices for both speed limit and location permissions (GPS tracking).
+ */
@Override
- protected void onStop() {
- super.onStop();
- if (mGoogleApiClient.isConnected()) {
- LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+ if (requestCode == REQUEST_PICK_SPEED_LIMIT) {
+ if (resultCode == RESULT_OK) {
+ // The user updated the speed limit.
+ int newSpeedLimit =
+ data.getIntExtra(SpeedPickerActivity.EXTRA_NEW_SPEED_LIMIT, mSpeedLimit);
+
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putInt(WearableMainActivity.PREFS_SPEED_LIMIT_KEY, newSpeedLimit);
+ editor.apply();
+
+ mSpeedLimit = newSpeedLimit;
+
+ updateSpeedInViews();
+ }
}
- mGoogleApiClient.disconnect();
}
+ /**
+ * Callback received when a permissions request has been completed.
+ */
@Override
- protected void onResume() {
- super.onResume();
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mCalendar = Calendar.getInstance();
- setSpeedLimit();
- adjustColor();
- updateRecordingIcon();
- }
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+
+ Log.d(TAG, "onRequestPermissionsResult(): " + permissions);
- private void updateRecordingIcon() {
- mSaveGpsLocation = LocationSettingActivity.getGpsRecordingStatusFromPreferences(this);
- mSaveImageView.setImageResource(mSaveGpsLocation ? R.drawable.ic_gps_saving_grey600_96dp
- : R.drawable.ic_gps_not_saving_grey600_96dp);
+
+ if (requestCode == REQUEST_GPS_PERMISSION) {
+ Log.i(TAG, "Received response for GPS permission request.");
+
+ if ((grantResults.length == 1)
+ && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
+ Log.i(TAG, "GPS permission granted.");
+ mGpsPermissionApproved = true;
+ } else {
+ Log.i(TAG, "GPS permission NOT granted.");
+ mGpsPermissionApproved = false;
+ }
+
+ updateActivityViewsBasedOnLocationPermissions();
+
+ }
}
/**
@@ -346,4 +479,4 @@ public class WearableMainActivity extends Activity implements GoogleApiClient.Co
private boolean hasGps() {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
}
-}
+} \ No newline at end of file
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/LocationSettingActivity.java b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/LocationSettingActivity.java
deleted file mode 100644
index 1f8be71c..00000000
--- a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/LocationSettingActivity.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 Google Inc. All Rights Reserved.
- *
- * 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.speedtracker.ui;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.View;
-import android.widget.TextView;
-
-import com.example.android.wearable.speedtracker.R;
-
-/**
- * A simple activity that allows the user to start or stop recording of GPS location data.
- */
-public class LocationSettingActivity extends Activity {
-
- private static final String PREFS_KEY_SAVE_GPS = "save-gps";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.saving_activity);
- TextView textView = (TextView) findViewById(R.id.textView);
- textView.setText(getGpsRecordingStatusFromPreferences(this) ? R.string.stop_saving_gps
- : R.string.start_saving_gps);
-
- }
-
- public void onClick(View view) {
- switch (view.getId()) {
- case R.id.submitBtn:
- saveGpsRecordingStatusToPreferences(LocationSettingActivity.this,
- !getGpsRecordingStatusFromPreferences(this));
- break;
- case R.id.cancelBtn:
- break;
- }
- finish();
- }
-
- /**
- * Get the persisted value for whether the app should record the GPS location data or not. If
- * there is no prior value persisted, it returns {@code false}.
- */
- public static boolean getGpsRecordingStatusFromPreferences(Context context) {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
- return pref.getBoolean(PREFS_KEY_SAVE_GPS, false);
- }
-
- /**
- * Persists the user selection to whether save the GPS location data or not.
- */
- public static void saveGpsRecordingStatusToPreferences(Context context, boolean value) {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
- pref.edit().putBoolean(PREFS_KEY_SAVE_GPS, value).apply();
-
- }
-}
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/SpeedPickerListAdapter.java b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/SpeedPickerListAdapter.java
index e3b284bf..df25a6a8 100644
--- a/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/SpeedPickerListAdapter.java
+++ b/wearable/wear/SpeedTracker/Wearable/src/main/java/com/example/android/wearable/speedtracker/ui/SpeedPickerListAdapter.java
@@ -41,6 +41,9 @@ public class SpeedPickerListAdapter extends WearableListView.Adapter {
mDataSet = dataset;
}
+ /**
+ * Displays all possible speed limit choices.
+ */
public static class ItemViewHolder extends WearableListView.ViewHolder {
private TextView mTextView;
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/main_activity.xml b/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/main_activity.xml
index a1b9081a..a2b678eb 100644
--- a/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/main_activity.xml
+++ b/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/main_activity.xml
@@ -29,11 +29,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
+ android:paddingLeft="16dp"
android:fontFamily="sans-serif-light"
+ android:textAlignment="center"
android:textSize="17sp"
android:textStyle="italic"
- android:id="@+id/acquiring_gps"
- android:text="@string/acquiring_gps"/>
+ android:id="@+id/gps_issue_text"
+ android:text=""/>
<TextView
android:layout_width="wrap_content"
@@ -84,18 +86,20 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_gps_not_saving_grey600_96dp"
- android:id="@+id/saving"
+ android:id="@+id/gps_permission"
+ android:onClick="onGpsPermissionClick"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="20dp"
- android:layout_marginLeft="60dp" />
+ android:layout_marginLeft="50dp" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/settings"
+ android:id="@+id/speed_limit_setting"
+ android:onClick="onSpeedLimitClick"
android:background="@drawable/settings"
android:layout_alignParentRight="true"
- android:layout_alignBottom="@+id/saving"
- android:layout_marginRight="60dp"/>
+ android:layout_alignBottom="@+id/gps_permission"
+ android:layout_marginRight="50dp"/>
</RelativeLayout>
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/saving_activity.xml b/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/saving_activity.xml
deleted file mode 100644
index c37d9593..00000000
--- a/wearable/wear/SpeedTracker/Wearable/src/main/res/layout/saving_activity.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent" android:layout_height="match_parent">
- <View
- android:id="@+id/center"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_centerInParent="true"/>
- <TextView
- android:id="@+id/textView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_above="@id/center"
- android:layout_marginBottom="18dp"
- android:fontFamily="sans-serif-light"
- android:textSize="18sp"
- android:text="@string/start_saving_gps"/>
- <android.support.wearable.view.CircledImageView
- android:id="@+id/cancelBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_below="@id/center"
- android:layout_toLeftOf="@id/center"
- android:layout_marginEnd="10dp"
- android:src="@drawable/ic_cancel_80"
- app:circle_color="@color/grey"
- android:onClick="onClick"
- app:circle_padding="@dimen/circle_padding"
- app:circle_radius="@dimen/circle_radius"
- app:circle_radius_pressed="@dimen/circle_radius_pressed" />
- <android.support.wearable.view.CircledImageView
- android:id="@+id/submitBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_below="@id/center"
- android:layout_toRightOf="@id/center"
- android:layout_marginStart="10dp"
- android:src="@drawable/ic_confirmation_80"
- app:circle_color="@color/blue"
- android:onClick="onClick"
- app:circle_padding="@dimen/circle_padding"
- app:circle_radius="@dimen/circle_radius"
- app:circle_radius_pressed="@dimen/circle_radius_pressed" />
-</RelativeLayout> \ No newline at end of file
diff --git a/wearable/wear/SpeedTracker/Wearable/src/main/res/values/strings.xml b/wearable/wear/SpeedTracker/Wearable/src/main/res/values/strings.xml
index dda3ecd6..b0c37478 100644
--- a/wearable/wear/SpeedTracker/Wearable/src/main/res/values/strings.xml
+++ b/wearable/wear/SpeedTracker/Wearable/src/main/res/values/strings.xml
@@ -25,11 +25,16 @@
<string name="speed_limit">Limit: %1$d mph</string>
<string name="acquiring_gps">Acquiring GPS Fix ...</string>
<string name="speed_for_list">%1$d mph</string>
- <string name="start_saving_gps">Start Recording GPS?</string>
- <string name="stop_saving_gps">Stop Recording GPS?</string>
+
+ <string name="enable_disable_gps_label">Enable Location Permission?</string>
+
<string name="mph">mph</string>
<string name="speed_limit_header">Speed Limit</string>
- <string name="gps_not_available">GPS not available.</string>
+ <string name="gps_not_available">No GPS on device. Will use phone GPS when available.</string>
<string name="ok">OK</string>
<string name="speed_format">%.0f</string>
+
+ <string name="permission_rationale">App requires location permission to function, tap GPS icon.</string>
+
+
</resources>
diff --git a/wearable/wear/SpeedTracker/template-params.xml b/wearable/wear/SpeedTracker/template-params.xml
index 1cf31619..8730de13 100644
--- a/wearable/wear/SpeedTracker/template-params.xml
+++ b/wearable/wear/SpeedTracker/template-params.xml
@@ -23,15 +23,17 @@
<package>com.example.android.wearable.speedtracker</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>23</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
</wearable>
- <dependency>com.google.android.gms:play-services-location:7.3.0</dependency>
- <dependency_wearable>com.google.android.gms:play-services-location:7.3.0</dependency_wearable>
+ <dependency>com.android.support:design:23.0.1</dependency>
+ <dependency>com.google.android.gms:play-services-location:8.1.0</dependency>
+ <dependency_wearable>com.google.android.gms:play-services-location:8.1.0</dependency_wearable>
<strings>
<intro>
diff --git a/wearable/wear/SynchronizedNotifications/Application/src/main/AndroidManifest.xml b/wearable/wear/SynchronizedNotifications/Application/src/main/AndroidManifest.xml
index 1737c7da..04a69e01 100644
--- a/wearable/wear/SynchronizedNotifications/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/SynchronizedNotifications/Application/src/main/AndroidManifest.xml
@@ -23,7 +23,7 @@
android:versionName="1.0">
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="23" />
<application android:allowBackup="true"
android:label="@string/app_name"
diff --git a/wearable/wear/SynchronizedNotifications/Wearable/src/main/AndroidManifest.xml b/wearable/wear/SynchronizedNotifications/Wearable/src/main/AndroidManifest.xml
index f9b0d9c8..5c4d259d 100644
--- a/wearable/wear/SynchronizedNotifications/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/SynchronizedNotifications/Wearable/src/main/AndroidManifest.xml
@@ -20,7 +20,7 @@
package="com.example.android.wearable.synchronizednotifications">
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/SynchronizedNotifications/template-params.xml b/wearable/wear/SynchronizedNotifications/template-params.xml
index 799b2267..97c22441 100644
--- a/wearable/wear/SynchronizedNotifications/template-params.xml
+++ b/wearable/wear/SynchronizedNotifications/template-params.xml
@@ -23,7 +23,8 @@
<package>com.example.android.wearable.synchronizednotifications</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/Timer/Wearable/src/main/AndroidManifest.xml b/wearable/wear/Timer/Wearable/src/main/AndroidManifest.xml
index 364fb5a6..59634fc4 100644
--- a/wearable/wear/Timer/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/Timer/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.wearable.timer" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/Timer/template-params.xml b/wearable/wear/Timer/template-params.xml
index 11884028..a3293f30 100644
--- a/wearable/wear/Timer/template-params.xml
+++ b/wearable/wear/Timer/template-params.xml
@@ -22,8 +22,7 @@
<group>Wearable</group>
<package>com.example.android.wearable.timer</package>
- <minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
diff --git a/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml b/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml
index 5433c94f..723e4e57 100644
--- a/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml
@@ -18,13 +18,19 @@
package="com.example.android.wearable.watchface" >
<uses-sdk android:minSdkVersion="18"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="23" />
<!-- Permissions required by the wearable app -->
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <!-- Requests to calendar are only made on the wear side (CalendarWatchFaceService.java), so
+ no runtime permissions are needed on the phone side. -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
+ <!-- Location permission used by FitDistanceWatchFaceService -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
<!-- All intent-filters for config actions must include the categories
com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION and
android.intent.category.DEFAULT. -->
@@ -55,6 +61,18 @@
</intent-filter>
</activity>
+ <!-- This activity is needed to allow the user to authorize Google Fit for the Fit Distance
+ WatchFace (required to view distance). -->
+ <activity
+ android:name=".FitDistanceWatchFaceConfigActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="com.example.android.wearable.watchface.CONFIG_FIT_DISTANCE" />
+ <category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity
android:name=".OpenGLWatchFaceConfigActivity"
android:label="@string/app_name">
diff --git a/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceConfigActivity.java b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceConfigActivity.java
new file mode 100644
index 00000000..1d8e4c9b
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceConfigActivity.java
@@ -0,0 +1,255 @@
+package com.example.android.wearable.watchface;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.Scopes;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.common.api.Scope;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.fitness.Fitness;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Switch;
+import android.widget.Toast;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Allows users of the Fit WatchFace to tie their Google Fit account to the WatchFace.
+ */
+public class FitDistanceWatchFaceConfigActivity extends Activity implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener {
+
+ private static final String TAG = "FitDistanceConfig";
+
+ // Request code for launching the Intent to resolve authorization.
+ private static final int REQUEST_OAUTH = 1;
+
+ // Shared Preference used to record if the user has enabled Google Fit previously.
+ private static final String PREFS_FIT_ENABLED_BY_USER =
+ "com.example.android.wearable.watchface.preferences.FIT_ENABLED_BY_USER";
+
+ /* Tracks whether an authorization activity is stacking over the current activity, i.e., when
+ * a known auth error is being resolved, such as showing the account chooser or presenting a
+ * consent dialog. This avoids common duplications as might happen on screen rotations, etc.
+ */
+ private static final String EXTRA_AUTH_STATE_PENDING =
+ "com.example.android.wearable.watchface.extra.AUTH_STATE_PENDING";
+
+ private static final long FIT_DISABLE_TIMEOUT_SECS = TimeUnit.SECONDS.toMillis(5);;
+
+ private boolean mResolvingAuthorization;
+
+ private boolean mFitEnabled;
+
+ private GoogleApiClient mGoogleApiClient;
+
+ private Switch mFitAuthSwitch;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_fit_watch_face_config);
+
+ mFitAuthSwitch = (Switch) findViewById(R.id.fit_auth_switch);
+
+ if (savedInstanceState != null) {
+ mResolvingAuthorization =
+ savedInstanceState.getBoolean(EXTRA_AUTH_STATE_PENDING, false);
+ } else {
+ mResolvingAuthorization = false;
+ }
+
+ // Checks if user previously enabled/approved Google Fit.
+ SharedPreferences sharedPreferences = getPreferences(Context.MODE_PRIVATE);
+ mFitEnabled =
+ sharedPreferences.getBoolean(PREFS_FIT_ENABLED_BY_USER, false);
+
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addApi(Fitness.HISTORY_API)
+ .addApi(Fitness.RECORDING_API)
+ .addApi(Fitness.CONFIG_API)
+ .addScope(new Scope(Scopes.FITNESS_LOCATION_READ_WRITE))
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .build();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ if ((mFitEnabled) && (mGoogleApiClient != null)) {
+
+ mFitAuthSwitch.setChecked(true);
+ mFitAuthSwitch.setEnabled(true);
+
+ mGoogleApiClient.connect();
+
+ } else {
+
+ mFitAuthSwitch.setChecked(false);
+ mFitAuthSwitch.setEnabled(true);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if ((mGoogleApiClient != null) && (mGoogleApiClient.isConnected())) {
+ mGoogleApiClient.disconnect();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle bundle) {
+ super.onSaveInstanceState(bundle);
+ bundle.putBoolean(EXTRA_AUTH_STATE_PENDING, mResolvingAuthorization);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mResolvingAuthorization =
+ savedInstanceState.getBoolean(EXTRA_AUTH_STATE_PENDING, false);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(TAG, "onActivityResult()");
+
+ if (requestCode == REQUEST_OAUTH) {
+ mResolvingAuthorization = false;
+
+ if (resultCode == RESULT_OK) {
+ setUserFitPreferences(true);
+
+ if (!mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected()) {
+ mGoogleApiClient.connect();
+ }
+ } else {
+ // User cancelled authorization, reset the switch.
+ setUserFitPreferences(false);
+ }
+ }
+ }
+
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ Log.d(TAG, "onConnected: " + connectionHint);
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+
+ if (cause == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
+ Log.i(TAG, "Connection lost. Cause: Network Lost.");
+ } else if (cause == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
+ Log.i(TAG, "Connection lost. Reason: Service Disconnected");
+ } else {
+ Log.i(TAG, "onConnectionSuspended: " + cause);
+ }
+
+ mFitAuthSwitch.setChecked(false);
+ mFitAuthSwitch.setEnabled(true);
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ Log.d(TAG, "Connection to Google Fit failed. Cause: " + result.toString());
+
+ if (!result.hasResolution()) {
+ // User cancelled authorization, reset the switch.
+ mFitAuthSwitch.setChecked(false);
+ mFitAuthSwitch.setEnabled(true);
+ // Show the localized error dialog
+ GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show();
+ return;
+ }
+
+ // Resolve failure if not already trying/authorizing.
+ if (!mResolvingAuthorization) {
+ try {
+ Log.i(TAG, "Attempting to resolve failed GoogleApiClient connection");
+ mResolvingAuthorization = true;
+ result.startResolutionForResult(this, REQUEST_OAUTH);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "Exception while starting resolution activity", e);
+ }
+ }
+ }
+
+ public void onSwitchClicked(View view) {
+
+ boolean userWantsToEnableFit = mFitAuthSwitch.isChecked();
+
+ if (userWantsToEnableFit) {
+
+ Log.d(TAG, "User wants to enable Fit.");
+ if ((mGoogleApiClient != null) && (!mGoogleApiClient.isConnected())) {
+ mGoogleApiClient.connect();
+ }
+
+ } else {
+ Log.d(TAG, "User wants to disable Fit.");
+
+ // Disable switch until disconnect request is finished.
+ mFitAuthSwitch.setEnabled(false);
+
+ PendingResult<Status> pendingResult = Fitness.ConfigApi.disableFit(mGoogleApiClient);
+
+ pendingResult.setResultCallback(new ResultCallback<Status>() {
+ @Override
+ public void onResult(Status status) {
+
+ if (status.isSuccess()) {
+ Toast.makeText(
+ FitDistanceWatchFaceConfigActivity.this,
+ "Disconnected from Google Fit.",
+ Toast.LENGTH_LONG).show();
+
+ setUserFitPreferences(false);
+
+ mGoogleApiClient.disconnect();
+
+
+ } else {
+ Toast.makeText(
+ FitDistanceWatchFaceConfigActivity.this,
+ "Unable to disconnect from Google Fit. See logcat for details.",
+ Toast.LENGTH_LONG).show();
+
+ // Re-set the switch since auth failed.
+ setUserFitPreferences(true);
+ }
+ }
+ }, FIT_DISABLE_TIMEOUT_SECS, TimeUnit.SECONDS);
+ }
+ }
+
+ private void setUserFitPreferences(boolean userFitPreferences) {
+
+ mFitEnabled = userFitPreferences;
+ SharedPreferences sharedPreferences = getPreferences(Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(PREFS_FIT_ENABLED_BY_USER, userFitPreferences);
+ editor.commit();
+
+ mFitAuthSwitch.setChecked(userFitPreferences);
+ mFitAuthSwitch.setEnabled(true);
+ }
+}
diff --git a/wearable/wear/WatchFace/Application/src/main/res/layout/activity_fit_watch_face_config.xml b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_fit_watch_face_config.xml
new file mode 100644
index 00000000..73d14891
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_fit_watch_face_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ tools:context="com.example.android.wearable.watchface.FitDistanceWatchFaceConfigActivity">
+
+ <Switch
+ android:id="@+id/fit_auth_switch"
+ android:text="@string/fit_config_switch_text"
+ android:enabled="false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:onClick="onSwitchClicked"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/wearable/wear/WatchFace/Application/src/main/res/values-w820dp/dimens.xml b/wearable/wear/WatchFace/Application/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 00000000..63fc8164
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/wearable/wear/WatchFace/Application/src/main/res/values/dimens.xml b/wearable/wear/WatchFace/Application/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..56dca871
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/values/dimens.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+<!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+ <dimen name="activity_vertical_margin">10dp</dimen>
+</resources>
diff --git a/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml b/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml
index 6c6834f0..275dcd3f 100644
--- a/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml
+++ b/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml
@@ -22,6 +22,8 @@
<string name="digital_config_minutes">Minutes</string>
<string name="digital_config_seconds">Seconds</string>
+ <string name="fit_config_switch_text">Google Fit</string>
+
<string name="title_no_device_connected">No wearable device is currently connected.</string>
<string name="ok_no_device_connected">OK</string>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml b/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml
index c96d7305..026107e7 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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.
@@ -13,12 +14,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.wearable.watchface" >
+ package="com.example.android.wearable.watchface" >
- <uses-sdk android:minSdkVersion="21"
- android:targetSdkVersion="21" />
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="23" />
<uses-feature android:name="android.hardware.type.watch" />
@@ -29,102 +30,113 @@
<!-- Calendar permission used by CalendarWatchFaceService -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
+ <!-- Location permission used by FitDistanceWatchFaceService -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
<application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+
+ <meta-data
+ android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+
+ <uses-library android:name="com.google.android.wearable" android:required="false" />
<service
- android:name=".AnalogWatchFaceService"
- android:label="@string/analog_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".AnalogWatchFaceService"
+ android:label="@string/analog_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview"
- android:resource="@drawable/preview_analog" />
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_analog" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
- android:resource="@drawable/preview_analog_circular" />
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_analog_circular" />
<meta-data
- android:name="com.google.android.wearable.watchface.companionConfigurationAction"
- android:value="com.example.android.wearable.watchface.CONFIG_ANALOG" />
+ android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+ android:value="com.example.android.wearable.watchface.CONFIG_ANALOG" />
+
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
+
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
-
<service
- android:name=".SweepWatchFaceService"
- android:label="@string/sweep_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".SweepWatchFaceService"
+ android:label="@string/sweep_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview"
- android:resource="@drawable/preview_analog" />
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_analog" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
- android:resource="@drawable/preview_analog_circular" />
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_analog_circular" />
+
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
+
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
-
<service
- android:name=".OpenGLWatchFaceService"
- android:label="@string/opengl_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".OpenGLWatchFaceService"
+ android:label="@string/opengl_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview"
- android:resource="@drawable/preview_opengl" />
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_opengl" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
- android:resource="@drawable/preview_opengl_circular" />
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_opengl_circular" />
<meta-data
- android:name="com.google.android.wearable.watchface.companionConfigurationAction"
- android:value="com.example.android.wearable.watchface.CONFIG_OPENGL" />
+ android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+ android:value="com.example.android.wearable.watchface.CONFIG_OPENGL" />
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
+
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
-
<service
- android:name=".CardBoundsWatchFaceService"
- android:label="@string/card_bounds_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".CardBoundsWatchFaceService"
+ android:label="@string/card_bounds_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview"
- android:resource="@drawable/preview_card_bounds" />
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_card_bounds" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
- android:resource="@drawable/preview_card_bounds_circular" />
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_card_bounds_circular" />
<meta-data
- android:name="com.google.android.wearable.watchface.companionConfigurationAction"
- android:value="com.example.android.wearable.watchface.CONFIG_CARD_BOUNDS" />
+ android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+ android:value="com.example.android.wearable.watchface.CONFIG_CARD_BOUNDS" />
+
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
+
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
-
-
<service
- android:name=".InteractiveWatchFaceService"
- android:label="@string/interactive_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".InteractiveWatchFaceService"
+ android:label="@string/interactive_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@@ -134,81 +146,130 @@
<meta-data
android:name="com.google.android.wearable.watchface.preview_circular"
android:resource="@drawable/preview_interactive_circular" />
+
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
- <category
- android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
-
<service
- android:name=".DigitalWatchFaceService"
- android:label="@string/digital_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".DigitalWatchFaceService"
+ android:label="@string/digital_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview"
- android:resource="@drawable/preview_digital" />
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_digital" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
- android:resource="@drawable/preview_digital_circular" />
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_digital_circular" />
<meta-data
- android:name="com.google.android.wearable.watchface.companionConfigurationAction"
- android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+ android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+ android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
<meta-data
- android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
- android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+ android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
+ android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
+
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
- <!-- All intent-filters for config actions must include the categories
+ <!--
+ All intent-filters for config actions must include the categories
com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION
- and android.intent.category.DEFAULT. -->
+ and android.intent.category.DEFAULT.
+ -->
<activity
- android:name=".DigitalWatchFaceWearableConfigActivity"
- android:label="@string/digital_config_name">
+ android:name=".DigitalWatchFaceWearableConfigActivity"
+ android:label="@string/digital_config_name" >
<intent-filter>
<action android:name="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+
<category android:name="com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
- android:name=".CalendarWatchFaceService"
- android:label="@string/calendar_name"
- android:permission="android.permission.BIND_WALLPAPER" >
+ android:name=".CalendarWatchFaceService"
+ android:label="@string/calendar_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview"
- android:resource="@drawable/preview_calendar" />
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_calendar" />
<meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
- android:resource="@drawable/preview_calendar_circular" />
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_calendar_circular" />
+
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
+
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
-
- <service android:name=".DigitalWatchFaceConfigListenerService">
+ <service android:name=".DigitalWatchFaceConfigListenerService" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
+ <service
+ android:name=".FitDistanceWatchFaceService"
+ android:label="@string/fit_distance_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_distance" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_distance_circular" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+ android:value="com.example.android.wearable.watchface.CONFIG_FIT_DISTANCE" />
- <meta-data
- android:name="com.google.android.gms.version"
- android:value="@integer/google_play_services_version" />
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+ </intent-filter>
+ </service>
+ <service
+ android:name=".FitStepsWatchFaceService"
+ android:label="@string/fit_steps_name"
+ android:permission="android.permission.BIND_WALLPAPER" >
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_fit" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_fit_circular" />
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+ </intent-filter>
+ </service>
+
+ <activity
+ android:name=".CalendarWatchFacePermissionActivity"
+ android:label="@string/title_activity_calendar_watch_face_permission" >
+ </activity>
</application>
</manifest>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFacePermissionActivity.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFacePermissionActivity.java
new file mode 100644
index 00000000..7effd33d
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFacePermissionActivity.java
@@ -0,0 +1,56 @@
+package com.example.android.wearable.watchface;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.wearable.activity.WearableActivity;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Simple Activity for displaying Calendar Permission Rationale to user.
+ */
+public class CalendarWatchFacePermissionActivity extends WearableActivity {
+
+ private static final String TAG = "PermissionActivity";
+
+ /* Id to identify permission request for calendar. */
+ private static final int PERMISSION_REQUEST_READ_CALENDAR = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_calendar_watch_face_permission);
+ setAmbientEnabled();
+ }
+
+ public void onClickEnablePermission(View view) {
+ Log.d(TAG, "onClickEnablePermission()");
+
+ // On 23+ (M+) devices, GPS permission not granted. Request permission.
+ ActivityCompat.requestPermissions(
+ this,
+ new String[]{Manifest.permission.READ_CALENDAR},
+ PERMISSION_REQUEST_READ_CALENDAR);
+
+ }
+
+ /*
+ * Callback received when a permissions request has been completed.
+ */
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+
+ Log.d(TAG, "onRequestPermissionsResult()");
+
+ if (requestCode == PERMISSION_REQUEST_READ_CALENDAR) {
+ if ((grantResults.length == 1)
+ && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
+ finish();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java
index a8ab9556..98a251cd 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java
@@ -16,11 +16,13 @@
package com.example.android.wearable.watchface;
+import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -30,8 +32,10 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
+import android.support.v4.app.ActivityCompat;
import android.support.wearable.provider.WearableCalendarContract;
import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.DynamicLayout;
import android.text.Editable;
@@ -74,31 +78,37 @@ public class CalendarWatchFaceService extends CanvasWatchFaceService {
final TextPaint mTextPaint = new TextPaint();
int mNumMeetings;
+ private boolean mCalendarPermissionApproved;
+ private String mCalendarNotApprovedMessage;
private AsyncTask<Void, Void, Integer> mLoadMeetingsTask;
+ private boolean mIsReceiverRegistered;
+
/** 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();
+
+ // Loads meetings.
+ if (mCalendarPermissionApproved) {
+ 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);
}
}
@@ -106,29 +116,59 @@ public class CalendarWatchFaceService extends CanvasWatchFaceService {
@Override
public void onCreate(SurfaceHolder holder) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onCreate");
- }
super.onCreate(holder);
+ Log.d(TAG, "onCreate");
+
+ mCalendarNotApprovedMessage =
+ getResources().getString(R.string.calendar_permission_not_approved);
+
+ /* Accepts tap events to allow permission changes by user. */
setWatchFaceStyle(new WatchFaceStyle.Builder(CalendarWatchFaceService.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
+ .setAcceptsTapEvents(true)
.build());
mTextPaint.setColor(FOREGROUND_COLOR);
mTextPaint.setTextSize(TEXT_SIZE);
- mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
+ // Enables app to handle 23+ (M+) style permissions.
+ mCalendarPermissionApproved =
+ ActivityCompat.checkSelfPermission(
+ getApplicationContext(),
+ Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED;
+
+ if (mCalendarPermissionApproved) {
+ mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
+ }
}
@Override
public void onDestroy() {
mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
- cancelLoadMeetingTask();
super.onDestroy();
}
+ /*
+ * Captures tap event (and tap type) and increments correct tap type total.
+ */
+ @Override
+ public void onTapCommand(int tapType, int x, int y, long eventTime) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Tap Command: " + tapType);
+ }
+
+ // Ignore lint error (fixed in wearable support library 1.4)
+ if (tapType == WatchFaceService.TAP_TYPE_TAP && !mCalendarPermissionApproved) {
+ Intent permissionIntent = new Intent(
+ getApplicationContext(),
+ CalendarWatchFacePermissionActivity.class);
+ permissionIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(permissionIntent);
+ }
+ }
+
@Override
public void onDraw(Canvas canvas, Rect bounds) {
// Create or update mLayout if necessary.
@@ -141,8 +181,13 @@ public class CalendarWatchFaceService extends CanvasWatchFaceService {
// Update the contents of mEditable.
mEditable.clear();
- mEditable.append(Html.fromHtml(getResources().getQuantityString(
- R.plurals.calendar_meetings, mNumMeetings, mNumMeetings)));
+
+ if (mCalendarPermissionApproved) {
+ mEditable.append(Html.fromHtml(getResources().getQuantityString(
+ R.plurals.calendar_meetings, mNumMeetings, mNumMeetings)));
+ } else {
+ mEditable.append(Html.fromHtml(mCalendarNotApprovedMessage));
+ }
// Draw the text on a solid background.
canvas.drawColor(BACKGROUND_COLOR);
@@ -151,15 +196,24 @@ public class CalendarWatchFaceService extends CanvasWatchFaceService {
@Override
public void onVisibilityChanged(boolean visible) {
+ Log.d(TAG, "onVisibilityChanged()");
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);
+ // Enables app to handle 23+ (M+) style permissions.
+ mCalendarPermissionApproved = ActivityCompat.checkSelfPermission(
+ getApplicationContext(),
+ Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED;
+
+ if (mCalendarPermissionApproved) {
+ 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);
@@ -204,9 +258,9 @@ public class CalendarWatchFaceService extends CanvasWatchFaceService {
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);
- }
+
+ Log.d(TAG, "Num meetings: " + numMeetings);
+
return numMeetings;
}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceService.java
new file mode 100644
index 00000000..b29a1902
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitDistanceWatchFaceService.java
@@ -0,0 +1,533 @@
+/*
+ * 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.Color;
+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.WatchFaceStyle;
+import android.text.format.DateFormat;
+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.Scopes;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.common.api.Scope;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.fitness.Fitness;
+import com.google.android.gms.fitness.FitnessStatusCodes;
+import com.google.android.gms.fitness.data.DataPoint;
+import com.google.android.gms.fitness.data.DataType;
+import com.google.android.gms.fitness.data.Field;
+import com.google.android.gms.fitness.result.DailyTotalResult;
+
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Displays the user's daily distance total via Google Fit. Distance is polled initially when the
+ * Google API Client successfully connects and once a minute after that via the onTimeTick callback.
+ * If you want more frequent updates, you will want to add your own Handler.
+ *
+ * Authentication IS a requirement to request distance from Google Fit on Wear. Otherwise, distance
+ * will always come back as zero (or stay at whatever the distance was prior to you
+ * de-authorizing watchface).
+ *
+ * In ambient mode, the seconds are replaced with an AM/PM indicator.
+ *
+ * On devices with low-bit ambient mode, the text is drawn without anti-aliasing. On devices which
+ * require burn-in protection, the hours are drawn in normal rather than bold.
+ *
+ */
+public class FitDistanceWatchFaceService extends CanvasWatchFaceService {
+
+ private static final String TAG = "DistanceWatchFace";
+
+ 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 active mode (non-ambient).
+ */
+ private static final long ACTIVE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener,
+ ResultCallback<DailyTotalResult> {
+
+ private static final int BACKGROUND_COLOR = Color.BLACK;
+ private static final int TEXT_HOURS_MINS_COLOR = Color.WHITE;
+ private static final int TEXT_SECONDS_COLOR = Color.GRAY;
+ private static final int TEXT_AM_PM_COLOR = Color.GRAY;
+ private static final int TEXT_COLON_COLOR = Color.GRAY;
+ private static final int TEXT_DISTANCE_COUNT_COLOR = Color.GRAY;
+
+ private static final String COLON_STRING = ":";
+
+ private static final int MSG_UPDATE_TIME = 0;
+
+ /* Handler to update the time periodically in interactive mode. */
+ private final Handler mUpdateTimeHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_TIME:
+ Log.v(TAG, "updating time");
+ invalidate();
+ if (shouldUpdateTimeHandlerBeRunning()) {
+ long timeMs = System.currentTimeMillis();
+ long delayMs =
+ ACTIVE_INTERVAL_MS - (timeMs % ACTIVE_INTERVAL_MS);
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+ }
+ break;
+ }
+ }
+ };
+
+ /**
+ * Handles time zone and locale changes.
+ */
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
+ }
+ };
+
+ /**
+ * Unregistering an unregistered receiver throws an exception. Keep track of the
+ * registration state to prevent that.
+ */
+ private boolean mRegisteredReceiver = false;
+
+ private Paint mHourPaint;
+ private Paint mMinutePaint;
+ private Paint mSecondPaint;
+ private Paint mAmPmPaint;
+ private Paint mColonPaint;
+ private Paint mDistanceCountPaint;
+
+ private float mColonWidth;
+
+ private Calendar mCalendar;
+
+ private float mXOffset;
+ private float mXDistanceOffset;
+ private float mYOffset;
+ private float mLineHeight;
+
+ private String mAmString;
+ private String mPmString;
+
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ private boolean mLowBitAmbient;
+
+ /*
+ * Google API Client used to make Google Fit requests for step data.
+ */
+ private GoogleApiClient mGoogleApiClient;
+
+ private boolean mDistanceRequested;
+
+ private float mDistanceTotal = 0;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ Log.d(TAG, "onCreate");
+
+ super.onCreate(holder);
+
+ mDistanceRequested = false;
+ mGoogleApiClient = new GoogleApiClient.Builder(FitDistanceWatchFaceService.this)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .addApi(Fitness.HISTORY_API)
+ .addApi(Fitness.RECORDING_API)
+ .addScope(new Scope(Scopes.FITNESS_LOCATION_READ))
+ // When user has multiple accounts, useDefaultAccount() allows Google Fit to
+ // associated with the main account for steps. It also replaces the need for
+ // a scope request.
+ .useDefaultAccount()
+ .build();
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(FitDistanceWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+
+ Resources resources = getResources();
+
+ mYOffset = resources.getDimension(R.dimen.fit_y_offset);
+ mLineHeight = resources.getDimension(R.dimen.fit_line_height);
+ mAmString = resources.getString(R.string.fit_am);
+ mPmString = resources.getString(R.string.fit_pm);
+
+ mHourPaint = createTextPaint(TEXT_HOURS_MINS_COLOR, BOLD_TYPEFACE);
+ mMinutePaint = createTextPaint(TEXT_HOURS_MINS_COLOR);
+ mSecondPaint = createTextPaint(TEXT_SECONDS_COLOR);
+ mAmPmPaint = createTextPaint(TEXT_AM_PM_COLOR);
+ mColonPaint = createTextPaint(TEXT_COLON_COLOR);
+ mDistanceCountPaint = createTextPaint(TEXT_DISTANCE_COUNT_COLOR);
+
+ mCalendar = Calendar.getInstance();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ super.onDestroy();
+ }
+
+ private Paint createTextPaint(int color) {
+ return createTextPaint(color, NORMAL_TYPEFACE);
+ }
+
+ private Paint createTextPaint(int color, Typeface typeface) {
+ Paint paint = new Paint();
+ paint.setColor(color);
+ paint.setTypeface(typeface);
+ paint.setAntiAlias(true);
+ return paint;
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ Log.d(TAG, "onVisibilityChanged: " + visible);
+
+ super.onVisibilityChanged(visible);
+
+ if (visible) {
+ mGoogleApiClient.connect();
+
+ registerReceiver();
+
+ // Update time zone and date formats, in case they changed while we weren't visible.
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ } else {
+ unregisterReceiver();
+
+ if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+ 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 (mRegisteredReceiver) {
+ return;
+ }
+ mRegisteredReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ FitDistanceWatchFaceService.this.registerReceiver(mReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredReceiver) {
+ return;
+ }
+ mRegisteredReceiver = false;
+ FitDistanceWatchFaceService.this.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void onApplyWindowInsets(WindowInsets insets) {
+ Log.d(TAG, "onApplyWindowInsets: " + (insets.isRound() ? "round" : "square"));
+
+ super.onApplyWindowInsets(insets);
+
+ // Load resources that have alternate values for round watches.
+ Resources resources = FitDistanceWatchFaceService.this.getResources();
+ boolean isRound = insets.isRound();
+ mXOffset = resources.getDimension(isRound
+ ? R.dimen.fit_x_offset_round : R.dimen.fit_x_offset);
+ mXDistanceOffset =
+ resources.getDimension(
+ isRound ?
+ R.dimen.fit_steps_or_distance_x_offset_round :
+ R.dimen.fit_steps_or_distance_x_offset);
+ float textSize = resources.getDimension(isRound
+ ? R.dimen.fit_text_size_round : R.dimen.fit_text_size);
+ float amPmSize = resources.getDimension(isRound
+ ? R.dimen.fit_am_pm_size_round : R.dimen.fit_am_pm_size);
+
+ mHourPaint.setTextSize(textSize);
+ mMinutePaint.setTextSize(textSize);
+ mSecondPaint.setTextSize(textSize);
+ mAmPmPaint.setTextSize(amPmSize);
+ mColonPaint.setTextSize(textSize);
+ mDistanceCountPaint.setTextSize(
+ resources.getDimension(R.dimen.fit_steps_or_distance_text_size));
+
+ 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);
+
+ Log.d(TAG, "onPropertiesChanged: burn-in protection = " + burnInProtection
+ + ", low-bit ambient = " + mLowBitAmbient);
+
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+ getTotalDistance();
+ invalidate();
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ super.onAmbientModeChanged(inAmbientMode);
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+
+ if (mLowBitAmbient) {
+ boolean antiAlias = !inAmbientMode;;
+ mHourPaint.setAntiAlias(antiAlias);
+ mMinutePaint.setAntiAlias(antiAlias);
+ mSecondPaint.setAntiAlias(antiAlias);
+ mAmPmPaint.setAntiAlias(antiAlias);
+ mColonPaint.setAntiAlias(antiAlias);
+ mDistanceCountPaint.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 String formatTwoDigitNumber(int hour) {
+ return String.format("%02d", hour);
+ }
+
+ private String getAmPmString(int amPm) {
+ return amPm == Calendar.AM ? mAmString : mPmString;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ long now = System.currentTimeMillis();
+ mCalendar.setTimeInMillis(now);
+ boolean is24Hour = DateFormat.is24HourFormat(FitDistanceWatchFaceService.this);
+
+ // Draw the background.
+ canvas.drawColor(BACKGROUND_COLOR);
+
+ // Draw the hours.
+ float x = mXOffset;
+ String hourString;
+ if (is24Hour) {
+ hourString = formatTwoDigitNumber(mCalendar.get(Calendar.HOUR_OF_DAY));
+ } else {
+ int hour = mCalendar.get(Calendar.HOUR);
+ if (hour == 0) {
+ hour = 12;
+ }
+ hourString = String.valueOf(hour);
+ }
+ canvas.drawText(hourString, x, mYOffset, mHourPaint);
+ x += mHourPaint.measureText(hourString);
+
+ // Draw first colon (between hour and minute).
+ canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+
+ x += mColonWidth;
+
+ // Draw the minutes.
+ String minuteString = formatTwoDigitNumber(mCalendar.get(Calendar.MINUTE));
+ canvas.drawText(minuteString, x, mYOffset, mMinutePaint);
+ x += mMinutePaint.measureText(minuteString);
+
+ // In interactive mode, draw a second colon followed by the seconds.
+ // Otherwise, if we're in 12-hour mode, draw AM/PM
+ if (!isInAmbientMode()) {
+ canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+
+ x += mColonWidth;
+ canvas.drawText(formatTwoDigitNumber(
+ mCalendar.get(Calendar.SECOND)), x, mYOffset, mSecondPaint);
+ } else if (!is24Hour) {
+ x += mColonWidth;
+ canvas.drawText(getAmPmString(
+ mCalendar.get(Calendar.AM_PM)), x, mYOffset, mAmPmPaint);
+ }
+
+ // Only render distance if there is no peek card, so they do not bleed into each other
+ // in ambient mode.
+ if (getPeekCardPosition().isEmpty()) {
+ canvas.drawText(
+ getString(R.string.fit_distance, mDistanceTotal),
+ mXDistanceOffset,
+ mYOffset + mLineHeight,
+ mDistanceCountPaint);
+ }
+ }
+
+ /**
+ * 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() {
+ Log.d(TAG, "updateTimer");
+
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ if (shouldUpdateTimeHandlerBeRunning()) {
+ 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 shouldUpdateTimeHandlerBeRunning() {
+ return isVisible() && !isInAmbientMode();
+ }
+
+ private void getTotalDistance() {
+
+ Log.d(TAG, "getTotalDistance()");
+
+ if ((mGoogleApiClient != null)
+ && (mGoogleApiClient.isConnected())
+ && (!mDistanceRequested)) {
+
+ mDistanceRequested = true;
+
+ PendingResult<DailyTotalResult> distanceResult =
+ Fitness.HistoryApi.readDailyTotal(
+ mGoogleApiClient,
+ DataType.TYPE_DISTANCE_DELTA);
+
+ distanceResult.setResultCallback(this);
+ }
+ }
+
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onConnected: " + connectionHint);
+
+ mDistanceRequested = false;
+
+ // Subscribe covers devices that do not have Google Fit installed.
+ subscribeToDistance();
+
+ getTotalDistance();
+ }
+
+ /*
+ * Subscribes to distance.
+ */
+ private void subscribeToDistance() {
+
+ if ((mGoogleApiClient != null) && (mGoogleApiClient.isConnecting())) {
+
+ Fitness.RecordingApi.subscribe(mGoogleApiClient, DataType.TYPE_DISTANCE_DELTA)
+ .setResultCallback(new ResultCallback<Status>() {
+ @Override
+ public void onResult(Status status) {
+ if (status.isSuccess()) {
+ if (status.getStatusCode()
+ == FitnessStatusCodes.SUCCESS_ALREADY_SUBSCRIBED) {
+ Log.i(TAG, "Existing subscription for activity detected.");
+ } else {
+ Log.i(TAG, "Successfully subscribed!");
+ }
+ } else {
+ Log.i(TAG, "There was a problem subscribing.");
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onConnectionSuspended: " + cause);
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onConnectionFailed: " + result);
+ }
+
+ @Override
+ public void onResult(DailyTotalResult dailyTotalResult) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onResult(): " + dailyTotalResult);
+
+ mDistanceRequested = false;
+
+ if (dailyTotalResult.getStatus().isSuccess()) {
+
+ List<DataPoint> points = dailyTotalResult.getTotal().getDataPoints();
+
+ if (!points.isEmpty()) {
+ mDistanceTotal = points.get(0).getValue(Field.FIELD_DISTANCE).asFloat();
+ Log.d(TAG, "distance updated: " + mDistanceTotal);
+ }
+ } else {
+ Log.e(TAG, "onResult() failed! " + dailyTotalResult.getStatus().getStatusMessage());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitStepsWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitStepsWatchFaceService.java
new file mode 100644
index 00000000..1f7b298f
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/FitStepsWatchFaceService.java
@@ -0,0 +1,542 @@
+/*
+ * 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 com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.fitness.Fitness;
+import com.google.android.gms.fitness.FitnessStatusCodes;
+import com.google.android.gms.fitness.data.DataPoint;
+import com.google.android.gms.fitness.data.DataType;
+import com.google.android.gms.fitness.data.Field;
+import com.google.android.gms.fitness.result.DailyTotalResult;
+
+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.Color;
+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.WatchFaceStyle;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.WindowInsets;
+
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The step count watch face shows user's daily step total via Google Fit (matches Google Fit app).
+ * Steps are polled initially when the Google API Client successfully connects and once a minute
+ * after that via the onTimeTick callback. If you want more frequent updates, you will want to add
+ * your own Handler.
+ *
+ * Authentication is not a requirement to request steps from Google Fit on Wear.
+ *
+ * In ambient mode, the seconds are replaced with an AM/PM indicator.
+ *
+ * On devices with low-bit ambient mode, the text is drawn without anti-aliasing. On devices which
+ * require burn-in protection, the hours are drawn in normal rather than bold.
+ *
+ */
+public class FitStepsWatchFaceService extends CanvasWatchFaceService {
+
+ private static final String TAG = "StepCountWatchFace";
+
+ 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 active mode (non-ambient).
+ */
+ private static final long ACTIVE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener,
+ ResultCallback<DailyTotalResult> {
+
+ private static final int BACKGROUND_COLOR = Color.BLACK;
+ private static final int TEXT_HOURS_MINS_COLOR = Color.WHITE;
+ private static final int TEXT_SECONDS_COLOR = Color.GRAY;
+ private static final int TEXT_AM_PM_COLOR = Color.GRAY;
+ private static final int TEXT_COLON_COLOR = Color.GRAY;
+ private static final int TEXT_STEP_COUNT_COLOR = Color.GRAY;
+
+ private static final String COLON_STRING = ":";
+
+ private static final int MSG_UPDATE_TIME = 0;
+
+ /* Handler to update the time periodically in interactive mode. */
+ private 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 (shouldUpdateTimeHandlerBeRunning()) {
+ long timeMs = System.currentTimeMillis();
+ long delayMs =
+ ACTIVE_INTERVAL_MS - (timeMs % ACTIVE_INTERVAL_MS);
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+ }
+ break;
+ }
+ }
+ };
+
+ /**
+ * Handles time zone and locale changes.
+ */
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
+ }
+ };
+
+ /**
+ * Unregistering an unregistered receiver throws an exception. Keep track of the
+ * registration state to prevent that.
+ */
+ private boolean mRegisteredReceiver = false;
+
+ private Paint mHourPaint;
+ private Paint mMinutePaint;
+ private Paint mSecondPaint;
+ private Paint mAmPmPaint;
+ private Paint mColonPaint;
+ private Paint mStepCountPaint;
+
+ private float mColonWidth;
+
+ private Calendar mCalendar;
+
+ private float mXOffset;
+ private float mXStepsOffset;
+ private float mYOffset;
+ private float mLineHeight;
+
+ private String mAmString;
+ private String mPmString;
+
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ private boolean mLowBitAmbient;
+
+ /*
+ * Google API Client used to make Google Fit requests for step data.
+ */
+ private GoogleApiClient mGoogleApiClient;
+
+ private boolean mStepsRequested;
+
+ private int mStepsTotal = 0;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+
+ super.onCreate(holder);
+
+ mStepsRequested = false;
+ mGoogleApiClient = new GoogleApiClient.Builder(FitStepsWatchFaceService.this)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .addApi(Fitness.HISTORY_API)
+ .addApi(Fitness.RECORDING_API)
+ // When user has multiple accounts, useDefaultAccount() allows Google Fit to
+ // associated with the main account for steps. It also replaces the need for
+ // a scope request.
+ .useDefaultAccount()
+ .build();
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(FitStepsWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+
+ Resources resources = getResources();
+
+ mYOffset = resources.getDimension(R.dimen.fit_y_offset);
+ mLineHeight = resources.getDimension(R.dimen.fit_line_height);
+ mAmString = resources.getString(R.string.fit_am);
+ mPmString = resources.getString(R.string.fit_pm);
+
+ mHourPaint = createTextPaint(TEXT_HOURS_MINS_COLOR, BOLD_TYPEFACE);
+ mMinutePaint = createTextPaint(TEXT_HOURS_MINS_COLOR);
+ mSecondPaint = createTextPaint(TEXT_SECONDS_COLOR);
+ mAmPmPaint = createTextPaint(TEXT_AM_PM_COLOR);
+ mColonPaint = createTextPaint(TEXT_COLON_COLOR);
+ mStepCountPaint = createTextPaint(TEXT_STEP_COUNT_COLOR);
+
+ mCalendar = Calendar.getInstance();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ super.onDestroy();
+ }
+
+ private Paint createTextPaint(int color) {
+ return createTextPaint(color, NORMAL_TYPEFACE);
+ }
+
+ private Paint createTextPaint(int color, Typeface typeface) {
+ Paint paint = new Paint();
+ paint.setColor(color);
+ 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 and date formats, in case they changed while we weren't visible.
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ } else {
+ unregisterReceiver();
+
+ if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+ 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 (mRegisteredReceiver) {
+ return;
+ }
+ mRegisteredReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ FitStepsWatchFaceService.this.registerReceiver(mReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredReceiver) {
+ return;
+ }
+ mRegisteredReceiver = false;
+ FitStepsWatchFaceService.this.unregisterReceiver(mReceiver);
+ }
+
+ @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 = FitStepsWatchFaceService.this.getResources();
+ boolean isRound = insets.isRound();
+ mXOffset = resources.getDimension(isRound
+ ? R.dimen.fit_x_offset_round : R.dimen.fit_x_offset);
+ mXStepsOffset = resources.getDimension(isRound
+ ? R.dimen.fit_steps_or_distance_x_offset_round : R.dimen.fit_steps_or_distance_x_offset);
+ float textSize = resources.getDimension(isRound
+ ? R.dimen.fit_text_size_round : R.dimen.fit_text_size);
+ float amPmSize = resources.getDimension(isRound
+ ? R.dimen.fit_am_pm_size_round : R.dimen.fit_am_pm_size);
+
+ mHourPaint.setTextSize(textSize);
+ mMinutePaint.setTextSize(textSize);
+ mSecondPaint.setTextSize(textSize);
+ mAmPmPaint.setTextSize(amPmSize);
+ mColonPaint.setTextSize(textSize);
+ mStepCountPaint.setTextSize(resources.getDimension(R.dimen.fit_steps_or_distance_text_size));
+
+ 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());
+ }
+
+ getTotalSteps();
+ 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);
+ mAmPmPaint.setAntiAlias(antiAlias);
+ mColonPaint.setAntiAlias(antiAlias);
+ mStepCountPaint.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 String formatTwoDigitNumber(int hour) {
+ return String.format("%02d", hour);
+ }
+
+ private String getAmPmString(int amPm) {
+ return amPm == Calendar.AM ? mAmString : mPmString;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ long now = System.currentTimeMillis();
+ mCalendar.setTimeInMillis(now);
+ boolean is24Hour = DateFormat.is24HourFormat(FitStepsWatchFaceService.this);
+
+ // Draw the background.
+ canvas.drawColor(BACKGROUND_COLOR);
+
+ // Draw the hours.
+ float x = mXOffset;
+ String hourString;
+ if (is24Hour) {
+ hourString = formatTwoDigitNumber(mCalendar.get(Calendar.HOUR_OF_DAY));
+ } else {
+ int hour = mCalendar.get(Calendar.HOUR);
+ if (hour == 0) {
+ hour = 12;
+ }
+ hourString = String.valueOf(hour);
+ }
+ canvas.drawText(hourString, x, mYOffset, mHourPaint);
+ x += mHourPaint.measureText(hourString);
+
+ // Draw first colon (between hour and minute).
+ canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+
+ x += mColonWidth;
+
+ // Draw the minutes.
+ String minuteString = formatTwoDigitNumber(mCalendar.get(Calendar.MINUTE));
+ canvas.drawText(minuteString, x, mYOffset, mMinutePaint);
+ x += mMinutePaint.measureText(minuteString);
+
+ // In interactive mode, draw a second colon followed by the seconds.
+ // Otherwise, if we're in 12-hour mode, draw AM/PM
+ if (!isInAmbientMode()) {
+ canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+
+ x += mColonWidth;
+ canvas.drawText(formatTwoDigitNumber(
+ mCalendar.get(Calendar.SECOND)), x, mYOffset, mSecondPaint);
+ } else if (!is24Hour) {
+ x += mColonWidth;
+ canvas.drawText(getAmPmString(
+ mCalendar.get(Calendar.AM_PM)), x, mYOffset, mAmPmPaint);
+ }
+
+ // Only render steps if there is no peek card, so they do not bleed into each other
+ // in ambient mode.
+ if (getPeekCardPosition().isEmpty()) {
+ canvas.drawText(
+ getString(R.string.fit_steps, mStepsTotal),
+ mXStepsOffset,
+ mYOffset + mLineHeight,
+ mStepCountPaint);
+ }
+ }
+
+ /**
+ * 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 (shouldUpdateTimeHandlerBeRunning()) {
+ 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 shouldUpdateTimeHandlerBeRunning() {
+ return isVisible() && !isInAmbientMode();
+ }
+
+ private void getTotalSteps() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "getTotalSteps()");
+ }
+
+ if ((mGoogleApiClient != null)
+ && (mGoogleApiClient.isConnected())
+ && (!mStepsRequested)) {
+
+ mStepsRequested = true;
+
+ PendingResult<DailyTotalResult> stepsResult =
+ Fitness.HistoryApi.readDailyTotal(
+ mGoogleApiClient,
+ DataType.TYPE_STEP_COUNT_DELTA);
+
+ stepsResult.setResultCallback(this);
+ }
+ }
+
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onConnected: " + connectionHint);
+ }
+ mStepsRequested = false;
+
+ // The subscribe step covers devices that do not have Google Fit installed.
+ subscribeToSteps();
+
+ getTotalSteps();
+ }
+
+ /*
+ * Subscribes to step count (for phones that don't have Google Fit app).
+ */
+ private void subscribeToSteps() {
+ Fitness.RecordingApi.subscribe(mGoogleApiClient, DataType.TYPE_STEP_COUNT_DELTA)
+ .setResultCallback(new ResultCallback<Status>() {
+ @Override
+ public void onResult(Status status) {
+ if (status.isSuccess()) {
+ if (status.getStatusCode()
+ == FitnessStatusCodes.SUCCESS_ALREADY_SUBSCRIBED) {
+ Log.i(TAG, "Existing subscription for activity detected.");
+ } else {
+ Log.i(TAG, "Successfully subscribed!");
+ }
+ } else {
+ Log.i(TAG, "There was a problem subscribing.");
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onConnectionSuspended: " + cause);
+ }
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onConnectionFailed: " + result);
+ }
+ }
+
+ @Override
+ public void onResult(DailyTotalResult dailyTotalResult) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "mGoogleApiAndFitCallbacks.onResult(): " + dailyTotalResult);
+ }
+
+ mStepsRequested = false;
+
+ if (dailyTotalResult.getStatus().isSuccess()) {
+
+ List<DataPoint> points = dailyTotalResult.getTotal().getDataPoints();;
+
+ if (!points.isEmpty()) {
+ mStepsTotal = points.get(0).getValue(Field.FIELD_STEPS).asInt();
+ Log.d(TAG, "steps updated: " + mStepsTotal);
+ }
+ } else {
+ Log.e(TAG, "onResult() failed! " + dailyTotalResult.getStatus().getStatusMessage());
+ }
+ }
+ }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 00000000..6bae68f5
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance.png
new file mode 100644
index 00000000..a96f3557
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance_circular.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance_circular.png
new file mode 100644
index 00000000..912d85bb
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_distance_circular.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit.png
new file mode 100644
index 00000000..04b8b5e9
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit_circular.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit_circular.png
new file mode 100644
index 00000000..b421e289
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_fit_circular.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 00000000..3f47b54c
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 00000000..cbe9e1cd
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 00000000..1d1b0f4d
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_calendar_watch_face_permission.xml b/wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_calendar_watch_face_permission.xml
new file mode 100644
index 00000000..bf0e3f67
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_calendar_watch_face_permission.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.wearable.view.BoxInsetLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/container"
+ android:background="@color/white"
+ android:paddingTop="32dp"
+ android:paddingLeft="36dp"
+ android:paddingRight="22dp"
+ tools:context="com.example.android.wearable.watchface.CalendarWatchFacePermissionActivity"
+ tools:deviceIds="wear">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:onClick="onClickEnablePermission"
+ android:orientation="vertical"
+ app:layout_box="all">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="16sp"
+ android:paddingBottom="18dp"
+ android:textColor="#000000"
+ android:text="@string/calendar_permission_text"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <android.support.wearable.view.CircledImageView
+ android:id="@+id/circle"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ app:circle_radius="20dp"
+ app:circle_color="#0086D4"
+ android:src="@drawable/ic_lock_open_white_24dp"/>
+
+ <android.support.v4.widget.Space
+ android:layout_width="8dp"
+ android:layout_height="8dp"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="16sp"
+ android:textColor="#0086D4"
+ android:text="Enable Permission"/>
+
+
+ </LinearLayout>
+
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout> \ No newline at end of file
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml b/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml
index 4973466e..0b0672b6 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml
@@ -32,4 +32,15 @@
<dimen name="interactive_y_offset">72dp</dimen>
<dimen name="interactive_y_offset_round">84dp</dimen>
<dimen name="interactive_line_height">25dp</dimen>
+ <dimen name="fit_text_size">40dp</dimen>
+ <dimen name="fit_text_size_round">45dp</dimen>
+ <dimen name="fit_steps_or_distance_text_size">20dp</dimen>
+ <dimen name="fit_am_pm_size">25dp</dimen>
+ <dimen name="fit_am_pm_size_round">30dp</dimen>
+ <dimen name="fit_x_offset">15dp</dimen>
+ <dimen name="fit_x_offset_round">25dp</dimen>
+ <dimen name="fit_steps_or_distance_x_offset">20dp</dimen>
+ <dimen name="fit_steps_or_distance_x_offset_round">30dp</dimen>
+ <dimen name="fit_y_offset">80dp</dimen>
+ <dimen name="fit_line_height">25dp</dimen>
</resources>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml b/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
index 19bc3e7f..4090995d 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
@@ -25,11 +25,22 @@
<string name="digital_config_name">Digital watch face configuration</string>
<string name="digital_am">AM</string>
<string name="digital_pm">PM</string>
+
+ <string name="fit_steps_name">Sample Fit Steps</string>
+ <string name="fit_distance_name">Sample Fit Distance</string>
+ <string name="fit_am">AM</string>
+ <string name="fit_pm">PM</string>
+ <string name="fit_steps">%1$d steps</string>
+ <string name="fit_distance">%1$,.2f meters</string>
+
<string name="calendar_name">Sample Calendar</string>
+ <string name="calendar_permission_not_approved">&lt;br&gt;&lt;br&gt;&lt;br&gt;WatchFace requires Calendar permission. Click on this WatchFace or visit Settings &gt; Permissions to approve.</string>
<plurals name="calendar_meetings">
<item quantity="one">&lt;br&gt;&lt;br&gt;&lt;br&gt;You have &lt;b&gt;%1$d&lt;/b&gt; meeting in the next 24 hours.</item>
<item quantity="other">&lt;br&gt;&lt;br&gt;&lt;br&gt;You have &lt;b&gt;%1$d&lt;/b&gt; meetings in the next 24 hours.</item>
</plurals>
+ <string name="title_activity_calendar_watch_face_permission">Calendar Permission Activity</string>
+ <string name="calendar_permission_text">WatchFace requires Calendar access.</string>
<!-- TODO: this should be shared (needs covering all the samples with Gradle build model) -->
<string name="color_black">Black</string>
diff --git a/wearable/wear/WatchFace/template-params.xml b/wearable/wear/WatchFace/template-params.xml
index cedf8db0..15df71a3 100644
--- a/wearable/wear/WatchFace/template-params.xml
+++ b/wearable/wear/WatchFace/template-params.xml
@@ -23,10 +23,13 @@
<package>com.example.android.wearable.watchface</package>
<minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>23</targetSdkVersionWear>
- <dependency_wearable>com.android.support:palette-v7:21.0.0</dependency_wearable>
+ <dependency_wearable>com.android.support:palette-v7:23.1.0</dependency_wearable>
<dependency>com.google.android.support:wearable:1.3.0</dependency>
+ <dependency>com.google.android.gms:play-services-fitness:8.3.0</dependency>
+ <dependency_wearable>com.google.android.gms:play-services-fitness:8.3.0</dependency_wearable>
<wearable>
<has_handheld_app>true</has_handheld_app>
@@ -37,8 +40,13 @@
<![CDATA[
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, interactive, etc. It also includes a watch-side configuration example.
+opengl, calendar, steps, interactive, etc. It also includes a watch-side configuration example.
The phone app includes a phone-side configuration example.
+
+Additional note on Steps WatchFace Sample, if the user has not installed or setup the Google Fit app
+on their phone and their Wear device has not configured the Google Fit Wear App, then you may get
+zero steps until one of the two is setup. Please note, many Wear devices configure the Google Fit
+Wear App beforehand.
]]>
</intro>
</strings>
diff --git a/wearable/wear/WatchViewStub/Wearable/src/main/AndroidManifest.xml b/wearable/wear/WatchViewStub/Wearable/src/main/AndroidManifest.xml
index 33a266d6..774817ba 100644
--- a/wearable/wear/WatchViewStub/Wearable/src/main/AndroidManifest.xml
+++ b/wearable/wear/WatchViewStub/Wearable/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.example.android.google.wearable.watchviewstub" >
<uses-sdk android:minSdkVersion="20"
- android:targetSdkVersion="21" />
+ android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />
diff --git a/wearable/wear/WatchViewStub/template-params.xml b/wearable/wear/WatchViewStub/template-params.xml
index ff7e7f83..39568aa9 100644
--- a/wearable/wear/WatchViewStub/template-params.xml
+++ b/wearable/wear/WatchViewStub/template-params.xml
@@ -19,8 +19,7 @@
<group>Wearable</group>
<package>com.example.android.google.wearable.watchviewstub</package>
- <minSdk>18</minSdk>
- <targetSdkVersion>22</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<strings>
<intro>
diff --git a/wearable/wear/WearSpeakerSample/CONTRIBUTING b/wearable/wear/WearSpeakerSample/CONTRIBUTING
new file mode 100644
index 00000000..fe1f5883
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/CONTRIBUTING
@@ -0,0 +1,34 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement (CLA).
+
+ * If you are an individual writing original source code and you're sure you
+ own the intellectual property, then you'll need to sign an [individual CLA]
+ (https://cla.developers.google.com).
+ * If you work for a company that wants to allow you to contribute your work,
+ then you'll need to sign a [corporate CLA]
+ (https://cla.developers.google.com).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+ Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+ you are contributing. Refer to the
+ [Android Code Style Guide]
+ (https://source.android.com/source/code-style.html) for the
+ recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
diff --git a/wearable/wear/WearSpeakerSample/build.gradle b/wearable/wear/WearSpeakerSample/build.gradle
new file mode 100644
index 00000000..25ba12af
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../../build"
+ pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/wearable/wear/WearSpeakerSample/buildSrc/build.gradle b/wearable/wear/WearSpeakerSample/buildSrc/build.gradle
new file mode 100644
index 00000000..7cebf71c
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/wearable/wear/WearSpeakerSample/gradle.properties b/wearable/wear/WearSpeakerSample/gradle.properties
new file mode 100644
index 00000000..1d3591c8
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true \ No newline at end of file
diff --git a/wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.jar b/wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..8c0fb64a
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.properties b/wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..1a127e91
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Oct 04 13:39:51 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
diff --git a/wearable/wear/WearSpeakerSample/gradlew b/wearable/wear/WearSpeakerSample/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/wearable/wear/WearSpeakerSample/gradlew.bat b/wearable/wear/WearSpeakerSample/gradlew.bat
new file mode 100644
index 00000000..aec99730
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/wearable/wear/WearSpeakerSample/screenshots/1.png b/wearable/wear/WearSpeakerSample/screenshots/1.png
new file mode 100644
index 00000000..d98e6fa3
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/screenshots/1.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/screenshots/2.png b/wearable/wear/WearSpeakerSample/screenshots/2.png
new file mode 100644
index 00000000..226da1f8
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/screenshots/2.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/screenshots/3.png b/wearable/wear/WearSpeakerSample/screenshots/3.png
new file mode 100644
index 00000000..d7467d78
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/screenshots/3.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/screenshots/4.png b/wearable/wear/WearSpeakerSample/screenshots/4.png
new file mode 100644
index 00000000..1044ee16
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/screenshots/4.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/settings.gradle b/wearable/wear/WearSpeakerSample/settings.gradle
new file mode 100644
index 00000000..8d97c994
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+include ':wear'
diff --git a/wearable/wear/WearSpeakerSample/template-params.xml b/wearable/wear/WearSpeakerSample/template-params.xml
new file mode 100644
index 00000000..f07d4c5d
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/template-params.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+
+ 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.
+-->
+<sample>
+ <name>WearSpeakerSample</name>
+ <group>Wearable</group>
+ <package>com.example.android.wearable.speaker</package>
+
+ <strings>
+ <intro>
+<![CDATA[
+A sample that shows how you can record voice using the microphone on a wearable and
+play the recorded voice or an mp3 file, if the wearable device has a built-in speaker.
+
+This sample doesn't have any companion phone app so you need to install this directly
+on your watch (using "adb").
+]]>
+ </intro>
+ </strings>
+
+ <template src="unmanaged" />
+
+ <metadata>
+ <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+ <status>PUBLISHED</status>
+ <!-- See http://go/sample-categories for details on the next 4 fields. -->
+ <categories>Wearable</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <!-- Values: {BEGINNER | INTERMEDIATE | ADVANCED | EXPERT} -->
+ <level>INTERMEDIATE</level>
+ <!-- Dimensions: 512x512, PNG fomrat -->
+ <icon>screenshots/1.png</icon>
+ <!-- Path to screenshots. Use <img> tags for each. -->
+ <!-- <screenshots>
+ <img>screenshots/composite-1.png</img>
+ </screenshots> -->
+ <!-- List of APIs that this sample should be cross-referenced under. Use <android>
+ for fully-qualified Framework class names ("android:" namespace).
+
+ Use <ext> for custom namespaces, if needed. See "Samples Index API" documentation
+ for more details. -->
+ <api_refs>
+ <android>android.media.AudioTrack</android>
+ <android>android.media.AudioRecord</android>
+ </api_refs>
+
+ <!-- 1-3 line description of the sample here.
+
+ Avoid simply rearranging the sample's title. What does this sample actually
+ accomplish, and how does it do it? -->
+ <description>
+<![CDATA[
+A sample that shows how you can record voice using the microphone on a wearable and
+play the recorded voice or an mp3 file, if the wearable device has a built-in speaker.
+
+This sample doesn't have any companion phone app so you need to install this directly
+on your watch (using "adb").
+]]>
+ </description>
+
+ <!-- Multi-paragraph introduction to sample, from an educational point-of-view.
+ Makrdown formatting allowed. This will be used to generate a mini-article for the
+ sample on DAC. -->
+ <!-- <intro>
+<![CDATA[
+]]>
+ </intro> -->
+ </metadata>
+</sample>
diff --git a/wearable/wear/WearSpeakerSample/wear/.gitignore b/wearable/wear/WearSpeakerSample/wear/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/wearable/wear/WearSpeakerSample/wear/build.gradle b/wearable/wear/WearSpeakerSample/wear/build.gradle
new file mode 100644
index 00000000..8d3e550f
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+apply plugin: 'com.android.application'
+
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ defaultConfig {
+ applicationId "com.example.android.wearable.speaker"
+ minSdkVersion 21
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile 'com.google.android.support:wearable:1.3.0'
+ compile 'com.google.android.gms:play-services-wearable:8.3.0'
+ compile 'com.android.support:appcompat-v7:23.1.0'
+
+}
diff --git a/wearable/wear/WearSpeakerSample/wear/proguard-rules.pro b/wearable/wear/WearSpeakerSample/wear/proguard-rules.pro
new file mode 100644
index 00000000..002bc05a
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${SDK}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/AndroidManifest.xml b/wearable/wear/WearSpeakerSample/wear/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..135d3e08
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.wearable.speaker" >
+
+ <uses-feature android:name="android.hardware.type.watch" />
+
+ <!-- the following permission is required to record audio using a microphone -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@android:style/Theme.DeviceDefault" >
+ <uses-library android:name="com.google.android.wearable" android:required="false" />
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java
new file mode 100644
index 00000000..e7a4870f
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.speaker;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.wearable.activity.WearableActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * We first get the required permission to use the MIC. If it is granted, then we continue with
+ * the application and present the UI with three icons: a MIC icon (if pressed, user can record up
+ * to 10 seconds), a Play icon (if clicked, it wil playback the recorded audio file) and a music
+ * note icon (if clicked, it plays an MP3 file that is included in the app).
+ */
+public class MainActivity extends WearableActivity implements UIAnimation.UIStateListener,
+ SoundRecorder.OnVoicePlaybackStateChangedListener {
+
+ private static final String TAG = "MainActivity";
+ private static final int PERMISSIONS_REQUEST_CODE = 100;
+ private static final long COUNT_DOWN_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final long MILLIS_IN_SECOND = TimeUnit.SECONDS.toMillis(1);
+ private static final String VOICE_FILE_NAME = "audiorecord.pcm";
+ private MediaPlayer mMediaPlayer;
+ private AppState mState = AppState.READY;
+ private UIAnimation.UIState mUiState = UIAnimation.UIState.HOME;
+ private SoundRecorder mSoundRecorder;
+
+ private UIAnimation mUIAnimation;
+ private ProgressBar mProgressBar;
+ private CountDownTimer mCountDownTimer;
+
+ enum AppState {
+ READY, PLAYING_VOICE, PLAYING_MUSIC, RECORDING
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+ mProgressBar = (ProgressBar) findViewById(R.id.progress);
+ mProgressBar.setMax((int) (COUNT_DOWN_MS / MILLIS_IN_SECOND));
+ setAmbientEnabled();
+ }
+
+ private void setProgressBar(long progressInMillis) {
+ mProgressBar.setProgress((int) (progressInMillis / MILLIS_IN_SECOND));
+ }
+
+ @Override
+ public void onUIStateChanged(UIAnimation.UIState state) {
+ Log.d(TAG, "UI State is: " + state);
+ if (mUiState == state) {
+ return;
+ }
+ switch (state) {
+ case MUSIC_UP:
+ mState = AppState.PLAYING_MUSIC;
+ mUiState = state;
+ playMusic();
+ break;
+ case MIC_UP:
+ mState = AppState.RECORDING;
+ mUiState = state;
+ mSoundRecorder.startRecording();
+ setProgressBar(COUNT_DOWN_MS);
+ mCountDownTimer = new CountDownTimer(COUNT_DOWN_MS, MILLIS_IN_SECOND) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ mProgressBar.setVisibility(View.VISIBLE);
+ setProgressBar(millisUntilFinished);
+ Log.d(TAG, "Time Left: " + millisUntilFinished / MILLIS_IN_SECOND);
+ }
+
+ @Override
+ public void onFinish() {
+ mProgressBar.setProgress(0);
+ mProgressBar.setVisibility(View.INVISIBLE);
+ mSoundRecorder.stopRecording();
+ mUIAnimation.transitionToHome();
+ mUiState = UIAnimation.UIState.HOME;
+ mState = AppState.READY;
+ mCountDownTimer = null;
+ }
+ };
+ mCountDownTimer.start();
+ break;
+ case SOUND_UP:
+ mState = AppState.PLAYING_VOICE;
+ mUiState = state;
+ mSoundRecorder.startPlay();
+ break;
+ case HOME:
+ switch (mState) {
+ case PLAYING_MUSIC:
+ mState = AppState.READY;
+ mUiState = state;
+ stopMusic();
+ break;
+ case PLAYING_VOICE:
+ mState = AppState.READY;
+ mUiState = state;
+ mSoundRecorder.stopPlaying();
+ break;
+ case RECORDING:
+ mState = AppState.READY;
+ mUiState = state;
+ mSoundRecorder.stopRecording();
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ mCountDownTimer = null;
+ }
+ mProgressBar.setVisibility(View.INVISIBLE);
+ setProgressBar(COUNT_DOWN_MS);
+ break;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Plays back the MP3 file embedded in the application
+ */
+ private void playMusic() {
+ if (mMediaPlayer == null) {
+ mMediaPlayer = MediaPlayer.create(this, R.raw.sound);
+ mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ // we need to transition to the READY/Home state
+ Log.d(TAG, "Music Finished");
+ mUIAnimation.transitionToHome();
+ }
+ });
+ }
+ mMediaPlayer.start();
+ }
+
+ /**
+ * Stops the playback of the MP3 file.
+ */
+ private void stopMusic() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ }
+
+ /**
+ * Checks the permission that this app needs and if it has not been granted, it will
+ * prompt the user to grant it, otherwise it shuts down the app.
+ */
+ private void checkPermissions() {
+ boolean recordAudioPermissionGranted =
+ ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
+ == PackageManager.PERMISSION_GRANTED;
+
+ if (recordAudioPermissionGranted) {
+ start();
+ } else {
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.RECORD_AUDIO},
+ PERMISSIONS_REQUEST_CODE);
+ }
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ String permissions[], int[] grantResults) {
+ if (requestCode == PERMISSIONS_REQUEST_CODE) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ start();
+ } else {
+ // Permission has been denied before. At this point we should show a dialog to
+ // user and explain why this permission is needed and direct him to go to the
+ // Permissions settings for the app in the System settings. For this sample, we
+ // simply exit to get to the important part.
+ Toast.makeText(this, R.string.exiting_for_permissions, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+ }
+
+ /**
+ * Starts the main flow of the application.
+ */
+ private void start() {
+ mSoundRecorder = new SoundRecorder(this, VOICE_FILE_NAME, this);
+ int[] thumbResources = new int[] {R.id.mic, R.id.play, R.id.music};
+ ImageView[] thumbs = new ImageView[3];
+ for(int i=0; i < 3; i++) {
+ thumbs[i] = (ImageView) findViewById(thumbResources[i]);
+ }
+ View containerView = findViewById(R.id.container);
+ ImageView expandedView = (ImageView) findViewById(R.id.expanded);
+ int animationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
+ mUIAnimation = new UIAnimation(containerView, thumbs, expandedView, animationDuration,
+ this);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (speakerIsSupported()) {
+ checkPermissions();
+ } else {
+ findViewById(R.id.container2).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Toast.makeText(MainActivity.this, R.string.no_speaker_supported,
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ if (mSoundRecorder != null) {
+ mSoundRecorder.cleanup();
+ mSoundRecorder = null;
+ }
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onPlaybackStopped() {
+ mUIAnimation.transitionToHome();
+ mUiState = UIAnimation.UIState.HOME;
+ mState = AppState.READY;
+ }
+
+ /**
+ * Determines if the wear device has a built-in speaker and if it is supported. Speaker, even if
+ * physically present, is only supported in Android M+ on a wear device..
+ */
+ public final boolean speakerIsSupported() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PackageManager packageManager = getPackageManager();
+ // The results from AudioManager.getDevices can't be trusted unless the device
+ // advertises FEATURE_AUDIO_OUTPUT.
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ return false;
+ }
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : devices) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java
new file mode 100644
index 00000000..a45bdd27
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.speaker;
+
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.media.MediaRecorder;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * A helper class to provide methods to record audio input from the MIC to the internal storage
+ * and to playback the same recorded audio file.
+ */
+public class SoundRecorder {
+
+ private static final String TAG = "SoundRecorder";
+ private static final int RECORDING_RATE = 8000; // can go up to 44K, if needed
+ private static final int CHANNEL_IN = AudioFormat.CHANNEL_IN_MONO;
+ private static final int CHANNELS_OUT = AudioFormat.CHANNEL_OUT_MONO;
+ private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+ private static int BUFFER_SIZE = AudioRecord
+ .getMinBufferSize(RECORDING_RATE, CHANNEL_IN, FORMAT);
+
+ private final String mOutputFileName;
+ private final AudioManager mAudioManager;
+ private final Handler mHandler;
+ private final Context mContext;
+ private State mState = State.IDLE;
+
+ private OnVoicePlaybackStateChangedListener mListener;
+ private AsyncTask<Void, Void, Void> mRecordingAsyncTask;
+ private AsyncTask<Void, Void, Void> mPlayingAsyncTask;
+
+ enum State {
+ IDLE, RECORDING, PLAYING
+ }
+
+ public SoundRecorder(Context context, String outputFileName,
+ OnVoicePlaybackStateChangedListener listener) {
+ mOutputFileName = outputFileName;
+ mListener = listener;
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mHandler = new Handler(Looper.getMainLooper());
+ mContext = context;
+ }
+
+ /**
+ * Starts recording from the MIC.
+ */
+ public void startRecording() {
+ if (mState != State.IDLE) {
+ Log.w(TAG, "Requesting to start recording while state was not IDLE");
+ return;
+ }
+
+ mRecordingAsyncTask = new AsyncTask<Void, Void, Void>() {
+
+ private AudioRecord mAudioRecord;
+
+ @Override
+ protected void onPreExecute() {
+ mState = State.RECORDING;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
+ RECORDING_RATE, CHANNEL_IN, FORMAT, BUFFER_SIZE * 3);
+ BufferedOutputStream bufferedOutputStream = null;
+ try {
+ bufferedOutputStream = new BufferedOutputStream(
+ mContext.openFileOutput(mOutputFileName, Context.MODE_PRIVATE));
+ byte[] buffer = new byte[BUFFER_SIZE];
+ mAudioRecord.startRecording();
+ while (!isCancelled()) {
+ int read = mAudioRecord.read(buffer, 0, buffer.length);
+ bufferedOutputStream.write(buffer, 0, read);
+ }
+ } catch (IOException | NullPointerException | IndexOutOfBoundsException e) {
+ Log.e(TAG, "Failed to record data: " + e);
+ } finally {
+ if (bufferedOutputStream != null) {
+ try {
+ bufferedOutputStream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ mState = State.IDLE;
+ mRecordingAsyncTask = null;
+ }
+
+ @Override
+ protected void onCancelled() {
+ if (mState == State.RECORDING) {
+ Log.d(TAG, "Stopping the recording ...");
+ mState = State.IDLE;
+ } else {
+ Log.w(TAG, "Requesting to stop recording while state was not RECORDING");
+ }
+ mRecordingAsyncTask = null;
+ }
+ };
+
+ mRecordingAsyncTask.execute();
+ }
+
+ public void stopRecording() {
+ if (mRecordingAsyncTask != null) {
+ mRecordingAsyncTask.cancel(true);
+ }
+ }
+
+ public void stopPlaying() {
+ if (mPlayingAsyncTask != null) {
+ mPlayingAsyncTask.cancel(true);
+ }
+ }
+
+ /**
+ * Starts playback of the recorded audio file.
+ */
+ public void startPlay() {
+ if (mState != State.IDLE) {
+ Log.w(TAG, "Requesting to play while state was not IDLE");
+ return;
+ }
+
+ if (!new File(mContext.getFilesDir(), mOutputFileName).exists()) {
+ // there is no recording to play
+ if (mListener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onPlaybackStopped();
+ }
+ });
+ }
+ return;
+ }
+ final int intSize = AudioTrack.getMinBufferSize(RECORDING_RATE, CHANNELS_OUT, FORMAT);
+
+ mPlayingAsyncTask = new AsyncTask<Void, Void, Void>() {
+
+ private AudioTrack mAudioTrack;
+
+ @Override
+ protected void onPreExecute() {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+ mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0 /* flags */);
+ mState = State.PLAYING;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, RECORDING_RATE,
+ CHANNELS_OUT, FORMAT, intSize, AudioTrack.MODE_STREAM);
+ byte[] buffer = new byte[intSize * 2];
+ FileInputStream in = null;
+ BufferedInputStream bis = null;
+ mAudioTrack.setVolume(AudioTrack.getMaxVolume());
+ mAudioTrack.play();
+ try {
+ in = mContext.openFileInput(mOutputFileName);
+ bis = new BufferedInputStream(in);
+ int read;
+ while (!isCancelled() && (read = bis.read(buffer, 0, buffer.length)) > 0) {
+ mAudioTrack.write(buffer, 0, read);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read the sound file into a byte array", e);
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ if (bis != null) {
+ bis.close();
+ }
+ } catch (IOException e) { /* ignore */}
+
+ mAudioTrack.release();
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to start playback", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ cleanup();
+ }
+
+ @Override
+ protected void onCancelled() {
+ cleanup();
+ }
+
+ private void cleanup() {
+ if (mListener != null) {
+ mListener.onPlaybackStopped();
+ }
+ mState = State.IDLE;
+ mPlayingAsyncTask = null;
+ }
+ };
+
+ mPlayingAsyncTask.execute();
+ }
+
+ public interface OnVoicePlaybackStateChangedListener {
+
+ /**
+ * Called when the playback of the audio file ends. This should be called on the UI thread.
+ */
+ void onPlaybackStopped();
+ }
+
+ /**
+ * Cleans up some resources related to {@link AudioTrack} and {@link AudioRecord}
+ */
+ public void cleanup() {
+ Log.d(TAG, "cleanup() is called");
+ stopPlaying();
+ stopRecording();
+ }
+}
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/UIAnimation.java b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/UIAnimation.java
new file mode 100644
index 00000000..7ce2fd53
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/UIAnimation.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.speaker;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+
+/**
+ * A helper class to provide a simple animation when user selects any of the three icons on the
+ * main UI.
+ */
+public class UIAnimation {
+
+ private AnimatorSet mCurrentAnimator;
+ private final int[] mLargeDrawables = new int[]{R.drawable.ic_mic_120dp,
+ R.drawable.ic_play_arrow_120dp, R.drawable.ic_audiotrack_120dp};
+ private final ImageView[] mThumbs;
+ private ImageView expandedImageView;
+ private final View mContainerView;
+ private final int mAnimationDurationTime;
+
+ private UIStateListener mListener;
+ private UIState mState = UIState.HOME;
+
+ public UIAnimation(View containerView, ImageView[] thumbs, ImageView expandedView,
+ int animationDuration, UIStateListener listener) {
+ mContainerView = containerView;
+ mThumbs = thumbs;
+ expandedImageView = expandedView;
+ mAnimationDurationTime = animationDuration;
+ mListener = listener;
+
+ mThumbs[0].setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ zoomImageFromThumb(0);
+ }
+ });
+
+ mThumbs[1].setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ zoomImageFromThumb(1);
+ }
+ });
+
+ mThumbs[2].setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ zoomImageFromThumb(2);
+ }
+ });
+ }
+
+ private void zoomImageFromThumb(final int index) {
+ int imageResId = mLargeDrawables[index];
+ final ImageView thumbView = mThumbs[index];
+ if (mCurrentAnimator != null) {
+ return;
+ }
+
+ expandedImageView.setImageResource(imageResId);
+
+ final Rect startBounds = new Rect();
+ final Rect finalBounds = new Rect();
+ final Point globalOffset = new Point();
+ thumbView.getGlobalVisibleRect(startBounds);
+ mContainerView.getGlobalVisibleRect(finalBounds, globalOffset);
+ startBounds.offset(-globalOffset.x, -globalOffset.y);
+ finalBounds.offset(-globalOffset.x, -globalOffset.y);
+ float startScale;
+ if ((float) finalBounds.width() / finalBounds.height()
+ > (float) startBounds.width() / startBounds.height()) {
+ startScale = (float) startBounds.height() / finalBounds.height();
+ float startWidth = startScale * finalBounds.width();
+ float deltaWidth = (startWidth - startBounds.width()) / 2;
+ startBounds.left -= deltaWidth;
+ startBounds.right += deltaWidth;
+ } else {
+ startScale = (float) startBounds.width() / finalBounds.width();
+ float startHeight = startScale * finalBounds.height();
+ float deltaHeight = (startHeight - startBounds.height()) / 2;
+ startBounds.top -= deltaHeight;
+ startBounds.bottom += deltaHeight;
+ }
+
+ for(int k=0; k < 3; k++) {
+ mThumbs[k].setAlpha(0f);
+ }
+ expandedImageView.setVisibility(View.VISIBLE);
+
+ expandedImageView.setPivotX(0f);
+ expandedImageView.setPivotY(0f);
+
+ AnimatorSet zommInAnimator = new AnimatorSet();
+ zommInAnimator.play(ObjectAnimator
+ .ofFloat(expandedImageView, View.X, startBounds.left, finalBounds.left)).with(
+ ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top, finalBounds
+ .top)).with(
+ ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f))
+ .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f));
+ zommInAnimator.setDuration(mAnimationDurationTime);
+ zommInAnimator.setInterpolator(new DecelerateInterpolator());
+ zommInAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentAnimator = null;
+ if (mListener != null) {
+ mState = UIState.getUIState(index);
+ mListener.onUIStateChanged(mState);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentAnimator = null;
+ }
+ });
+ zommInAnimator.start();
+ mCurrentAnimator = zommInAnimator;
+
+ final float startScaleFinal = startScale;
+ expandedImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mCurrentAnimator != null) {
+ return;
+ }
+ AnimatorSet zoomOutAnimator = new AnimatorSet();
+ zoomOutAnimator.play(ObjectAnimator
+ .ofFloat(expandedImageView, View.X, startBounds.left))
+ .with(ObjectAnimator
+ .ofFloat(expandedImageView,
+ View.Y, startBounds.top))
+ .with(ObjectAnimator
+ .ofFloat(expandedImageView,
+ View.SCALE_X, startScaleFinal))
+ .with(ObjectAnimator
+ .ofFloat(expandedImageView,
+ View.SCALE_Y, startScaleFinal));
+ zoomOutAnimator.setDuration(mAnimationDurationTime);
+ zoomOutAnimator.setInterpolator(new DecelerateInterpolator());
+ zoomOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int k = 0; k < 3; k++) {
+ mThumbs[k].setAlpha(1f);
+ }
+ expandedImageView.setVisibility(View.GONE);
+ mCurrentAnimator = null;
+ if (mListener != null) {
+ mState = UIState.HOME;
+ mListener.onUIStateChanged(mState);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ thumbView.setAlpha(1f);
+ expandedImageView.setVisibility(View.GONE);
+ mCurrentAnimator = null;
+ }
+ });
+ zoomOutAnimator.start();
+ mCurrentAnimator = zoomOutAnimator;
+ }
+ });
+ }
+
+ public enum UIState {
+ MIC_UP(0), SOUND_UP(1), MUSIC_UP(2), HOME(3);
+ private int mState;
+
+ UIState(int state) {
+ mState = state;
+ }
+
+ static UIState getUIState(int state) {
+ for(UIState uiState : values()) {
+ if (uiState.mState == state) {
+ return uiState;
+ }
+ }
+ return null;
+ }
+ }
+
+ public interface UIStateListener {
+ void onUIStateChanged(UIState state);
+ }
+
+ public void transitionToHome() {
+ if (mState == UIState.HOME) {
+ return;
+ }
+ expandedImageView.callOnClick();
+
+ }
+}
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/circle.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/circle.xml
new file mode 100644
index 00000000..df4abe52
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/circle.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" >
+ <size android:width="100dp"
+ android:height="100dp"/>
+ <stroke
+ android:width="3dp"
+ android:color="@color/circle_color"/>
+ <solid android:color="@color/circle_color"/>
+</shape> \ No newline at end of file
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_120dp.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_120dp.xml
new file mode 100644
index 00000000..0971d96d
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_120dp.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<vector android:height="120dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="120dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/large_icons_color" android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
+</vector>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_32dp.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_32dp.xml
new file mode 100644
index 00000000..70de799c
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_audiotrack_32dp.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<vector android:height="32dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/small_icons_color" android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
+</vector>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_120dp.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_120dp.xml
new file mode 100644
index 00000000..15e798a2
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_120dp.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<vector android:height="120dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="120dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/large_icons_color" android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zm5.3,-3c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_32dp.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_32dp.xml
new file mode 100644
index 00000000..c9417dd2
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_mic_32dp.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<vector android:height="32dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/small_icons_color" android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zm5.3,-3c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_120dp.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_120dp.xml
new file mode 100644
index 00000000..e87660d2
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_120dp.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<vector android:height="120dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="120dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/large_icons_color" android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_32dp.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_32dp.xml
new file mode 100644
index 00000000..9dd86787
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/drawable/ic_play_arrow_32dp.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<vector android:height="32dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/small_icons_color" android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/layout/main_activity.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/layout/main_activity.xml
new file mode 100644
index 00000000..7e004ad4
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/layout/main_activity.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <RelativeLayout
+ android:id="@+id/container2"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/background_color">
+
+ <View
+ android:id="@+id/circle"
+ android:layout_width="140dp"
+ android:layout_height="140dp"
+ android:layout_centerInParent="true"
+ android:background="@drawable/circle" />
+
+ <View
+ android:id="@+id/center"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ android:layout_centerInParent="true"
+ android:visibility="invisible" />
+
+ <ImageView
+ android:id="@+id/mic"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_above="@+id/center"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="13dp"
+ android:src="@drawable/ic_mic_32dp" />
+
+ <ImageView
+ android:id="@+id/play"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/center"
+ android:layout_marginRight="13dp"
+ android:layout_marginTop="12dp"
+ android:layout_toLeftOf="@+id/center"
+ android:src="@drawable/ic_play_arrow_32dp" />
+
+ <ImageView
+ android:id="@+id/music"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/center"
+ android:layout_marginLeft="13dp"
+ android:layout_marginTop="12dp"
+ android:layout_toRightOf="@+id/center"
+ android:src="@drawable/ic_audiotrack_32dp" />
+
+ <ProgressBar
+ android:id="@+id/progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignStart="@+id/circle"
+ android:layout_alignEnd="@+id/circle"
+ android:layout_below="@+id/circle"
+ android:progressTint="@color/progressbar_tint"
+ android:progressBackgroundTint="@color/progressbar_background_tint"
+ android:layout_marginTop="5dp"
+ android:visibility="invisible" />
+ </RelativeLayout>
+
+ <ImageView
+ android:id="@+id/expanded"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="center"
+ android:visibility="invisible" />
+</FrameLayout> \ No newline at end of file
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-hdpi/ic_launcher.png b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..cde69bcc
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-mdpi/ic_launcher.png b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..c133a0cb
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xhdpi/ic_launcher.png b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..bfa42f0e
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..324e72cd
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/raw/sound.mp3 b/wearable/wear/WearSpeakerSample/wear/src/main/res/raw/sound.mp3
new file mode 100644
index 00000000..94e3d0e5
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/raw/sound.mp3
Binary files differ
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/values/colors.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/values/colors.xml
new file mode 100644
index 00000000..e9b8605f
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<resources>
+ <color name="small_icons_color">#FFF3E0</color>
+ <color name="large_icons_color">#FFF3E0</color>
+ <color name="background_color">#FF9100</color>
+ <color name="circle_color">#E65100</color>
+ <color name="progressbar_tint">#FFD180</color>
+ <color name="progressbar_background_tint">#E65100</color>
+</resources> \ No newline at end of file
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/res/values/strings.xml b/wearable/wear/WearSpeakerSample/wear/src/main/res/values/strings.xml
new file mode 100644
index 00000000..cc342b5c
--- /dev/null
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+ 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.
+ -->
+<resources>
+ <string name="app_name">Wear Speaker Sample</string>
+ <string name="exiting_for_permissions">Recording Audio permission is required, exiting now!</string>
+ <string name="no_speaker_supported">Speaker is not supported</string>
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/template-params.xml b/wearable/wear/XYZTouristAttractions/template-params.xml
index 473b1110..69f2f779 100644
--- a/wearable/wear/XYZTouristAttractions/template-params.xml
+++ b/wearable/wear/XYZTouristAttractions/template-params.xml
@@ -20,6 +20,7 @@
<package>com.example.android.xyztouristattractions</package>
<minSdk>18</minSdk>
<targetSdkVersion>23</targetSdkVersion>
+ <targetSdkVersionWear>22</targetSdkVersionWear>
<wearable>
<has_handheld_app>true</has_handheld_app>