• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentSender;
23 import android.content.pm.PackageManager;
24 import android.graphics.Bitmap;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.provider.MediaStore;
29 import android.util.Log;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.ArrayAdapter;
34 import android.widget.Button;
35 import android.widget.ImageView;
36 import android.widget.ListView;
37 import android.widget.TextView;
38 
39 import com.google.android.gms.common.ConnectionResult;
40 import com.google.android.gms.common.api.ResultCallback;
41 import com.google.android.gms.common.api.GoogleApiClient;
42 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
43 import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
44 import com.google.android.gms.common.data.FreezableUtils;
45 import com.google.android.gms.wearable.Asset;
46 import com.google.android.gms.wearable.DataApi.DataItemResult;
47 import com.google.android.gms.wearable.DataEvent;
48 import com.google.android.gms.wearable.DataEventBuffer;
49 import com.google.android.gms.wearable.MessageApi.SendMessageResult;
50 import com.google.android.gms.wearable.DataApi;
51 import com.google.android.gms.wearable.MessageApi;
52 import com.google.android.gms.wearable.MessageEvent;
53 import com.google.android.gms.wearable.Node;
54 import com.google.android.gms.wearable.NodeApi;
55 import com.google.android.gms.wearable.PutDataMapRequest;
56 import com.google.android.gms.wearable.PutDataRequest;
57 import com.google.android.gms.wearable.Wearable;
58 
59 import java.io.ByteArrayOutputStream;
60 import java.io.IOException;
61 import java.util.Collection;
62 import java.util.Date;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.concurrent.ScheduledExecutorService;
66 import java.util.concurrent.ScheduledFuture;
67 import java.util.concurrent.ScheduledThreadPoolExecutor;
68 import java.util.concurrent.TimeUnit;
69 
70 /**
71  * Receives its own events using a listener API designed for foreground activities. Updates a data
72  * item every second while it is open. Also allows user to take a photo and send that as an asset to
73  * the paired wearable.
74  */
75 public class MainActivity extends Activity implements DataApi.DataListener,
76         MessageApi.MessageListener, NodeApi.NodeListener, ConnectionCallbacks,
77         OnConnectionFailedListener {
78 
79     private static final String TAG = "MainActivity";
80 
81     /** Request code for launching the Intent to resolve Google Play services errors. */
82     private static final int REQUEST_RESOLVE_ERROR = 1000;
83 
84     private static final String START_ACTIVITY_PATH = "/start-activity";
85     private static final String COUNT_PATH = "/count";
86     private static final String IMAGE_PATH = "/image";
87     private static final String IMAGE_KEY = "photo";
88     private static final String COUNT_KEY = "count";
89 
90     private GoogleApiClient mGoogleApiClient;
91     private boolean mResolvingError = false;
92     private boolean mCameraSupported = false;
93 
94     private ListView mDataItemList;
95     private Button mTakePhotoBtn;
96     private Button mSendPhotoBtn;
97     private ImageView mThumbView;
98     private Bitmap mImageBitmap;
99     private View mStartActivityBtn;
100 
101     private DataItemAdapter mDataItemListAdapter;
102     private Handler mHandler;
103 
104     // Send DataItems.
105     private ScheduledExecutorService mGeneratorExecutor;
106     private ScheduledFuture<?> mDataItemGeneratorFuture;
107 
108     static final int REQUEST_IMAGE_CAPTURE = 1;
109 
110     @Override
onCreate(Bundle b)111     public void onCreate(Bundle b) {
112         super.onCreate(b);
113         mHandler = new Handler();
114         LOGD(TAG, "onCreate");
115         mCameraSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
116         setContentView(R.layout.main_activity);
117         setupViews();
118 
119         // Stores DataItems received by the local broadcaster or from the paired watch.
120         mDataItemListAdapter = new DataItemAdapter(this, android.R.layout.simple_list_item_1);
121         mDataItemList.setAdapter(mDataItemListAdapter);
122 
123         mGeneratorExecutor = new ScheduledThreadPoolExecutor(1);
124 
125         mGoogleApiClient = new GoogleApiClient.Builder(this)
126                 .addApi(Wearable.API)
127                 .addConnectionCallbacks(this)
128                 .addOnConnectionFailedListener(this)
129                 .build();
130     }
131 
132     @Override
onActivityResult(int requestCode, int resultCode, Intent data)133     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
134         if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
135             Bundle extras = data.getExtras();
136             mImageBitmap = (Bitmap) extras.get("data");
137             mThumbView.setImageBitmap(mImageBitmap);
138         }
139     }
140 
141     @Override
onStart()142     protected void onStart() {
143         super.onStart();
144         if (!mResolvingError) {
145             mGoogleApiClient.connect();
146         }
147     }
148 
149     @Override
onResume()150     public void onResume() {
151         super.onResume();
152         mDataItemGeneratorFuture = mGeneratorExecutor.scheduleWithFixedDelay(
153                 new DataItemGenerator(), 1, 5, TimeUnit.SECONDS);
154     }
155 
156     @Override
onPause()157     public void onPause() {
158         super.onPause();
159         mDataItemGeneratorFuture.cancel(true /* mayInterruptIfRunning */);
160     }
161 
162     @Override
onStop()163     protected void onStop() {
164         if (!mResolvingError) {
165             Wearable.DataApi.removeListener(mGoogleApiClient, this);
166             Wearable.MessageApi.removeListener(mGoogleApiClient, this);
167             Wearable.NodeApi.removeListener(mGoogleApiClient, this);
168             mGoogleApiClient.disconnect();
169         }
170         super.onStop();
171     }
172 
173     @Override //ConnectionCallbacks
onConnected(Bundle connectionHint)174     public void onConnected(Bundle connectionHint) {
175         LOGD(TAG, "Google API Client was connected");
176         mResolvingError = false;
177         mStartActivityBtn.setEnabled(true);
178         mSendPhotoBtn.setEnabled(mCameraSupported);
179         Wearable.DataApi.addListener(mGoogleApiClient, this);
180         Wearable.MessageApi.addListener(mGoogleApiClient, this);
181         Wearable.NodeApi.addListener(mGoogleApiClient, this);
182     }
183 
184     @Override //ConnectionCallbacks
onConnectionSuspended(int cause)185     public void onConnectionSuspended(int cause) {
186         LOGD(TAG, "Connection to Google API client was suspended");
187         mStartActivityBtn.setEnabled(false);
188         mSendPhotoBtn.setEnabled(false);
189     }
190 
191     @Override //OnConnectionFailedListener
onConnectionFailed(ConnectionResult result)192     public void onConnectionFailed(ConnectionResult result) {
193         if (mResolvingError) {
194             // Already attempting to resolve an error.
195             return;
196         } else if (result.hasResolution()) {
197             try {
198                 mResolvingError = true;
199                 result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
200             } catch (IntentSender.SendIntentException e) {
201                 // There was an error with the resolution intent. Try again.
202                 mGoogleApiClient.connect();
203             }
204         } else {
205             Log.e(TAG, "Connection to Google API client has failed");
206             mResolvingError = false;
207             mStartActivityBtn.setEnabled(false);
208             mSendPhotoBtn.setEnabled(false);
209             Wearable.DataApi.removeListener(mGoogleApiClient, this);
210             Wearable.MessageApi.removeListener(mGoogleApiClient, this);
211             Wearable.NodeApi.removeListener(mGoogleApiClient, this);
212         }
213     }
214 
215     @Override //DataListener
onDataChanged(DataEventBuffer dataEvents)216     public void onDataChanged(DataEventBuffer dataEvents) {
217         LOGD(TAG, "onDataChanged: " + dataEvents);
218         final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
219         dataEvents.close();
220         runOnUiThread(new Runnable() {
221             @Override
222             public void run() {
223                 for (DataEvent event : events) {
224                     if (event.getType() == DataEvent.TYPE_CHANGED) {
225                         mDataItemListAdapter.add(
226                                 new Event("DataItem Changed", event.getDataItem().toString()));
227                     } else if (event.getType() == DataEvent.TYPE_DELETED) {
228                         mDataItemListAdapter.add(
229                                 new Event("DataItem Deleted", event.getDataItem().toString()));
230                     }
231                 }
232             }
233         });
234     }
235 
236     @Override //MessageListener
onMessageReceived(final MessageEvent messageEvent)237     public void onMessageReceived(final MessageEvent messageEvent) {
238         LOGD(TAG, "onMessageReceived() A message from watch was received:" + messageEvent
239                 .getRequestId() + " " + messageEvent.getPath());
240         mHandler.post(new Runnable() {
241             @Override
242             public void run() {
243                 mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString()));
244             }
245         });
246 
247     }
248 
249     @Override //NodeListener
onPeerConnected(final Node peer)250     public void onPeerConnected(final Node peer) {
251         LOGD(TAG, "onPeerConnected: " + peer);
252         mHandler.post(new Runnable() {
253             @Override
254             public void run() {
255                 mDataItemListAdapter.add(new Event("Connected", peer.toString()));
256             }
257         });
258 
259     }
260 
261     @Override //NodeListener
onPeerDisconnected(final Node peer)262     public void onPeerDisconnected(final Node peer) {
263         LOGD(TAG, "onPeerDisconnected: " + peer);
264         mHandler.post(new Runnable() {
265             @Override
266             public void run() {
267                 mDataItemListAdapter.add(new Event("Disconnected", peer.toString()));
268             }
269         });
270     }
271 
272     /**
273      * A View Adapter for presenting the Event objects in a list
274      */
275     private static class DataItemAdapter extends ArrayAdapter<Event> {
276 
277         private final Context mContext;
278 
DataItemAdapter(Context context, int unusedResource)279         public DataItemAdapter(Context context, int unusedResource) {
280             super(context, unusedResource);
281             mContext = context;
282         }
283 
284         @Override
getView(int position, View convertView, ViewGroup parent)285         public View getView(int position, View convertView, ViewGroup parent) {
286             ViewHolder holder;
287             if (convertView == null) {
288                 holder = new ViewHolder();
289                 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
290                         Context.LAYOUT_INFLATER_SERVICE);
291                 convertView = inflater.inflate(android.R.layout.two_line_list_item, null);
292                 convertView.setTag(holder);
293                 holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
294                 holder.text2 = (TextView) convertView.findViewById(android.R.id.text2);
295             } else {
296                 holder = (ViewHolder) convertView.getTag();
297             }
298             Event event = getItem(position);
299             holder.text1.setText(event.title);
300             holder.text2.setText(event.text);
301             return convertView;
302         }
303 
304         private class ViewHolder {
305 
306             TextView text1;
307             TextView text2;
308         }
309     }
310 
311     private class Event {
312 
313         String title;
314         String text;
315 
Event(String title, String text)316         public Event(String title, String text) {
317             this.title = title;
318             this.text = text;
319         }
320     }
321 
getNodes()322     private Collection<String> getNodes() {
323         HashSet<String> results = new HashSet<String>();
324         NodeApi.GetConnectedNodesResult nodes =
325                 Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
326 
327         for (Node node : nodes.getNodes()) {
328             results.add(node.getId());
329         }
330 
331         return results;
332     }
333 
sendStartActivityMessage(String node)334     private void sendStartActivityMessage(String node) {
335         Wearable.MessageApi.sendMessage(
336                 mGoogleApiClient, node, START_ACTIVITY_PATH, new byte[0]).setResultCallback(
337                 new ResultCallback<SendMessageResult>() {
338                     @Override
339                     public void onResult(SendMessageResult sendMessageResult) {
340                         if (!sendMessageResult.getStatus().isSuccess()) {
341                             Log.e(TAG, "Failed to send message with status code: "
342                                     + sendMessageResult.getStatus().getStatusCode());
343                         }
344                     }
345                 }
346         );
347     }
348 
349     private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> {
350 
351         @Override
doInBackground(Void... args)352         protected Void doInBackground(Void... args) {
353             Collection<String> nodes = getNodes();
354             for (String node : nodes) {
355                 sendStartActivityMessage(node);
356             }
357             return null;
358         }
359     }
360 
361     /** Sends an RPC to start a fullscreen Activity on the wearable. */
onStartWearableActivityClick(View view)362     public void onStartWearableActivityClick(View view) {
363         LOGD(TAG, "Generating RPC");
364 
365         // Trigger an AsyncTask that will query for a list of connected nodes and send a
366         // "start-activity" message to each connected node.
367         new StartWearableActivityTask().execute();
368     }
369 
370     /** Generates a DataItem based on an incrementing count. */
371     private class DataItemGenerator implements Runnable {
372 
373         private int count = 0;
374 
375         @Override
run()376         public void run() {
377             PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
378             putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
379             PutDataRequest request = putDataMapRequest.asPutDataRequest();
380 
381             LOGD(TAG, "Generating DataItem: " + request);
382             if (!mGoogleApiClient.isConnected()) {
383                 return;
384             }
385             Wearable.DataApi.putDataItem(mGoogleApiClient, request)
386                     .setResultCallback(new ResultCallback<DataItemResult>() {
387                         @Override
388                         public void onResult(DataItemResult dataItemResult) {
389                             if (!dataItemResult.getStatus().isSuccess()) {
390                                 Log.e(TAG, "ERROR: failed to putDataItem, status code: "
391                                         + dataItemResult.getStatus().getStatusCode());
392                             }
393                         }
394                     });
395         }
396     }
397 
398     /**
399      * Dispatches an {@link android.content.Intent} to take a photo. Result will be returned back
400      * in onActivityResult().
401      */
dispatchTakePictureIntent()402     private void dispatchTakePictureIntent() {
403         Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
404         if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
405             startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
406         }
407     }
408 
409     /**
410      * Builds an {@link com.google.android.gms.wearable.Asset} from a bitmap. The image that we get
411      * back from the camera in "data" is a thumbnail size. Typically, your image should not exceed
412      * 320x320 and if you want to have zoom and parallax effect in your app, limit the size of your
413      * image to 640x400. Resize your image before transferring to your wearable device.
414      */
toAsset(Bitmap bitmap)415     private static Asset toAsset(Bitmap bitmap) {
416         ByteArrayOutputStream byteStream = null;
417         try {
418             byteStream = new ByteArrayOutputStream();
419             bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
420             return Asset.createFromBytes(byteStream.toByteArray());
421         } finally {
422             if (null != byteStream) {
423                 try {
424                     byteStream.close();
425                 } catch (IOException e) {
426                     // ignore
427                 }
428             }
429         }
430     }
431 
432     /**
433      * Sends the asset that was created form the photo we took by adding it to the Data Item store.
434      */
sendPhoto(Asset asset)435     private void sendPhoto(Asset asset) {
436         PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH);
437         dataMap.getDataMap().putAsset(IMAGE_KEY, asset);
438         dataMap.getDataMap().putLong("time", new Date().getTime());
439         PutDataRequest request = dataMap.asPutDataRequest();
440         Wearable.DataApi.putDataItem(mGoogleApiClient, request)
441                 .setResultCallback(new ResultCallback<DataItemResult>() {
442                     @Override
443                     public void onResult(DataItemResult dataItemResult) {
444                         LOGD(TAG, "Sending image was successful: " + dataItemResult.getStatus()
445                                 .isSuccess());
446                     }
447                 });
448 
449     }
450 
onTakePhotoClick(View view)451     public void onTakePhotoClick(View view) {
452         dispatchTakePictureIntent();
453     }
454 
onSendPhotoClick(View view)455     public void onSendPhotoClick(View view) {
456         if (null != mImageBitmap && mGoogleApiClient.isConnected()) {
457             sendPhoto(toAsset(mImageBitmap));
458         }
459     }
460 
461     /**
462      * Sets up UI components and their callback handlers.
463      */
setupViews()464     private void setupViews() {
465         mTakePhotoBtn = (Button) findViewById(R.id.takePhoto);
466         mSendPhotoBtn = (Button) findViewById(R.id.sendPhoto);
467 
468         // Shows the image received from the handset
469         mThumbView = (ImageView) findViewById(R.id.imageView);
470         mDataItemList = (ListView) findViewById(R.id.data_item_list);
471 
472         mStartActivityBtn = findViewById(R.id.start_wearable_activity);
473     }
474 
475     /**
476      * As simple wrapper around Log.d
477      */
LOGD(final String tag, String message)478     private static void LOGD(final String tag, String message) {
479         if (Log.isLoggable(tag, Log.DEBUG)) {
480             Log.d(tag, message);
481         }
482     }
483 
484 }
485