4

So I got an exception from my app as follows:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=http://google.com (has extras) }
    at  android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1512)
    at  android.app.Instrumentation.execStartActivity(Instrumentation.java:1384)
    at  android.app.Activity.startActivityForResult(Activity.java:3248)
    at  android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:839)
    at  android.app.Activity.startActivity(Activity.java:3359)
    at  android.text.style.URLSpan.onClick(URLSpan.java:62)
    at  android.text.method.LinkMovementMethod.onTouchEvent(LinkMovementMethod.java:212)
    at  android.widget.TextView.onTouchEvent(TextView.java:8704)
    at  android.view.View.dispatchTouchEvent(View.java:5556)
    at  android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957)
    at  android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726)
    at  android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957)
    at  android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726)
    at  android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957)
    at  android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726)
    at  android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957)
    at  android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726)
    at  android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957)
    at  android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726)
    at  android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1957)
    at  android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1726)
    at  com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1940)
    at  com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1390)
    at  android.app.Activity.dispatchTouchEvent(Activity.java:2414)
    at  com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1888)
    at  android.view.View.dispatchPointerEvent(View.java:5736)
    at  android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3017)
    at  android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2582)
    at  android.view.ViewRootImpl.processInputEvents(ViewRootImpl.java:887)
    at  android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2591)
    at  android.os.Handler.dispatchMessage(Handler.java:99)
    at  android.os.Looper.loop(Looper.java:137)
    at  android.app.ActivityThread.main(ActivityThread.java:4697)
    at  java.lang.reflect.Method.invokeNative(Native Method)
    at  java.lang.reflect.Method.invoke(Method.java:511)
    at  com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:787)
    at  com.android.internal.os.ZygoteInit.main(ZygoteInit.java:554)
    at  dalvik.system.NativeStart.main(Native Method)

This doesn't lead to my code. I assume this happens when user clicks URL in the text (and there are texts with URLs in app). What it's about? How could it be that Android device doesn't have a browser? How to bypass this or at least to catch (I need URLs be clickable in text)?

UPDATE
Actually I found that this exception happens in TextView which is intended to be an empty list view stub:

listView = (ListView) findViewById(R.id.listView);
// empty pass list with clickable url inside
final TextView listEmptyView = (TextView) findViewById(R.id.txtNoPasses);
listEmptyView.setMovementMethod(LinkMovementMethod.getInstance());
listView.setEmptyView(listEmptyView);

So I tried this (following CommonsWare's article) but my URL remains completely unclickable in this case:

    listView = (ListView) findViewById(R.id.listView);
    // empty pass list with clickable url inside
    final TextView listEmptyView = (TextView) findViewById(R.id.txtNoPasses);
    fixTextView(listEmptyView);
    listView.setEmptyView(listEmptyView);
}

private void fixTextView(TextView textView) {
    final SpannableString current = new SpannableString(textView.getText());
    final URLSpan[] spans = current.getSpans(0, current.length(), URLSpan.class);
    int start, end;
    for (URLSpan span : spans) {
        start = current.getSpanStart(span);
        end = current.getSpanEnd(span);
        current.removeSpan(span);
        current.setSpan(new DefensiveURLSpan(span.getURL()), start, end, 0);
    }
}

private static class DefensiveURLSpan extends URLSpan {
      public DefensiveURLSpan(String url) {
        super(url);
      }

      @Override
      public void onClick(View widget) {
        try {
          android.util.Log.d(getClass().getSimpleName(), "Got here!");
          super.onClick(widget);
        }
        catch (ActivityNotFoundException e) {
          // do something useful here
          // android.text.SpannedString cannot be cast to android.text.SpannableString
        }
      }
}

And TextView:

<TextView
        android:id="@+id/txtNoPasses"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:visibility="gone"
        android:gravity="center"
        android:padding="15dp"
        android:text="@string/no_passes"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

And no_passes string is following:

<string name="no_passes">Currently you have no cards to use.\n<a href="http://cardz.website.com">You could add some cards from our website</a></string>
Sufian
  • 5,997
  • 14
  • 60
  • 111
Stan
  • 6,121
  • 8
  • 48
  • 85

2 Answers2

5

I assume that this is a TextView with android:autolink set, or that otherwise displays HTML (for example, using Html.fromHtml()).

The problems is that this creates URLSpan instances for embedded URIs, and this class "blindly" calls startActivity() with the supplied URI. This crashes whenever the URI does not match with any registered activity. For "http" URLS this can happen when the device doesn't have a browser application, or (more likely) when using a restricted profile that blocks access to it.

The problem and its solution is well explained in this CommonsWare post. You can replace the URLSpan instances with a custom derived class that intercepts onClick() to avoid the exception.

See also this answer for a related issue.

Community
  • 1
  • 1
matiash
  • 52,725
  • 16
  • 117
  • 149
  • This is almost the answer. However I use nor URLSpan neither Linkify but setMovementMethod(LinkMovementMethod.getInstance()) and I have no idea how to apply CommonsWare's article to this. The only visible way for me now - is to follow Jeff Mixon's answer to apply or don't the setMovementMethod(). – Stan Nov 07 '14 at 17:13
  • @Stan The solution should be the same, as `URLSpan` is used behind the scenes (as you can see from the exception stack). You should be able to copy `fixTextView()` from the post as is. – matiash Nov 07 '14 at 17:25
  • Actually I already tried it together with removing setMovementMethod() but the link stays not clickable as a result. Did I miss something? I'll add the code to my question. – Stan Nov 07 '14 at 17:31
  • @Stan Don't remove `setMovementMethod()`, that is ok. Just adding the `fixTextView()` after `setText()` should suffice. – matiash Nov 07 '14 at 18:04
2

It's possible for an Android device not to have a browser. It is not required, per say, but obviously pretty rare. Android-ish devices like Kindles come to mind, although they still would have a browser...

Nonetheless, you can disable URL linking in your TextView as follows:

textView.setLinksClickable(false);

Also, related question.

Edit

See this answer:

String url = "http://www.example.com";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
PackageManager manager = mContext.getPackageManager();
List<ResolveInfo> list = manager.queryIntentActivities(intent, 0);

if (list != null && list.size() > 0) {
    //You have at least one activity to handle the intent
} else {
    //No activity to handle the intent.
}

Create a new intent with ACTION_VIEW and a url and use this pattern to determine whether or not the device has any apps which can handle a url. If it doesn't, then you disable clicking links on the TextView for that user.

Community
  • 1
  • 1
Jeffrey Mixon
  • 10,446
  • 3
  • 25
  • 42
  • Yeah, but I need it clickable and it works on most devices. I mean I surprised a bit (ok, its android, it will always be filled with bunches of bugs) but I wish to handle such situations if its possible to avoid app crash... So its not about disallowing urls clicking its about how to avoid app crash in such situations. – Stan Nov 04 '14 at 22:42
  • See my edit. You just check if the user has any valid apps for that `Intent` signature and disable linking if not. – Jeffrey Mixon Nov 04 '14 at 22:49
  • This works in my case cuz I can decive to apply or don't the setMovementMethod(). However for URLSpan the matiash's answer is better choice. – Stan Nov 07 '14 at 17:14