Home | History | Annotate | Download | only in com.example.android.wearable.datalayer
      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.net.Uri;
     26 import android.os.AsyncTask;
     27 import android.os.Bundle;
     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.GoogleApiClient;
     41 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
     42 import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
     43 import com.google.android.gms.common.api.ResultCallback;
     44 import com.google.android.gms.wearable.Asset;
     45 import com.google.android.gms.wearable.CapabilityApi;
     46 import com.google.android.gms.wearable.CapabilityInfo;
     47 import com.google.android.gms.wearable.DataApi;
     48 import com.google.android.gms.wearable.DataApi.DataItemResult;
     49 import com.google.android.gms.wearable.DataEvent;
     50 import com.google.android.gms.wearable.DataEventBuffer;
     51 import com.google.android.gms.wearable.MessageApi;
     52 import com.google.android.gms.wearable.MessageApi.SendMessageResult;
     53 import com.google.android.gms.wearable.MessageEvent;
     54 import com.google.android.gms.wearable.Node;
     55 import com.google.android.gms.wearable.NodeApi;
     56 import com.google.android.gms.wearable.PutDataMapRequest;
     57 import com.google.android.gms.wearable.PutDataRequest;
     58 import com.google.android.gms.wearable.Wearable;
     59 
     60 import java.io.ByteArrayOutputStream;
     61 import java.io.IOException;
     62 import java.util.Collection;
     63 import java.util.Date;
     64 import java.util.HashSet;
     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
     73  * to the paired wearable.
     74  */
     75 public class MainActivity extends Activity implements
     76         CapabilityApi.CapabilityListener,
     77         MessageApi.MessageListener,
     78         DataApi.DataListener,
     79         ConnectionCallbacks,
     80         OnConnectionFailedListener {
     81 
     82     private static final String TAG = "MainActivity";
     83 
     84     //Request code for launching the Intent to resolve Google Play services errors.
     85     private static final int REQUEST_RESOLVE_ERROR = 1000;
     86 
     87     private static final int REQUEST_IMAGE_CAPTURE = 1;
     88 
     89     private static final String START_ACTIVITY_PATH = "/start-activity";
     90     private static final String COUNT_PATH = "/count";
     91     private static final String IMAGE_PATH = "/image";
     92     private static final String IMAGE_KEY = "photo";
     93     private static final String COUNT_KEY = "count";
     94 
     95     private GoogleApiClient mGoogleApiClient;
     96     private boolean mResolvingError = false;
     97     private boolean mCameraSupported = false;
     98 
     99     private ListView mDataItemList;
    100     private Button mSendPhotoBtn;
    101     private ImageView mThumbView;
    102     private Bitmap mImageBitmap;
    103     private View mStartActivityBtn;
    104 
    105     private DataItemAdapter mDataItemListAdapter;
    106 
    107     // Send DataItems.
    108     private ScheduledExecutorService mGeneratorExecutor;
    109     private ScheduledFuture<?> mDataItemGeneratorFuture;
    110 
    111     @Override
    112     public void onCreate(Bundle savedInstanceState) {
    113         super.onCreate(savedInstanceState);
    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
    133     protected void onStart() {
    134         super.onStart();
    135         if (!mResolvingError) {
    136             mGoogleApiClient.connect();
    137         }
    138     }
    139 
    140     @Override
    141     public void onResume() {
    142         super.onResume();
    143         mDataItemGeneratorFuture = mGeneratorExecutor.scheduleWithFixedDelay(
    144                 new DataItemGenerator(), 1, 5, TimeUnit.SECONDS);
    145     }
    146 
    147     @Override
    148     public void onPause() {
    149         super.onPause();
    150         mDataItemGeneratorFuture.cancel(true /* mayInterruptIfRunning */);
    151     }
    152 
    153     @Override
    154     protected void onStop() {
    155         if (!mResolvingError && (mGoogleApiClient != null) && (mGoogleApiClient.isConnected())) {
    156             Wearable.DataApi.removeListener(mGoogleApiClient, this);
    157             Wearable.MessageApi.removeListener(mGoogleApiClient, this);
    158             Wearable.CapabilityApi.removeListener(mGoogleApiClient, this);
    159             mGoogleApiClient.disconnect();
    160         }
    161         super.onStop();
    162     }
    163 
    164     @Override
    165     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    166         if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
    167             Bundle extras = data.getExtras();
    168             mImageBitmap = (Bitmap) extras.get("data");
    169             mThumbView.setImageBitmap(mImageBitmap);
    170         }
    171     }
    172 
    173     @Override
    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.CapabilityApi.addListener(
    182                 mGoogleApiClient, this, Uri.parse("wear://"), CapabilityApi.FILTER_REACHABLE);
    183     }
    184 
    185     @Override
    186     public void onConnectionSuspended(int cause) {
    187         LOGD(TAG, "Connection to Google API client was suspended");
    188         mStartActivityBtn.setEnabled(false);
    189         mSendPhotoBtn.setEnabled(false);
    190     }
    191 
    192     @Override
    193     public void onConnectionFailed(ConnectionResult result) {
    194         if (!mResolvingError) {
    195 
    196             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.CapabilityApi.removeListener(mGoogleApiClient, this);
    212             }
    213         }
    214     }
    215 
    216     @Override
    217     public void onDataChanged(DataEventBuffer dataEvents) {
    218         LOGD(TAG, "onDataChanged: " + dataEvents);
    219 
    220         for (DataEvent event : dataEvents) {
    221             if (event.getType() == DataEvent.TYPE_CHANGED) {
    222                 mDataItemListAdapter.add(
    223                         new Event("DataItem Changed", event.getDataItem().toString()));
    224             } else if (event.getType() == DataEvent.TYPE_DELETED) {
    225                 mDataItemListAdapter.add(
    226                         new Event("DataItem Deleted", event.getDataItem().toString()));
    227             }
    228         }
    229     }
    230 
    231     @Override
    232     public void onMessageReceived(final MessageEvent messageEvent) {
    233         LOGD(TAG, "onMessageReceived() A message from watch was received:"
    234                 + messageEvent.getRequestId() + " " + messageEvent.getPath());
    235 
    236         mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString()));
    237     }
    238 
    239     @Override
    240     public void onCapabilityChanged(final CapabilityInfo capabilityInfo) {
    241         LOGD(TAG, "onCapabilityChanged: " + capabilityInfo);
    242 
    243         mDataItemListAdapter.add(new Event("onCapabilityChanged", capabilityInfo.toString()));
    244     }
    245 
    246     /**
    247      * Sets up UI components and their callback handlers.
    248      */
    249     private void setupViews() {
    250         mSendPhotoBtn = (Button) findViewById(R.id.sendPhoto);
    251         mThumbView = (ImageView) findViewById(R.id.imageView);
    252         mDataItemList = (ListView) findViewById(R.id.data_item_list);
    253         mStartActivityBtn = findViewById(R.id.start_wearable_activity);
    254     }
    255 
    256     public void onTakePhotoClick(View view) {
    257         dispatchTakePictureIntent();
    258     }
    259 
    260     public void onSendPhotoClick(View view) {
    261         if (null != mImageBitmap && mGoogleApiClient.isConnected()) {
    262             sendPhoto(toAsset(mImageBitmap));
    263         }
    264     }
    265 
    266     /**
    267      * Sends an RPC to start a fullscreen Activity on the wearable.
    268      */
    269     public void onStartWearableActivityClick(View view) {
    270         LOGD(TAG, "Generating RPC");
    271 
    272         // Trigger an AsyncTask that will query for a list of connected nodes and send a
    273         // "start-activity" message to each connected node.
    274         new StartWearableActivityTask().execute();
    275     }
    276 
    277     private void sendStartActivityMessage(String node) {
    278         Wearable.MessageApi.sendMessage(
    279                 mGoogleApiClient, node, START_ACTIVITY_PATH, new byte[0]).setResultCallback(
    280                 new ResultCallback<SendMessageResult>() {
    281                     @Override
    282                     public void onResult(SendMessageResult sendMessageResult) {
    283                         if (!sendMessageResult.getStatus().isSuccess()) {
    284                             Log.e(TAG, "Failed to send message with status code: "
    285                                     + sendMessageResult.getStatus().getStatusCode());
    286                         }
    287                     }
    288                 }
    289         );
    290     }
    291 
    292     /**
    293      * Dispatches an {@link android.content.Intent} to take a photo. Result will be returned back
    294      * in onActivityResult().
    295      */
    296     private void dispatchTakePictureIntent() {
    297         Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    298         if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
    299             startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    300         }
    301     }
    302 
    303     /**
    304      * Builds an {@link com.google.android.gms.wearable.Asset} from a bitmap. The image that we get
    305      * back from the camera in "data" is a thumbnail size. Typically, your image should not exceed
    306      * 320x320 and if you want to have zoom and parallax effect in your app, limit the size of your
    307      * image to 640x400. Resize your image before transferring to your wearable device.
    308      */
    309     private static Asset toAsset(Bitmap bitmap) {
    310         ByteArrayOutputStream byteStream = null;
    311         try {
    312             byteStream = new ByteArrayOutputStream();
    313             bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
    314             return Asset.createFromBytes(byteStream.toByteArray());
    315         } finally {
    316             if (null != byteStream) {
    317                 try {
    318                     byteStream.close();
    319                 } catch (IOException e) {
    320                     // ignore
    321                 }
    322             }
    323         }
    324     }
    325 
    326     /**
    327      * Sends the asset that was created from the photo we took by adding it to the Data Item store.
    328      */
    329     private void sendPhoto(Asset asset) {
    330         PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH);
    331         dataMap.getDataMap().putAsset(IMAGE_KEY, asset);
    332         dataMap.getDataMap().putLong("time", new Date().getTime());
    333         PutDataRequest request = dataMap.asPutDataRequest();
    334         request.setUrgent();
    335 
    336         Wearable.DataApi.putDataItem(mGoogleApiClient, request)
    337                 .setResultCallback(new ResultCallback<DataItemResult>() {
    338                     @Override
    339                     public void onResult(DataItemResult dataItemResult) {
    340                         LOGD(TAG, "Sending image was successful: " + dataItemResult.getStatus()
    341                                 .isSuccess());
    342                     }
    343                 });
    344     }
    345 
    346     private Collection<String> getNodes() {
    347         HashSet<String> results = new HashSet<>();
    348         NodeApi.GetConnectedNodesResult nodes =
    349                 Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
    350 
    351         for (Node node : nodes.getNodes()) {
    352             results.add(node.getId());
    353         }
    354 
    355         return results;
    356     }
    357 
    358     /**
    359      * As simple wrapper around Log.d
    360      */
    361     private static void LOGD(final String tag, String message) {
    362         if (Log.isLoggable(tag, Log.DEBUG)) {
    363             Log.d(tag, message);
    364         }
    365     }
    366 
    367     /**
    368      * A View Adapter for presenting the Event objects in a list
    369      */
    370     private static class DataItemAdapter extends ArrayAdapter<Event> {
    371 
    372         private final Context mContext;
    373 
    374         public DataItemAdapter(Context context, int unusedResource) {
    375             super(context, unusedResource);
    376             mContext = context;
    377         }
    378 
    379         @Override
    380         public View getView(int position, View convertView, ViewGroup parent) {
    381             ViewHolder holder;
    382             if (convertView == null) {
    383                 holder = new ViewHolder();
    384                 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
    385                         Context.LAYOUT_INFLATER_SERVICE);
    386                 convertView = inflater.inflate(android.R.layout.two_line_list_item, null);
    387                 convertView.setTag(holder);
    388                 holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
    389                 holder.text2 = (TextView) convertView.findViewById(android.R.id.text2);
    390             } else {
    391                 holder = (ViewHolder) convertView.getTag();
    392             }
    393             Event event = getItem(position);
    394             holder.text1.setText(event.title);
    395             holder.text2.setText(event.text);
    396             return convertView;
    397         }
    398 
    399         private class ViewHolder {
    400             TextView text1;
    401             TextView text2;
    402         }
    403     }
    404 
    405     private class Event {
    406 
    407         String title;
    408         String text;
    409 
    410         public Event(String title, String text) {
    411             this.title = title;
    412             this.text = text;
    413         }
    414     }
    415 
    416     private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> {
    417 
    418         @Override
    419         protected Void doInBackground(Void... args) {
    420             Collection<String> nodes = getNodes();
    421             for (String node : nodes) {
    422                 sendStartActivityMessage(node);
    423             }
    424             return null;
    425         }
    426     }
    427 
    428     /**
    429      * Generates a DataItem based on an incrementing count.
    430      */
    431     private class DataItemGenerator implements Runnable {
    432 
    433         private int count = 0;
    434 
    435         @Override
    436         public void run() {
    437             PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH);
    438             putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++);
    439 
    440             PutDataRequest request = putDataMapRequest.asPutDataRequest();
    441             request.setUrgent();
    442 
    443             LOGD(TAG, "Generating DataItem: " + request);
    444             if (!mGoogleApiClient.isConnected()) {
    445                 return;
    446             }
    447             Wearable.DataApi.putDataItem(mGoogleApiClient, request)
    448                     .setResultCallback(new ResultCallback<DataItemResult>() {
    449                         @Override
    450                         public void onResult(DataItemResult dataItemResult) {
    451                             if (!dataItemResult.getStatus().isSuccess()) {
    452                                 Log.e(TAG, "ERROR: failed to putDataItem, status code: "
    453                                         + dataItemResult.getStatus().getStatusCode());
    454                             }
    455                         }
    456                     });
    457         }
    458     }
    459 }