close
The Wayback Machine - https://web.archive.org/web/20160415201946/https://developer.android.com/samples/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.html
Show navigation Hide navigation
DataLayer / Application / src / com.example.android.wearable.datalayer /

MainActivity.java

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