7

We are attempting to hook up our AndroidTV app to append results into the global search. I'm running into an issue where I cannot make an api call to get the results because the system calls my content provider on the main thread.

@Override
public Cursor query(Uri uri, String[] projection, String search, String[] selectionArgs, String searchOrder) {

    ... Logic here that calls the API using RxJava / Retrofit

    return cursor;
}


<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/foo"
android:searchSettingsDescription="@string/foo_results"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="com.foo.search.provider"
android:searchSuggestIntentAction="android.intent.action.VIEW" />

<provider
   android:authorities="com.foo.search.provider"
   android:name=".search.GlobalSearchProvider"
   android:exported="true"/>

When i do a global search i can see that the ContentProvider#query is called. If i attempt to do an api call on the current thread i get an networkonmainthreadexception.

I have attempted to notifty the cursor that data has changed via but had no success either.

getContext().getContentResolver().notifyChange(Uri.parse("content://com.foo.test"), null);
...
cursor.setNotificationUri(getContext().getContentResolver(), Uri.parse("content://com.foo.test"));

Is there anyway i can force the O.S to call the content provider on a seperate thread or at least notify the search that the cursor has new content?

Thank You

Darussian
  • 1,573
  • 1
  • 16
  • 28

5 Answers5

6

One of the solutions can be to set the content provider process

android:process:":androidtv"

and set the ThreadPolicy to LAX just before making network call

ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);

By running contentprovider in a different process, even if the query runs on main thread, it will not affect your UI operations

nandeesh
  • 24,740
  • 6
  • 69
  • 79
  • Could you please take a look at my answer below? – Sebastiano Oct 14 '14 at 08:12
  • @dextor I guess you can try to change the order in which your searchableinfo appears in SearchManager.getSearchablesInGlobalSearch() list. I think you can do this by changing the name of searchable Activity. So that your app search will be done last by the search app, but I am not sure if it will work. Further the point of making a separate process is so that the query does not run on main thread, if you just set the policy lax, the query will still run in main thread, and the UI operations might be delayed or you could get ANR – nandeesh Oct 14 '14 at 10:00
  • My search results are already displayed as the last result, so that is a no go. I know that running on a separate process "solves" the issue, but what I've noticed is that the resulting lag is way higher than running on the same process. And I can't figure out why. – Sebastiano Oct 14 '14 at 10:08
  • The lag is due to the fact that you're blocking the UI thread until your results come back. Running on a separate process doesn't do anything since the main UI thread will wait for the results before continuing, no matter what process the request executes on from your side. – Cachapa Apr 17 '15 at 12:04
  • Great solution. I created a ContentProvider which queries result from network and returns it in the form of Cursor. Then I was using android Loader to show data in RecyclerView. But I was getting NetworkOnMainThreadException. I called StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); in my ContentProvider query method before the network call and it worked like a Champ. Thanks a lot @nandeesh – Pioneer Sep 24 '17 at 07:07
1

I've also struggled with this as I didn't find the currently accepted answer of blocking the UI acceptable.

However, according to Marc Bächinger from the Google TV team, this is only a problem with the emulator. In more recent builds (such as those in the currently available hardware) the search providers are called in a background thread, which avoid the problem altogether.

I've been able to test it on the Nexus Player and can confirm it works properly.

Source: https://plus.google.com/+DanielCachapa/posts/dbNMoyoRGEi

Cachapa
  • 1,761
  • 1
  • 15
  • 16
0

EDITED ANSWER

I've experienced this issue myself and I had to rely on the accepted answer's proposed solution. However, I've noticed that there is a noticeable lag when typing in the 'Global search' box. This lag is:

  1. Caused by the application, since its removal makes the lag disappear
  2. Most likely due to a synchronous wait on the queried applications - since our app does two network requests, the query() method takes time to complete, resulting in this lag

I found out that the separate process (:androidtv) is not necessary. By setting the ThreadPolicy.LAX configuration, the network request will still execute without throwing a NetworkOnMainThreadException.

I still don't understand why the lag is there.


ORIGINAL ANSWER

I don't believe that the accepted answer, while it certainly works, is the right way do it.

Once the query() method is called, you should spawn a new thread/task/job to perform a network call (therefore, avoiding the NetworkOnMainThreadException), which will update the adapter once it obtains the desired data.

There are different ways of doing this. You can either use a callback or an event bus (e.g., Otto). This is the method that I call to update the adapter:

public void updateSearchResult(ArrayList<Data> result) {
    mListRowAdapter.clear();
    mListRowAdapter.addAll(0, result);
    HeaderItem header = new HeaderItem(0, "Search results", null);
    mRowsAdapter.add(new ListRow(header, mListRowAdapter));
}
Sebastiano
  • 12,289
  • 6
  • 47
  • 80
  • I cannot kick off a seperate thread because there is no way i can communicate back to the AndroidTV framework that the data in cursor has been updated. – Darussian Oct 04 '14 at 15:14
  • Why not? (maybe I'm missing something) – Sebastiano Oct 04 '14 at 18:14
  • I'm not control in what the framework does, and they have no added any logic for me to communicate back to it. – Darussian Oct 05 '14 at 14:21
  • I'm still missing the point. Is it because you're using RxJava/Retrofit? Or because you have your own provider? Because in my TV application I do as I've written in my answer and it works as expected. – Sebastiano Oct 05 '14 at 14:34
  • In order to to provide AndroidTV with the search results from our app we need to pass it the data via a cursor that is provided through a ContentProvider that is registered to supply Global Result. In order to get the search results I need to query our API which has to be either on a seperate thread or with ThreadPolicy.LAX. Your answer works if your are doing presenting search results within the app because you have direct access to the adapter that is showing the data. For global search all i have control over is the cursor that gets passed to the framework that handles presenting the data. – Darussian Oct 05 '14 at 19:18
  • I have come to face your exact same problem. The solution posted above works, but it introduces a lag in the search interface (which is not there if I uninstall the application). Most likely it is due to the network call. Is it the same for you? – Sebastiano Oct 13 '14 at 17:16
  • I have noticed a slight lag as well. I believe this is currently the only way to do this until Google adds hooks to notify that data has changed. – Darussian Oct 14 '14 at 01:05
0

In order to solve for displaying the result in the global search using API in query method what I basically did is introduce a delay between fetching of api result and querying the db for results to return a cursor.

You can do this via

private Cursor getSuggestions(final String query) {
    Cursor cursor;
    cursor = getCursor(query);
    if (cursor==null || cursor.getCount() == 0) {
    //apiCall
      try {
        Thread.sleep(X millis);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      cursor = getCursor(query);
    }
    return cursor;
  }

Will keep on looking to see if, we can get some kind of hook to reattach without using a delay.

Ankit Khare
  • 1,345
  • 11
  • 30
0

Because I wasn't really familiar with the Android thread. For anyone who has the same problem as me. The main point is that the query() method in content provider is not running on the UI thread.

Instead of using the asynchronous function to do HTTP requests and then update the cursor, please simply use the synchronous function to do HTTP requests and then return the cursor with the data you want.