0

I'm trying to transfer an asset by following the android developer training which says to use this code:

@Override
public void onDataChanged(DataEventBuffer dataEvents) {
  for (DataEvent event : dataEvents) {
    if (event.getType() == DataEvent.TYPE_CHANGED &&
        event.getDataItem().getUri().getPath().equals("/image")) {
      DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
      Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
      Bitmap bitmap = loadBitmapFromAsset(profileAsset);
      // Do something with the bitmap
    }
  }
}

public Bitmap loadBitmapFromAsset(Asset asset) {
    if (asset == null) {
        throw new IllegalArgumentException("Asset must be non-null");
    }
    ConnectionResult result =
           mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);
    if (!result.isSuccess()) {
        return null;
    }
    // convert asset into a file descriptor and block until it's ready
    InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
            mGoogleApiClient, asset).await().getInputStream();
            mGoogleApiClient.disconnect();

    if (assetInputStream == null) {
        Log.w(TAG, "Requested an unknown Asset.");
        return null;
    }
    // decode the stream into a bitmap
    return BitmapFactory.decodeStream(assetInputStream);
}

So I have done the same thing in roughly the same way:

    // Build a new GoogleApiClient for the Wearable API
    googleClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                @Override
                public void onConnected(Bundle bundle) {
                    Wearable.DataApi.addListener(googleClient, onDataChangedListener);
                }

                @Override
                public void onConnectionSuspended(int i) {

                }
            })
            .addApi(Wearable.API)
            .build();
    googleClient.connect();

and in my onDatachanged method I have:

public DataApi.DataListener onDataChangedListener = new DataApi.DataListener() {
    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        Log.d(TAG, "Data changed: " + dataEvents);

        for (DataEvent event : dataEvents) {
            Log.d(TAG, "Data received: " + event.getDataItem().getUri());

            if (event.getType() == DataEvent.TYPE_CHANGED &&
                event.getDataItem().getUri().getPath().equals("/audio")) {
                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
                Asset audioAsset = dataMapItem.getDataMap().getAsset("audioAsset");
                audioBytes = loadBytesFromAsset(audioAsset);
            }

            // Set play button enabled
            handler.post(onNewAudio());
        }
    }
}

with my loadBytesFromAsset() method:

public byte[] loadBytesFromAsset(Asset asset) {
    if (asset == null) {
        throw new IllegalArgumentException("Asset must be non-null");
    }

    result = googleClient.blockingConnect(3000, TimeUnit.MILLISECONDS);
    if(!result.isSuccess()){
        return null;
    }

    // Convert asset into a file descriptor and block until it's ready
    InputStream assetInputStream = Wearable.DataApi.getFdForAsset(googleClient, asset).await().getInputStream();
    googleClient.disconnect();

    if (assetInputStream == null) {
        Log.w(TAG, "Requested an unknown Asset.");
        return null;
    }
    // Decode the stream into a byte[]
    return getBytesFromInputStream(assetInputStream);
}

This seems to be doing exactly as the Android developer training suggests, but when I run it, the 'loadBytesFromAsset()' method crashes with an exception saying I can't call blockingConnect() on the UI thread. Does anyone know how to solve this? How should I be listening for and then retrieving assets? Thanks in advance.

aga
  • 27,954
  • 13
  • 86
  • 121
Highway62
  • 800
  • 1
  • 10
  • 25

3 Answers3

2

Ok, I got it working (kind of), still having problems with onDataChanged not being called, but the issue with the UI thread and blocking.connect call was solved by re-doing the code like this post here. The way they have done it is by making the class implement the DataApi.DataListener, GoogleApiClient.ConnectionCallbacks, and GoogleApiClient.OnConnectionFailedListener interfaces, like so:

public class MainActivity extends Activity implements
        DataApi.DataListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{
    private TextView mTextView;
    private static final long CONNECTION_TIME_OUT_MS = 100;
    private static final String ON_MESSAGE = "On!";
    private static final String OFF_MESSAGE = "Off!";
    private static final String TAG = "Moto360DisplayControl";
    private GoogleApiClient client;
    private String nodeId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initApi();
    }

    private void initApi() {
        client = getGoogleApiClient(this);
        retrieveDeviceNode();
    }

    private GoogleApiClient getGoogleApiClient(Context context) {
        return new GoogleApiClient.Builder(context)
            .addApi(Wearable.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();
    }

    private void retrieveDeviceNode() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
                NodeApi.GetConnectedNodesResult result =
                    Wearable.NodeApi.getConnectedNodes(client).await();
                List<Node> nodes = result.getNodes();
                if (nodes.size() > 0) {
                    nodeId = nodes.get(0).getId();
                }
                client.disconnect();
            }
        }).start();
    }

    @Override
    protected void onStart() {
        super.onStart();
        client.connect();
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        Wearable.DataApi.addListener(client, this);
        Toast.makeText(this, "AddedListener!", Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionSuspended(int num) {
        Toast.makeText(this, "ConnectionSuspended", Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionFailed(ConnectionResult res) {
        Toast.makeText(this, "ConnectionFailed", Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onStop() {
        Wearable.DataApi.removeListener(client, this);
        client.disconnect();
        super.onStop();
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        Toast.makeText(this, "DataChanged!", Toast.LENGTH_LONG).show();
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().equals("/image")) {
                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
                Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
                Bitmap bitmap = loadBitmapFromAsset(profileAsset);
                // Do something with bitmap
                Toast.makeText(this, "DataChanged!", Toast.LENGTH_LONG).show();
            }
        }
    }

    public Bitmap loadBitmapFromAsset(Asset asset) {
        if (asset == null) {
            throw new IllegalArgumentException("Asset must be non-null");
        }

        ConnectionResult result = client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
        if (!result.isSuccess()) {
            return null;
        }

        // Convert asset into a file descriptor and block until it's ready
        InputStream assetInputStream = Wearable.DataApi.getFdForAsset(client, asset).await().getInputStream();
        client.disconnect();

        if (assetInputStream == null) {
            Log.w(TAG, "Requested an unknown Asset.");
            return null;
        }

        // Decode the stream into a bitmap
        return BitmapFactory.decodeStream(assetInputStream);
    }
}

Using this method, the problem was solved, I'm still working on trying to solve the problem of onDataChanged not being called, which I have asked here.

Community
  • 1
  • 1
Highway62
  • 800
  • 1
  • 10
  • 25
1

(Disclaimer: I never actually tested this answer, but it is just me reading through the API)

Please see this - https://developers.google.com/android/reference/com/google/android/gms/common/api/GoogleApiClient

You can see that along side connectBlocking you also have connect. And the documentation says

Connects the client to Google Play services. This method returns immediately, and connects to the service in the background. If the connection is successful, onConnected(Bundle) is called and enqueued items are executed. On a failure, onConnectionFailed(ConnectionResult) is called.

So what you need to do is call registerConnectionCallbacks and pass it a ConnectionCallbacks that implements onConnected. These callbacks will run in the UI thread (just like your current callbacks are running there). In addition, you can also do the same with isConnectionFailedListenerRegistered which will be called when the connection fails. This is actually what you are already doing in the first segment of your code, just that there you are setting the listener in the builder.

This will require some changing of your code, but I don't think anything too serious.

Ginandi
  • 843
  • 8
  • 20
  • Oh, and don't forget to call unregisterConnectionCallbacks when you are the callback finishes its work – Ginandi Dec 23 '15 at 21:57
0

Given the code structure that you have, it seems to me that you are registering your onDataChangedListener in onConnected() callback (which is the right place). In your onDataChangedListener#onDataChanged() callback (which is called on the Main thread), you are calling loadBytesFromAsset(). In this method, you don't need to connect your google api client again; it should already be connected at that point so there is no need to call the blocking connect method. It is a good practice to check to make sure you are connects (apiClient.isConnected()) and then go ahead and do what you would like to do.

There is also no need to disconnect your API client prior to leaving your app (in fact, better not to do so) unless you are really sure that you don't want to do anything else in your app that would need a connection; it is better to just call the disconnect in the onStop() of your activity (and make a connection in onStart()).

That said, if any process that you need to do in those callbacks (which are on the Main thread) is a long process, then you would need to spawn a separate thread (say, using AsyncTask, an IntentService or something of that sort) and handle the long process there.

Ali Naddaf
  • 16,951
  • 2
  • 21
  • 28