Search code examples
androidgoogle-apiwear-osandroid-assetsandroid-wear-data-api

How do I transfer an Android asset without blocking the UI thread?


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.


Solution

  • 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.