am 437b88c7: am 1d5ed62b: am 03236391: Sync sample prebuilts to lmp-mr1-ub-docs

* commit '437b88c73b91f62e43207f328d0d6c4f988d5f8d':
  Sync sample prebuilts to lmp-mr1-ub-docs
This commit is contained in:
Trevor Johns
2015-04-28 18:16:38 +00:00
committed by Android Git Automerger
43 changed files with 908 additions and 179 deletions

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.datalayer" >
<uses-sdk android:minSdkVersion="18"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<resources>
<string-array name="android_wear_capabilities" translatable="false">
<!-- declaring the provided capabilities -->
<item>capability_1</item>
<item>capability_2</item>
</string-array>
</resources>

View File

@@ -37,18 +37,18 @@ import android.widget.ListView;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.ResultCallback;
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.ResultCallback;
import com.google.android.gms.common.data.FreezableUtils;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataApi.DataItemResult;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.MessageApi.SendMessageResult;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageApi.SendMessageResult;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
@@ -69,8 +69,8 @@ import java.util.concurrent.TimeUnit;
/**
* Receives its own events using a listener API designed for foreground activities. Updates a data
* item every second while it is open. Also allows user to take a photo and send that as an asset to
* the paired wearable.
* item every second while it is open. Also allows user to take a photo and send that as an asset
* to the paired wearable.
*/
public class MainActivity extends Activity implements DataApi.DataListener,
MessageApi.MessageListener, NodeApi.NodeListener, ConnectionCallbacks,
@@ -78,7 +78,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
private static final String TAG = "MainActivity";
/** Request code for launching the Intent to resolve Google Play services errors. */
/**
* Request code for launching the Intent to resolve Google Play services errors.
*/
private static final int REQUEST_RESOLVE_ERROR = 1000;
private static final String START_ACTIVITY_PATH = "/start-activity";
@@ -92,7 +94,6 @@ public class MainActivity extends Activity implements DataApi.DataListener,
private boolean mCameraSupported = false;
private ListView mDataItemList;
private Button mTakePhotoBtn;
private Button mSendPhotoBtn;
private ImageView mThumbView;
private Bitmap mImageBitmap;
@@ -320,7 +321,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
}
private Collection<String> getNodes() {
HashSet<String> results = new HashSet<String>();
HashSet<String> results = new HashSet<>();
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
@@ -358,7 +359,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
}
}
/** Sends an RPC to start a fullscreen Activity on the wearable. */
/**
* Sends an RPC to start a fullscreen Activity on the wearable.
*/
public void onStartWearableActivityClick(View view) {
LOGD(TAG, "Generating RPC");
@@ -367,7 +370,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
new StartWearableActivityTask().execute();
}
/** Generates a DataItem based on an incrementing count. */
/**
* Generates a DataItem based on an incrementing count.
*/
private class DataItemGenerator implements Runnable {
private int count = 0;
@@ -462,13 +467,9 @@ public class MainActivity extends Activity implements DataApi.DataListener,
* Sets up UI components and their callback handlers.
*/
private void setupViews() {
mTakePhotoBtn = (Button) findViewById(R.id.takePhoto);
mSendPhotoBtn = (Button) findViewById(R.id.sendPhoto);
// Shows the image received from the handset
mThumbView = (ImageView) findViewById(R.id.imageView);
mDataItemList = (ListView) findViewById(R.id.data_item_list);
mStartActivityBtn = findViewById(R.id.start_wearable_activity);
}

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.datalayer" >
<uses-sdk android:minSdkVersion="20"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="20dp"/>
<padding android:left="10dp" android:right="10dp" android:top="5dp" android:bottom="5dp"/>
<solid android:color="@color/black"/>
</shape>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:id="@+id/photo"
android:scaleType="centerCrop"
android:src="@drawable/photo_placeholder"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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"
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/black">
<ListView
android:id="@+id/dataItem_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transcriptMode="alwaysScroll"/>
<TextView
android:id="@+id/intro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center"
android:fontFamily="sans-serif-condensed-light"
android:text="@string/intro" />
</RelativeLayout>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:id="@+id/center"
android:visibility="invisible"
android:layout_centerVertical="true" />
<Button
android:id="@+id/capability_2_btn"
android:text="@string/capability_2_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/center"
android:layout_centerHorizontal="true"
android:textSize="12sp"
android:onClick="onClicked" />
<Button
android:id="@+id/capabilities_1_and_2_btn"
android:text="@string/capabilities_1_and_2_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/center"
android:textSize="10sp"
android:layout_alignEnd="@+id/capability_2_btn"
android:layout_alignStart="@+id/capability_2_btn"
android:onClick="onClicked" />
</RelativeLayout>

View File

@@ -15,25 +15,24 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/dataItem_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transcriptMode="alwaysScroll"/>
android:background="@color/white">
<TextView
android:id="@+id/intro"
<android.support.wearable.view.GridViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.wearable.view.DotsPageIndicator
android:id="@+id/page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center"
android:fontFamily="sans-serif-condensed-light"
android:text="@string/intro" />
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:padding="5dp"
android:background="@drawable/rounded_background">
</android.support.wearable.view.DotsPageIndicator>
</RelativeLayout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<resources>
<dimen name="dots_spacing">15dp</dimen>
</resources>

View File

@@ -17,4 +17,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Data Layer Sample Wearable App</string>
<string name="intro">Waiting for data to arrive\u2026</string>
<string name="capability_2_notification">Capability 2</string>
<string name="capabilities_1_and_2_notification">Capabilities 1 and 2</string>
<string name="no_device">No device provides the requested capabilities</string>
<string name="connected_nodes">Nodes: %1$s</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<resources>
<string-array name="android_wear_capabilities" translatable="false">
<!-- declaring the provided capabilities -->
<item>capability_1</item>
</string-array>
</resources>

View File

@@ -45,8 +45,6 @@ public class DataLayerListenerService extends WearableListenerService {
public static final String COUNT_PATH = "/count";
public static final String IMAGE_PATH = "/image";
public static final String IMAGE_KEY = "photo";
private static final String COUNT_KEY = "count";
private static final int MAX_LOG_TAG_LENGTH = 23;
GoogleApiClient mGoogleApiClient;
@Override
@@ -63,11 +61,12 @@ public class DataLayerListenerService extends WearableListenerService {
LOGD(TAG, "onDataChanged: " + dataEvents);
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
dataEvents.close();
if(!mGoogleApiClient.isConnected()) {
if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting()) {
ConnectionResult connectionResult = mGoogleApiClient
.blockingConnect(30, TimeUnit.SECONDS);
if (!connectionResult.isSuccess()) {
Log.e(TAG, "DataLayerListenerService failed to connect to GoogleApiClient.");
Log.e(TAG, "DataLayerListenerService failed to connect to GoogleApiClient, "
+ "error code: " + connectionResult.getErrorCode());
return;
}
}

View File

@@ -19,27 +19,33 @@ package com.example.android.wearable.datalayer;
import static com.example.android.wearable.datalayer.DataLayerListenerService.LOGD;
import android.app.Activity;
import android.content.Context;
import android.app.Fragment;
import android.app.FragmentManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.support.wearable.view.DotsPageIndicator;
import android.support.wearable.view.FragmentGridPagerAdapter;
import android.support.wearable.view.GridViewPager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.android.wearable.datalayer.fragments.AssetFragment;
import com.example.android.wearable.datalayer.fragments.DataFragment;
import com.example.android.wearable.datalayer.fragments.DiscoveryFragment;
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.ResultCallback;
import com.google.android.gms.common.data.FreezableUtils;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
@@ -51,39 +57,47 @@ import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Shows events and photo from the Wearable APIs.
* The main activity with a view pager, containing three pages:<p/>
* <ul>
* <li>
* Page 1: shows a list of DataItems received from the phone application
* </li>
* <li>
* Page 2: shows the photo that is sent from the phone application
* </li>
* <li>
* Page 3: includes two buttons to show the connected phone and watch devices
* </li>
* </ul>
*/
public class MainActivity extends Activity implements ConnectionCallbacks,
OnConnectionFailedListener, DataApi.DataListener, MessageApi.MessageListener,
NodeApi.NodeListener {
private static final String TAG = "MainActivity";
private static final String CAPABILITY_1_NAME = "capability_1";
private static final String CAPABILITY_2_NAME = "capability_2";
private GoogleApiClient mGoogleApiClient;
private ListView mDataItemList;
private TextView mIntroText;
private DataItemAdapter mDataItemListAdapter;
private View mLayout;
private Handler mHandler;
private GridViewPager mPager;
private DataFragment mDataFragment;
private AssetFragment mAssetFragment;
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
mHandler = new Handler();
LOGD(TAG, "onCreate");
setContentView(R.layout.main_activity);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mDataItemList = (ListView) findViewById(R.id.dataItem_list);
mIntroText = (TextView) findViewById(R.id.intro);
mLayout = findViewById(R.id.layout);
// Stores data events received by the local broadcaster.
mDataItemListAdapter = new DataItemAdapter(this, android.R.layout.simple_list_item_1);
mDataItemList.setAdapter(mDataItemListAdapter);
setupViews();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
@@ -128,8 +142,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
runOnUiThread(new Runnable() {
@Override
public void run() {
mIntroText.setVisibility(View.INVISIBLE);
mDataItemListAdapter.add(new Event(title, text));
mDataFragment.appendItem(title, text);
}
});
}
@@ -151,8 +164,9 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
mHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Setting background image..");
mLayout.setBackground(new BitmapDrawable(getResources(), bitmap));
Log.d(TAG, "Setting background image on second page..");
moveToPage(1);
mAssetFragment.setBackgroundImage(bitmap);
}
});
@@ -171,6 +185,70 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
}
}
public void onClicked(View view) {
switch (view.getId()) {
case R.id.capability_2_btn:
showNodes(CAPABILITY_2_NAME);
break;
case R.id.capabilities_1_and_2_btn:
showNodes(CAPABILITY_1_NAME, CAPABILITY_2_NAME);
break;
default:
Log.e(TAG, "Unknown click event registered");
}
}
/**
* 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(
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();
Set<Node> nodes = new HashSet<>();
if (capabilitiesMap.isEmpty()) {
showDiscoveredNodes(nodes);
return;
}
for (String capabilityName : capabilityNames) {
CapabilityInfo capabilityInfo = capabilitiesMap.get(capabilityName);
if (capabilityInfo != null) {
nodes.addAll(capabilityInfo.getNodes());
}
}
showDiscoveredNodes(nodes);
}
private void showDiscoveredNodes(Set<Node> nodes) {
List<String> nodesList = new ArrayList<>();
for (Node node : nodes) {
nodesList.add(node.getDisplayName());
}
Log.d(TAG, "Connected Nodes: " + (nodesList.isEmpty()
? "No connected device was found for the given capabilities"
: TextUtils.join(",", nodesList)));
String msg;
if (!nodesList.isEmpty()) {
msg = getString(R.string.connected_nodes,
TextUtils.join(", ", nodesList));
} else {
msg = getString(R.string.no_device);
}
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
}
});
}
/**
* Extracts {@link android.graphics.Bitmap} data from the
* {@link com.google.android.gms.wearable.Asset}
@@ -206,50 +284,53 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
generateEvent("Node Disconnected", node.getId());
}
private static class DataItemAdapter extends ArrayAdapter<Event> {
private void setupViews() {
mPager = (GridViewPager) findViewById(R.id.pager);
mPager.setOffscreenPageCount(2);
DotsPageIndicator dotsPageIndicator = (DotsPageIndicator) findViewById(R.id.page_indicator);
dotsPageIndicator.setDotSpacing((int) getResources().getDimension(R.dimen.dots_spacing));
dotsPageIndicator.setPager(mPager);
mDataFragment = new DataFragment();
mAssetFragment = new AssetFragment();
DiscoveryFragment discoveryFragment = new DiscoveryFragment();
List<Fragment> pages = new ArrayList<>();
pages.add(mDataFragment);
pages.add(mAssetFragment);
pages.add(discoveryFragment);
final MyPagerAdapter adapter = new MyPagerAdapter(getFragmentManager(), pages);
mPager.setAdapter(adapter);
}
private final Context mContext;
/**
* Switches to the page {@code index}. The first page has index 0.
*/
private void moveToPage(int index) {
mPager.setCurrentItem(0, index, true);
}
public DataItemAdapter(Context context, int unusedResource) {
super(context, unusedResource);
mContext = context;
private class MyPagerAdapter extends FragmentGridPagerAdapter {
private List<Fragment> mFragments;
public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
mFragments = fragments;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(android.R.layout.two_line_list_item, null);
convertView.setTag(holder);
holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
holder.text2 = (TextView) convertView.findViewById(android.R.id.text2);
} else {
holder = (ViewHolder) convertView.getTag();
}
Event event = getItem(position);
holder.text1.setText(event.title);
holder.text2.setText(event.text);
return convertView;
public int getRowCount() {
return 1;
}
private class ViewHolder {
TextView text1;
TextView text2;
}
@Override
public int getColumnCount(int row) {
return mFragments == null ? 0 : mFragments.size();
}
private class Event {
String title;
String text;
public Event(String title, String text) {
this.title = title;
this.text = text;
@Override
public Fragment getFragment(int row, int column) {
return mFragments.get(column);
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2015 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.datalayer.fragments;
import android.app.Fragment;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.example.android.wearable.datalayer.R;
/**
* A simple fragment that shows a (photo) asset received from the phone.
*/
public class AssetFragment extends Fragment {
private ImageView mPhoto;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.asset_fragment, container, false);
mPhoto = (ImageView) view.findViewById(R.id.photo);
return view;
}
public void setBackgroundImage(Bitmap bitmap) {
mPhoto.setImageBitmap(bitmap);
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2015 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.datalayer.fragments;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.example.android.wearable.datalayer.R;
/**
* A fragment that shows a list of DataItems received from the phone
*/
public class DataFragment extends Fragment {
private DataItemAdapter mDataItemListAdapter;
private TextView mIntroText;
private boolean mInitialized;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.data_fragment, container, false);
ListView dataItemList = (ListView) view.findViewById(R.id.dataItem_list);
mIntroText = (TextView) view.findViewById(R.id.intro);
mDataItemListAdapter = new DataItemAdapter(getActivity(),
android.R.layout.simple_list_item_1);
dataItemList.setAdapter(mDataItemListAdapter);
mInitialized = true;
return view;
}
public void appendItem(String title, String text) {
if (!mInitialized) {
return;
}
mIntroText.setVisibility(View.INVISIBLE);
mDataItemListAdapter.add(new Event(title, text));
}
private static class DataItemAdapter extends ArrayAdapter<Event> {
private final Context mContext;
public DataItemAdapter(Context context, int unusedResource) {
super(context, unusedResource);
mContext = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(android.R.layout.two_line_list_item, null);
convertView.setTag(holder);
holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
holder.text2 = (TextView) convertView.findViewById(android.R.id.text2);
} else {
holder = (ViewHolder) convertView.getTag();
}
Event event = getItem(position);
holder.text1.setText(event.title);
holder.text2.setText(event.text);
return convertView;
}
private class ViewHolder {
TextView text1;
TextView text2;
}
}
private class Event {
String title;
String text;
public Event(String title, String text) {
this.title = title;
this.text = text;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2015 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.datalayer.fragments;
import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.android.wearable.datalayer.R;
/**
* A simple fragment with two buttons to show connected phones and watches
*/
public class DiscoveryFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.discovery_fragment, container, false);
}
}

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.delayedconfirmation" >
<uses-sdk android:minSdkVersion="18"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<application
android:allowBackup="true"

View File

@@ -0,0 +1,6 @@
<resources>
<string-array name="android_wear_capabilities" translatable="false">
<!-- declaring that phone can handle confirmation messages -->
<item>confirmation_handler</item>
</string-array>
</resources>

View File

@@ -112,9 +112,9 @@ public class MainActivity extends Activity implements MessageApi.MessageListener
@Override
public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
for (final Node node : getConnectedNodesResult.getNodes()) {
Wearable.MessageApi.sendMessage(
mGoogleApiClient, node.getId(), START_ACTIVITY_PATH, new byte[0])
.setResultCallback(getSendMessageResultCallback());
Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(),
START_ACTIVITY_PATH, new byte[0]).setResultCallback(
getSendMessageResultCallback());
}
}
});

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.delayedconfirmation" >
<uses-sdk android:minSdkVersion="20"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />

View File

@@ -22,4 +22,5 @@
<string name="notification_title">DelayedConfirmation</string>
<string name="notification_timer_selected">Timer Selected</string>
<string name="notification_timer_finished">Timer Finished</string>
<string name="no_device_found">No device able to confirm was found</string>
</resources>

View File

@@ -23,19 +23,23 @@ import android.os.Bundle;
import android.support.wearable.view.DelayedConfirmationView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import java.util.Set;
public class MainActivity extends Activity implements
DelayedConfirmationView.DelayedConfirmationListener,
GoogleApiClient.OnConnectionFailedListener {
GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks,
CapabilityApi.CapabilityListener {
private static final String TAG = "DelayedConfirmation";
private static final int NUM_SECONDS = 5;
@@ -43,9 +47,15 @@ public class MainActivity extends Activity implements
private static final String TIMER_SELECTED_PATH = "/timer_selected";
private static final String TIMER_FINISHED_PATH = "/timer_finished";
/* name of the capability that the phone side provides */
private static final String CONFIRMATION_HANDLER_CAPABILITY_NAME = "confirmation_handler";
private DelayedConfirmationView delayedConfirmationView;
private GoogleApiClient mGoogleApiClient;
/* the preferred note that can handle the confirmation capability */
private Node mConfirmationHandlerNode;
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
@@ -55,6 +65,7 @@ public class MainActivity extends Activity implements
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addOnConnectionFailedListener(this)
.addConnectionCallbacks(this)
.build();
}
@@ -67,11 +78,13 @@ public class MainActivity extends Activity implements
}
@Override
protected void onDestroy() {
protected void onPause() {
if (mGoogleApiClient.isConnected()) {
Wearable.CapabilityApi.removeCapabilityListener(mGoogleApiClient, this,
CONFIRMATION_HANDLER_CAPABILITY_NAME);
mGoogleApiClient.disconnect();
}
super.onDestroy();
super.onPause();
}
/**
@@ -112,32 +125,94 @@ public class MainActivity extends Activity implements
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "Failed to connect to Google Api Client");
mConfirmationHandlerNode = null;
}
private void sendMessageToCompanion(final String path) {
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(
new ResultCallback<NodeApi.GetConnectedNodesResult>() {
@Override
public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
for (final Node node : getConnectedNodesResult.getNodes()) {
Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path,
new byte[0]).setResultCallback(getSendMessageResultCallback());
if (mConfirmationHandlerNode != null) {
Wearable.MessageApi.sendMessage(mGoogleApiClient, mConfirmationHandlerNode.getId(),
path, new byte[0])
.setResultCallback(getSendMessageResultCallback(mConfirmationHandlerNode));
} else {
Toast.makeText(this, R.string.no_device_found, Toast.LENGTH_SHORT).show();
}
}
}
);
}
private ResultCallback<MessageApi.SendMessageResult> getSendMessageResultCallback() {
private ResultCallback<MessageApi.SendMessageResult> getSendMessageResultCallback(
final Node node) {
return new ResultCallback<MessageApi.SendMessageResult>() {
@Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
if (!sendMessageResult.getStatus().isSuccess()) {
Log.e(TAG, "Failed to connect to Google Api Client with status "
Log.e(TAG, "Failed to send message with status "
+ sendMessageResult.getStatus());
} else {
Log.d(TAG, "Sent confirmation message to node " + node.getDisplayName());
}
}
};
}
private void setupConfirmationHandlerNode() {
Wearable.CapabilityApi.addCapabilityListener(
mGoogleApiClient, this, CONFIRMATION_HANDLER_CAPABILITY_NAME);
Wearable.CapabilityApi.getCapability(
mGoogleApiClient, CONFIRMATION_HANDLER_CAPABILITY_NAME,
CapabilityApi.FILTER_REACHABLE).setResultCallback(
new ResultCallback<CapabilityApi.GetCapabilityResult>() {
@Override
public void onResult(CapabilityApi.GetCapabilityResult result) {
if (!result.getStatus().isSuccess()) {
Log.e(TAG, "setupConfirmationHandlerNode() Failed to get capabilities, "
+ "status: " + result.getStatus().getStatusMessage());
return;
}
updateConfirmationCapability(result.getCapability());
}
});
}
private void updateConfirmationCapability(CapabilityInfo capabilityInfo) {
Set<Node> connectedNodes = capabilityInfo.getNodes();
if (connectedNodes.isEmpty()) {
mConfirmationHandlerNode = null;
} else {
mConfirmationHandlerNode = pickBestNode(connectedNodes);
}
}
/**
* We pick a node that is capabale of handling the confirmation. If there is more than one,
* then we would prefer the one that is directly connected to this device. In general,
* depending on the situation and requirements, the "best" node might be picked based on other
* criteria.
*/
private Node pickBestNode(Set<Node> connectedNodes) {
Node best = null;
if (connectedNodes != null) {
for (Node node : connectedNodes) {
if (node.isNearby()) {
return node;
}
best = node;
}
}
return best;
}
@Override
public void onConnected(Bundle bundle) {
setupConfirmationHandlerNode();
}
@Override
public void onConnectionSuspended(int cause) {
mConfirmationHandlerNode = null;
}
@Override
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
updateConfirmationCapability(capabilityInfo);
}
}

View File

@@ -25,6 +25,7 @@ import com.google.android.gms.wearable.WearableListenerService;
* Listens for a message telling it to start the Wearable MainActivity.
*/
public class WearableMessageListenerService extends WearableListenerService {
private static final String START_ACTIVITY_PATH = "/start-activity";
@Override

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.findphone">
<uses-sdk android:minSdkVersion="18"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.VIBRATE" />
<application

View File

@@ -0,0 +1,7 @@
<resources>
<string-array name="android_wear_capabilities">
<!-- declaring that phone provides find_me capability -->
<item>find_me</item>
</string-array>
</resources>

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.findphone" >
<uses-sdk android:minSdkVersion="20"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.type.watch" />

View File

@@ -18,25 +18,105 @@ package com.example.android.wearable.findphone;
import android.app.Notification;
import android.app.NotificationManager;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import java.util.List;
import java.util.Set;
/**
* Listens for disconnection from home device.
* Listens for changes in connectivity between this wear device and the phone. More precisely, we
* need to distinguish the case that the wear device and the phone are connected directly from all
* other possible cases. To this end, the phone app has registered itself to provide the "find_me"
* capability and we need to look for connected nodes that provide this capability AND are nearby,
* to exclude a connection through the cloud. The proper way would have been to use the
* {@code onCapabilitiesChanged()} callback but currently that callback cannot discover the case
* where a connection switches from wifi to direct; this shortcoming will be addressed in future
* updates but for now we will use the {@code onConnectedNodes()} callback.
*/
public class DisconnectListenerService extends WearableListenerService {
public class DisconnectListenerService extends WearableListenerService
implements GoogleApiClient.ConnectionCallbacks {
private static final String TAG = "ExampleFindPhoneApp";
private static final int FORGOT_PHONE_NOTIFICATION_ID = 1;
/* the capability that the phone app would provide */
private static final String FIND_ME_CAPABILITY_NAME = "find_me";
private GoogleApiClient mGoogleApiClient;
@Override
public void onPeerDisconnected(com.google.android.gms.wearable.Node peer) {
// Create a "forgot phone" notification when phone connection is broken.
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.build();
}
@Override
public void onConnectedNodes(List<Node> connectedNodes) {
// After we are notified by this callback, we need to query for the nodes that provide the
// "find_me" capability and are directly connected.
if (mGoogleApiClient.isConnected()) {
setOrUpdateNotification();
} else if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
private void setOrUpdateNotification() {
Wearable.CapabilityApi.getCapability(
mGoogleApiClient, FIND_ME_CAPABILITY_NAME,
CapabilityApi.FILTER_REACHABLE).setResultCallback(
new ResultCallback<CapabilityApi.GetCapabilityResult>() {
@Override
public void onResult(CapabilityApi.GetCapabilityResult result) {
if (result.getStatus().isSuccess()) {
updateFindMeCapability(result.getCapability());
} else {
Log.e(TAG,
"setOrUpdateNotification() Failed to get capabilities, "
+ "status: "
+ result.getStatus().getStatusMessage());
}
}
});
}
private void updateFindMeCapability(CapabilityInfo capabilityInfo) {
Set<Node> connectedNodes = capabilityInfo.getNodes();
if (connectedNodes.isEmpty()) {
setupLostConnectivityNotification();
} else {
for (Node node : connectedNodes) {
// we are only considering those nodes that are directly connected
if (node.isNearby()) {
((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
.cancel(FORGOT_PHONE_NOTIFICATION_ID);
}
}
}
}
/**
* Creates a notification to inform user that the connectivity to phone has been lost (possibly
* left the phone behind).
*/
private void setupLostConnectivityNotification() {
Notification.Builder notificationBuilder = new Notification.Builder(this)
.setContentTitle(getString(R.string.left_phone_title))
.setContentText(getString(R.string.left_phone_content))
.setVibrate(new long[] {0, 200}) // Vibrate for 200 milliseconds.
.setVibrate(new long[]{0, 200}) // Vibrate for 200 milliseconds.
.setSmallIcon(R.drawable.ic_launcher)
.setLocalOnly(true)
.setPriority(Notification.PRIORITY_MAX);
@@ -46,10 +126,20 @@ public class DisconnectListenerService extends WearableListenerService {
}
@Override
public void onPeerConnected(com.google.android.gms.wearable.Node peer) {
// Remove the "forgot phone" notification when connection is restored.
((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
.cancel(FORGOT_PHONE_NOTIFICATION_ID);
public void onConnected(Bundle bundle) {
setOrUpdateNotification();
}
@Override
public void onConnectionSuspended(int cause) {
}
@Override
public void onDestroy() {
if (mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting()) {
mGoogleApiClient.disconnect();
}
super.onDestroy();
}
}

View File

@@ -28,7 +28,6 @@ import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
public class FindPhoneActivity extends Activity {
private static final int FIND_PHONE_NOTIFICATION_ID = 2;

View File

@@ -18,7 +18,7 @@
package="com.example.android.wearable.quiz" >
<uses-sdk android:minSdkVersion="18"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<application
android:allowBackup="true"

View File

@@ -0,0 +1,7 @@
<resources>
<string-array name="android_wear_capabilities">
<!-- declaring that phone provides reset_quiz capability -->
<item>reset_quiz</item>
</string-array>
</resources>

View File

@@ -210,6 +210,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
* indexes. For example, question0 should come before question1.
*/
private static class Question implements Comparable<Question> {
private String question;
private int questionIndex;
private String[] answers;
@@ -253,6 +254,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
/**
* Create a quiz, as defined in Quiz.json, when the user clicks on "Read quiz from file."
*
* @throws IOException
*/
public void readQuizFromFile(View view) throws IOException, JSONException {
@@ -414,7 +416,8 @@ public class MainActivity extends Activity implements DataApi.DataListener,
@Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, data);
Wearable.MessageApi
.sendMessage(mGoogleApiClient, node.getId(), path, data);
}
if (path.equals(QUIZ_EXITED_PATH) && mGoogleApiClient.isConnected()) {
@@ -429,7 +432,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
*/
public void resetQuiz(View view) {
// Reset quiz status in phone layout.
for(int i = 0; i < questionsContainer.getChildCount(); i++) {
for (int i = 0; i < questionsContainer.getChildCount(); i++) {
LinearLayout questionStatusElement = (LinearLayout) questionsContainer.getChildAt(i);
TextView questionText = (TextView) questionStatusElement.findViewById(R.id.question);
TextView questionStatus = (TextView) questionStatusElement.findViewById(R.id.status);
@@ -481,6 +484,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
* Callback that marks a DataItem, which represents a question, as unanswered and not deleted.
*/
private class ResetDataItemCallback implements ResultCallback<DataApi.DataItemResult> {
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {
if (dataItemResult.getStatus().isSuccess()) {

View File

@@ -19,7 +19,7 @@
<uses-sdk android:minSdkVersion="20"
android:targetSdkVersion="21" />
android:targetSdkVersion="22" />
<uses-feature android:name="android.hardware.type.watch" />

View File

@@ -38,4 +38,5 @@ public final class Constants {
public static final String RESET_QUIZ_PATH = "/reset_quiz";
public static final int CONNECT_TIMEOUT_MS = 100;
public static final int GET_CAPABILITIES_TIMEOUT_MS = 5000;
}

View File

@@ -16,6 +16,9 @@
package com.example.android.wearable.quiz;
import static com.example.android.wearable.quiz.Constants.CONNECT_TIMEOUT_MS;
import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_DELETED;
import android.app.IntentService;
import android.content.Intent;
import android.net.Uri;
@@ -33,9 +36,6 @@ import com.google.android.gms.wearable.Wearable;
import java.util.concurrent.TimeUnit;
import static com.example.android.wearable.quiz.Constants.CONNECT_TIMEOUT_MS;
import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_DELETED;
/**
* Used to update quiz status on the phone when user dismisses a question on the watch.
*/

View File

@@ -64,6 +64,7 @@ import java.util.concurrent.TimeUnit;
* When the quiz ends, this listener receives a message telling it to create an end-of-quiz report.
*/
public class QuizListenerService extends WearableListenerService {
private static final String TAG = "QuizSample";
private static final int QUIZ_REPORT_NOTIF_ID = -1; // Never used by question notifications.
private static final Map<Integer, Integer> questionNumToDrawableId;

View File

@@ -16,29 +16,34 @@
package com.example.android.wearable.quiz;
import static com.example.android.wearable.quiz.Constants.CONNECT_TIMEOUT_MS;
import static com.example.android.wearable.quiz.Constants.GET_CAPABILITIES_TIMEOUT_MS;
import static com.example.android.wearable.quiz.Constants.RESET_QUIZ_PATH;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static com.example.android.wearable.quiz.Constants.CONNECT_TIMEOUT_MS;
import static com.example.android.wearable.quiz.Constants.RESET_QUIZ_PATH;
/**
* Service to reset the quiz (by sending a message to the phone) when the Reset Quiz
* action on the Quiz Report is selected.
*/
public class QuizReportActionService extends IntentService {
public static final String ACTION_RESET_QUIZ = "com.example.android.wearable.quiz.RESET_QUIZ";
private static final String TAG = "QuizReportActionReceiver";
private static final String TAG = "QuizReportActionService";
private static final String RESET_QUIZ_CAPABILITY_NAME = "reset_quiz";
public QuizReportActionService() {
super(QuizReportActionService.class.getSimpleName());
@@ -47,18 +52,35 @@ public class QuizReportActionService extends IntentService {
@Override
public void onHandleIntent(Intent intent) {
if (intent.getAction().equals(ACTION_RESET_QUIZ)) {
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
final GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.build();
ConnectionResult result = googleApiClient.blockingConnect(CONNECT_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (!result.isSuccess()) {
Log.e(TAG, "QuizListenerService failed to connect to GoogleApiClient.");
Log.e(TAG, "QuizReportActionService failed to connect to GoogleApiClient.");
return;
}
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
CapabilityApi.GetCapabilityResult capabilityResult = Wearable.CapabilityApi
.getCapability(googleApiClient, RESET_QUIZ_CAPABILITY_NAME,
CapabilityApi.FILTER_REACHABLE)
.await(GET_CAPABILITIES_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (capabilityResult.getStatus().isSuccess()) {
sendResetMessage(googleApiClient, capabilityResult.getCapability());
} else {
Log.e(TAG, "Failed to get capabilities, status: "
+ capabilityResult.getStatus().getStatusMessage());
}
}
}
private void sendResetMessage(GoogleApiClient googleApiClient, CapabilityInfo capabilityInfo) {
Set<Node> connectedNodes = capabilityInfo.getNodes();
if (connectedNodes.isEmpty()) {
Log.w(TAG, "No node capable of resetting quiz was found");
} else {
for (Node node : connectedNodes) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), RESET_QUIZ_PATH,
new byte[0]);
}

View File

@@ -16,6 +16,10 @@
package com.example.android.wearable.quiz;
import static com.example.android.wearable.quiz.Constants.CHOSEN_ANSWER_CORRECT;
import static com.example.android.wearable.quiz.Constants.QUESTION_INDEX;
import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_ANSWERED;
import android.app.IntentService;
import android.app.NotificationManager;
import android.content.Intent;
@@ -34,15 +38,12 @@ import com.google.android.gms.wearable.Wearable;
import java.util.concurrent.TimeUnit;
import static com.example.android.wearable.quiz.Constants.CHOSEN_ANSWER_CORRECT;
import static com.example.android.wearable.quiz.Constants.QUESTION_INDEX;
import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_ANSWERED;
/**
* Updates quiz status on the phone when user selects an answer to a question on the watch.
*/
public class UpdateQuestionService extends IntentService
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
public static final String EXTRA_QUESTION_CORRECT = "extra_question_correct";
public static final String EXTRA_QUESTION_INDEX = "extra_question_index";

View File

@@ -0,0 +1,7 @@
<resources>
<string-array name="android_wear_capabilities" translatable="false">
<!-- declaring that phone has the capability to show details -->
<item>@string/show_detail_capability_name</item>
</string-array>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="show_detail_capability_name">show_details</string>
</resources>

View File

@@ -20,14 +20,19 @@ import android.app.IntentService;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.example.android.xyztouristattractions.R;
import com.example.android.xyztouristattractions.common.Constants;
import com.example.android.xyztouristattractions.common.Utils;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.Wearable;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -35,6 +40,7 @@ import java.util.concurrent.TimeUnit;
* operations that do not necessarily need to be tied to a UI.
*/
public class UtilityService extends IntentService {
private static final String TAG = UtilityService.class.getSimpleName();
private static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
@@ -42,6 +48,7 @@ public class UtilityService extends IntentService {
private static final String ACTION_START_DEVICE_ACTIVITY = "start_device_activity";
private static final String EXTRA_START_PATH = "start_path";
private static final String EXTRA_START_ACTIVITY_INFO = "start_activity_info";
private static final long GET_CAPABILITY_TIMEOUT_S = 10;
public static void clearNotification(Context context) {
Intent intent = new Intent(context, UtilityService.class);
@@ -131,7 +138,8 @@ public class UtilityService extends IntentService {
}
/**
* Sends the actual message to ask other devices to start an activity
* Sends the actual message to ask other devices that are capable of showing "details" to start
* the appropriate activity
*
* @param path the path to pass to the wearable message API
* @param extraInfo extra info that varies based on the path being sent
@@ -145,14 +153,24 @@ public class UtilityService extends IntentService {
Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
Iterator<String> itr = Utils.getNodes(googleApiClient).iterator();
while (itr.hasNext()) {
// Loop through all connected nodes
CapabilityApi.GetCapabilityResult result = Wearable.CapabilityApi.getCapability(
googleApiClient,
getApplicationContext().getString(R.string.show_detail_capability_name),
CapabilityApi.FILTER_REACHABLE)
.await(GET_CAPABILITY_TIMEOUT_S, TimeUnit.SECONDS);
if (result.getStatus().isSuccess()) {
Set<Node> nodes = result.getCapability().getNodes();
for (Node node : nodes) {
Wearable.MessageApi.sendMessage(
googleApiClient, itr.next(), path, extraInfo.getBytes());
googleApiClient, node.getId(), path, extraInfo.getBytes());
}
} else {
Log.e(TAG, "startDeviceActivityInternal() Failed to get capabilities, status: "
+ result.getStatus().getStatusMessage());
}
googleApiClient.disconnect();
}
}
}