/* * Copyright (C) 2014 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; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.WorkerThread; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Tasks; import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.CapabilityClient; import com.google.android.gms.wearable.CapabilityInfo; import com.google.android.gms.wearable.DataClient; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataItem; import com.google.android.gms.wearable.MessageClient; import com.google.android.gms.wearable.MessageEvent; import com.google.android.gms.wearable.Node; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; 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. */ public class MainActivity extends Activity implements DataClient.OnDataChangedListener, MessageClient.OnMessageReceivedListener, CapabilityClient.OnCapabilityChangedListener { private static final String TAG = "MainActivity"; private static final int REQUEST_IMAGE_CAPTURE = 1; private static final String START_ACTIVITY_PATH = "/start-activity"; private static final String COUNT_PATH = "/count"; private static final String IMAGE_PATH = "/image"; private static final String IMAGE_KEY = "photo"; private static final String COUNT_KEY = "count"; private boolean mCameraSupported = false; private ListView mDataItemList; private Button mSendPhotoBtn; private ImageView mThumbView; private Bitmap mImageBitmap; private View mStartActivityBtn; private DataItemAdapter mDataItemListAdapter; // Send DataItems. private ScheduledExecutorService mGeneratorExecutor; private ScheduledFuture<?> mDataItemGeneratorFuture; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LOGD(TAG, "onCreate"); mCameraSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); setContentView(R.layout.main_activity); setupViews(); // Stores DataItems received by the local broadcaster or from the paired watch. mDataItemListAdapter = new DataItemAdapter(this, android.R.layout.simple_list_item_1); mDataItemList.setAdapter(mDataItemListAdapter); mGeneratorExecutor = new ScheduledThreadPoolExecutor(1); } @Override public void onResume() { super.onResume(); mDataItemGeneratorFuture = mGeneratorExecutor.scheduleWithFixedDelay( new DataItemGenerator(), 1, 5, TimeUnit.SECONDS); mStartActivityBtn.setEnabled(true); mSendPhotoBtn.setEnabled(mCameraSupported); // Instantiates clients without member variables, as clients are inexpensive to create and // won't lose their listeners. (They are cached and shared between GoogleApi instances.) Wearable.getDataClient(this).addListener(this); Wearable.getMessageClient(this).addListener(this); Wearable.getCapabilityClient(this) .addListener(this, Uri.parse("wear://"), CapabilityClient.FILTER_REACHABLE); } @Override public void onPause() { super.onPause(); mDataItemGeneratorFuture.cancel(true /* mayInterruptIfRunning */); Wearable.getDataClient(this).removeListener(this); Wearable.getMessageClient(this).removeListener(this); Wearable.getCapabilityClient(this).removeListener(this); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { Bundle extras = data.getExtras(); mImageBitmap = (Bitmap) extras.get("data"); mThumbView.setImageBitmap(mImageBitmap); } } @Override public void onDataChanged(DataEventBuffer dataEvents) { LOGD(TAG, "onDataChanged: " + dataEvents); for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_CHANGED) { mDataItemListAdapter.add( new Event("DataItem Changed", event.getDataItem().toString())); } else if (event.getType() == DataEvent.TYPE_DELETED) { mDataItemListAdapter.add( new Event("DataItem Deleted", event.getDataItem().toString())); } } } @Override public void onMessageReceived(MessageEvent messageEvent) { LOGD( TAG, "onMessageReceived() A message from watch was received:" + messageEvent.getRequestId() + " " + messageEvent.getPath()); mDataItemListAdapter.add(new Event("Message from watch", messageEvent.toString())); } @Override public void onCapabilityChanged(final CapabilityInfo capabilityInfo) { LOGD(TAG, "onCapabilityChanged: " + capabilityInfo); mDataItemListAdapter.add(new Event("onCapabilityChanged", capabilityInfo.toString())); } /** Sets up UI components and their callback handlers. */ private void setupViews() { mSendPhotoBtn = findViewById(R.id.sendPhoto); mThumbView = findViewById(R.id.imageView); mDataItemList = findViewById(R.id.data_item_list); mStartActivityBtn = findViewById(R.id.start_wearable_activity); } public void onTakePhotoClick(View view) { dispatchTakePictureIntent(); } public void onSendPhotoClick(View view) { if (null != mImageBitmap) { sendPhoto(toAsset(mImageBitmap)); } } /** Sends an RPC to start a fullscreen Activity on the wearable. */ public void onStartWearableActivityClick(View view) { LOGD(TAG, "Generating RPC"); // Trigger an AsyncTask that will query for a list of connected nodes and send a // "start-activity" message to each connected node. new StartWearableActivityTask().execute(); } @WorkerThread private void sendStartActivityMessage(String node) { Task<Integer> sendMessageTask = Wearable.getMessageClient(this).sendMessage(node, START_ACTIVITY_PATH, new byte[0]); try { // Block on a task and get the result synchronously (because this is on a background // thread). Integer result = Tasks.await(sendMessageTask); LOGD(TAG, "Message sent: " + result); } catch (ExecutionException exception) { Log.e(TAG, "Task failed: " + exception); } catch (InterruptedException exception) { Log.e(TAG, "Interrupt occurred: " + exception); } } /** * Dispatches an {@link android.content.Intent} to take a photo. Result will be returned back in * onActivityResult(). */ private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } /** * Builds an {@link com.google.android.gms.wearable.Asset} from a bitmap. The image that we get * back from the camera in "data" is a thumbnail size. Typically, your image should not exceed * 320x320 and if you want to have zoom and parallax effect in your app, limit the size of your * image to 640x400. Resize your image before transferring to your wearable device. */ private static Asset toAsset(Bitmap bitmap) { ByteArrayOutputStream byteStream = null; try { byteStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream); return Asset.createFromBytes(byteStream.toByteArray()); } finally { if (null != byteStream) { try { byteStream.close(); } catch (IOException e) { // ignore } } } } /** * Sends the asset that was created from the photo we took by adding it to the Data Item store. */ private void sendPhoto(Asset asset) { PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH); dataMap.getDataMap().putAsset(IMAGE_KEY, asset); dataMap.getDataMap().putLong("time", new Date().getTime()); PutDataRequest request = dataMap.asPutDataRequest(); request.setUrgent(); Task<DataItem> dataItemTask = Wearable.getDataClient(this).putDataItem(request); dataItemTask.addOnSuccessListener( new OnSuccessListener<DataItem>() { @Override public void onSuccess(DataItem dataItem) { LOGD(TAG, "Sending image was successful: " + dataItem); } }); } @WorkerThread private Collection<String> getNodes() { HashSet<String> results = new HashSet<>(); Task<List<Node>> nodeListTask = Wearable.getNodeClient(getApplicationContext()).getConnectedNodes(); try { // Block on a task and get the result synchronously (because this is on a background // thread). List<Node> nodes = Tasks.await(nodeListTask); for (Node node : nodes) { results.add(node.getId()); } } catch (ExecutionException exception) { Log.e(TAG, "Task failed: " + exception); } catch (InterruptedException exception) { Log.e(TAG, "Interrupt occurred: " + exception); } return results; } /** As simple wrapper around Log.d */ private static void LOGD(final String tag, String message) { if (Log.isLoggable(tag, Log.DEBUG)) { Log.d(tag, message); } } /** A View Adapter for presenting the Event objects in a list */ 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; } } private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... args) { Collection<String> nodes = getNodes(); for (String node : nodes) { sendStartActivityMessage(node); } return null; } } /** Generates a DataItem based on an incrementing count. */ private class DataItemGenerator implements Runnable { private int count = 0; @Override public void run() { PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH); putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++); PutDataRequest request = putDataMapRequest.asPutDataRequest(); request.setUrgent(); LOGD(TAG, "Generating DataItem: " + request); Task<DataItem> dataItemTask = Wearable.getDataClient(getApplicationContext()).putDataItem(request); try { // Block on a task and get the result synchronously (because this is on a background // thread). DataItem dataItem = Tasks.await(dataItemTask); LOGD(TAG, "DataItem saved: " + dataItem); } catch (ExecutionException exception) { Log.e(TAG, "Task failed: " + exception); } catch (InterruptedException exception) { Log.e(TAG, "Interrupt occurred: " + exception); } } } }