1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.wearable.datalayer;
18 
19 import static com.example.android.wearable.datalayer.DataLayerListenerService.LOGD;
20 
21 import android.app.Activity;
22 import android.app.Fragment;
23 import android.app.FragmentManager;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.net.Uri;
27 import android.os.AsyncTask;
28 import android.os.Bundle;
29 import android.support.wearable.view.DotsPageIndicator;
30 import android.support.wearable.view.FragmentGridPagerAdapter;
31 import android.support.wearable.view.GridViewPager;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.view.View;
35 import android.view.WindowManager;
36 import android.widget.Toast;
37 
38 import com.example.android.wearable.datalayer.fragments.AssetFragment;
39 import com.example.android.wearable.datalayer.fragments.DataFragment;
40 import com.example.android.wearable.datalayer.fragments.DiscoveryFragment;
41 import com.google.android.gms.common.ConnectionResult;
42 import com.google.android.gms.common.api.GoogleApiClient;
43 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
44 import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
45 import com.google.android.gms.common.api.PendingResult;
46 import com.google.android.gms.common.api.ResultCallback;
47 import com.google.android.gms.wearable.Asset;
48 import com.google.android.gms.wearable.CapabilityApi;
49 import com.google.android.gms.wearable.CapabilityInfo;
50 import com.google.android.gms.wearable.DataApi;
51 import com.google.android.gms.wearable.DataEvent;
52 import com.google.android.gms.wearable.DataEventBuffer;
53 import com.google.android.gms.wearable.DataMapItem;
54 import com.google.android.gms.wearable.MessageApi;
55 import com.google.android.gms.wearable.MessageEvent;
56 import com.google.android.gms.wearable.Node;
57 import com.google.android.gms.wearable.Wearable;
58 
59 import java.io.InputStream;
60 import java.util.ArrayList;
61 import java.util.HashSet;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 
66 /**
67  * The main activity with a view pager, containing three pages:<p/>
68  * <ul>
69  * <li>
70  * Page 1: shows a list of DataItems received from the phone application
71  * </li>
72  * <li>
73  * Page 2: shows the photo that is sent from the phone application
74  * </li>
75  * <li>
76  * Page 3: includes two buttons to show the connected phone and watch devices
77  * </li>
78  * </ul>
79  */
80 public class MainActivity extends Activity implements
81         ConnectionCallbacks,
82         OnConnectionFailedListener,
83         DataApi.DataListener,
84         MessageApi.MessageListener,
85         CapabilityApi.CapabilityListener {
86 
87     private static final String TAG = "MainActivity";
88     private static final String CAPABILITY_1_NAME = "capability_1";
89     private static final String CAPABILITY_2_NAME = "capability_2";
90 
91     private GoogleApiClient mGoogleApiClient;
92     private GridViewPager mPager;
93     private DataFragment mDataFragment;
94     private AssetFragment mAssetFragment;
95 
96     @Override
onCreate(Bundle savedInstanceState)97     public void onCreate(Bundle savedInstanceState) {
98         super.onCreate(savedInstanceState);
99         setContentView(R.layout.main_activity);
100         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
101         setupViews();
102         mGoogleApiClient = new GoogleApiClient.Builder(this)
103                 .addApi(Wearable.API)
104                 .addConnectionCallbacks(this)
105                 .addOnConnectionFailedListener(this)
106                 .build();
107     }
108 
109     @Override
onResume()110     protected void onResume() {
111         super.onResume();
112         mGoogleApiClient.connect();
113     }
114 
115     @Override
onPause()116     protected void onPause() {
117         if ((mGoogleApiClient != null) && mGoogleApiClient.isConnected()) {
118             Wearable.DataApi.removeListener(mGoogleApiClient, this);
119             Wearable.MessageApi.removeListener(mGoogleApiClient, this);
120             Wearable.CapabilityApi.removeListener(mGoogleApiClient, this);
121             mGoogleApiClient.disconnect();
122         }
123 
124         super.onPause();
125     }
126 
127     @Override
onConnected(Bundle connectionHint)128     public void onConnected(Bundle connectionHint) {
129         LOGD(TAG, "onConnected(): Successfully connected to Google API client");
130         Wearable.DataApi.addListener(mGoogleApiClient, this);
131         Wearable.MessageApi.addListener(mGoogleApiClient, this);
132         Wearable.CapabilityApi.addListener(
133                 mGoogleApiClient, this, Uri.parse("wear://"), CapabilityApi.FILTER_REACHABLE);
134     }
135 
136     @Override
onConnectionSuspended(int cause)137     public void onConnectionSuspended(int cause) {
138         LOGD(TAG, "onConnectionSuspended(): Connection to Google API client was suspended");
139     }
140 
141     @Override
onConnectionFailed(ConnectionResult result)142     public void onConnectionFailed(ConnectionResult result) {
143         Log.e(TAG, "onConnectionFailed(): Failed to connect, with result: " + result);
144     }
145 
146     @Override
onDataChanged(DataEventBuffer dataEvents)147     public void onDataChanged(DataEventBuffer dataEvents) {
148         LOGD(TAG, "onDataChanged(): " + dataEvents);
149 
150         for (DataEvent event : dataEvents) {
151             if (event.getType() == DataEvent.TYPE_CHANGED) {
152                 String path = event.getDataItem().getUri().getPath();
153                 if (DataLayerListenerService.IMAGE_PATH.equals(path)) {
154                     DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
155                     Asset photoAsset = dataMapItem.getDataMap()
156                             .getAsset(DataLayerListenerService.IMAGE_KEY);
157                     // Loads image on background thread.
158                     new LoadBitmapAsyncTask().execute(photoAsset);
159 
160                 } else if (DataLayerListenerService.COUNT_PATH.equals(path)) {
161                     LOGD(TAG, "Data Changed for COUNT_PATH");
162                     mDataFragment.appendItem("DataItem Changed", event.getDataItem().toString());
163                 } else {
164                     LOGD(TAG, "Unrecognized path: " + path);
165                 }
166 
167             } else if (event.getType() == DataEvent.TYPE_DELETED) {
168                 mDataFragment.appendItem("DataItem Deleted", event.getDataItem().toString());
169             } else {
170                 mDataFragment.appendItem("Unknown data event type", "Type = " + event.getType());
171             }
172         }
173     }
174 
onClicked(View view)175     public void onClicked(View view) {
176         switch (view.getId()) {
177             case R.id.capability_2_btn:
178                 showNodes(CAPABILITY_2_NAME);
179                 break;
180             case R.id.capabilities_1_and_2_btn:
181                 showNodes(CAPABILITY_1_NAME, CAPABILITY_2_NAME);
182                 break;
183             default:
184                 Log.e(TAG, "Unknown click event registered");
185         }
186     }
187 
188     /**
189      * Find the connected nodes that provide at least one of the given capabilities
190      */
showNodes(final String... capabilityNames)191     private void showNodes(final String... capabilityNames) {
192 
193         PendingResult<CapabilityApi.GetAllCapabilitiesResult> pendingCapabilityResult =
194                 Wearable.CapabilityApi.getAllCapabilities(
195                         mGoogleApiClient,
196                         CapabilityApi.FILTER_REACHABLE);
197 
198         pendingCapabilityResult.setResultCallback(
199                 new ResultCallback<CapabilityApi.GetAllCapabilitiesResult>() {
200                     @Override
201                     public void onResult(
202                             CapabilityApi.GetAllCapabilitiesResult getAllCapabilitiesResult) {
203 
204                         if (!getAllCapabilitiesResult.getStatus().isSuccess()) {
205                             Log.e(TAG, "Failed to get capabilities");
206                             return;
207                         }
208 
209                         Map<String, CapabilityInfo> capabilitiesMap =
210                                 getAllCapabilitiesResult.getAllCapabilities();
211                         Set<Node> nodes = new HashSet<>();
212 
213                         if (capabilitiesMap.isEmpty()) {
214                             showDiscoveredNodes(nodes);
215                             return;
216                         }
217                         for (String capabilityName : capabilityNames) {
218                             CapabilityInfo capabilityInfo = capabilitiesMap.get(capabilityName);
219                             if (capabilityInfo != null) {
220                                 nodes.addAll(capabilityInfo.getNodes());
221                             }
222                         }
223                         showDiscoveredNodes(nodes);
224                     }
225 
226                     private void showDiscoveredNodes(Set<Node> nodes) {
227                         List<String> nodesList = new ArrayList<>();
228                         for (Node node : nodes) {
229                             nodesList.add(node.getDisplayName());
230                         }
231                         LOGD(TAG, "Connected Nodes: " + (nodesList.isEmpty()
232                                 ? "No connected device was found for the given capabilities"
233                                 : TextUtils.join(",", nodesList)));
234                         String msg;
235                         if (!nodesList.isEmpty()) {
236                             msg = getString(R.string.connected_nodes,
237                                     TextUtils.join(", ", nodesList));
238                         } else {
239                             msg = getString(R.string.no_device);
240                         }
241                         Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
242                     }
243                 });
244     }
245 
246     @Override
onMessageReceived(MessageEvent event)247     public void onMessageReceived(MessageEvent event) {
248         LOGD(TAG, "onMessageReceived: " + event);
249         mDataFragment.appendItem("Message", event.toString());
250     }
251 
252     @Override
onCapabilityChanged(CapabilityInfo capabilityInfo)253     public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
254         LOGD(TAG, "onCapabilityChanged: " + capabilityInfo);
255         mDataFragment.appendItem("onCapabilityChanged", capabilityInfo.toString());
256     }
257 
setupViews()258     private void setupViews() {
259         mPager = (GridViewPager) findViewById(R.id.pager);
260         mPager.setOffscreenPageCount(2);
261         DotsPageIndicator dotsPageIndicator = (DotsPageIndicator) findViewById(R.id.page_indicator);
262         dotsPageIndicator.setDotSpacing((int) getResources().getDimension(R.dimen.dots_spacing));
263         dotsPageIndicator.setPager(mPager);
264         mDataFragment = new DataFragment();
265         mAssetFragment = new AssetFragment();
266         DiscoveryFragment discoveryFragment = new DiscoveryFragment();
267         List<Fragment> pages = new ArrayList<>();
268         pages.add(mDataFragment);
269         pages.add(mAssetFragment);
270         pages.add(discoveryFragment);
271         final MyPagerAdapter adapter = new MyPagerAdapter(getFragmentManager(), pages);
272         mPager.setAdapter(adapter);
273     }
274 
275     /**
276      * Switches to the page {@code index}. The first page has index 0.
277      */
moveToPage(int index)278     private void moveToPage(int index) {
279         mPager.setCurrentItem(0, index, true);
280     }
281 
282     private class MyPagerAdapter extends FragmentGridPagerAdapter {
283 
284         private List<Fragment> mFragments;
285 
MyPagerAdapter(FragmentManager fm, List<Fragment> fragments)286         public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
287             super(fm);
288             mFragments = fragments;
289         }
290 
291         @Override
getRowCount()292         public int getRowCount() {
293             return 1;
294         }
295 
296         @Override
getColumnCount(int row)297         public int getColumnCount(int row) {
298             return mFragments == null ? 0 : mFragments.size();
299         }
300 
301         @Override
getFragment(int row, int column)302         public Fragment getFragment(int row, int column) {
303             return mFragments.get(column);
304         }
305 
306     }
307 
308     /*
309      * Extracts {@link android.graphics.Bitmap} data from the
310      * {@link com.google.android.gms.wearable.Asset}
311      */
312     private class LoadBitmapAsyncTask extends AsyncTask<Asset, Void, Bitmap> {
313 
314         @Override
doInBackground(Asset... params)315         protected Bitmap doInBackground(Asset... params) {
316 
317             if (params.length > 0) {
318 
319                 Asset asset = params[0];
320 
321                 InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
322                         mGoogleApiClient, asset).await().getInputStream();
323 
324                 if (assetInputStream == null) {
325                     Log.w(TAG, "Requested an unknown Asset.");
326                     return null;
327                 }
328                 return BitmapFactory.decodeStream(assetInputStream);
329 
330             } else {
331                 Log.e(TAG, "Asset must be non-null");
332                 return null;
333             }
334         }
335 
336         @Override
onPostExecute(Bitmap bitmap)337         protected void onPostExecute(Bitmap bitmap) {
338 
339             if (bitmap != null) {
340                 LOGD(TAG, "Setting background image on second page..");
341                 moveToPage(1);
342                 mAssetFragment.setBackgroundImage(bitmap);
343             }
344         }
345     }
346 }