Android

Build your smart car’s on-board computer!

Reading this post you will be able to create your smart car’s on-board computer, for example you will be to able to ask your Google Assistant (from your device or from an Android Auto head unit) the temperature of your car’s coolant liquid! Woa!

smart car

 

What do you need:

  • an Android device (with Google Assistant or Android Auto since your going to use this in your car)
  • Torque
  • Tasker
  • Join
  • An IFTTT account
  • an OBD2 device

Such a long list of stuff!

Let’s start!

IFTTT AND TASKER INTEGRATION

Thanks to Join, a new app from joaomgcd, you can create a URL to push information to your devices in a very easy and powerful way! Read his well written tutorial here  and you will be able to generate a URL to use in the Webhook that action of IFTTT.

As reference, I’d like to report the old way of doing this, so everyone can read it and understand how much Join has simplified and improved this process!

TASKER AND TORQUE INTEGRATION

Now, you will need to make Tasker able to read your car’s data using Torque. To accomplish this, open Torque and set it up to log your car’s data to a file on your sdcard (you can do it making changes in the Settings – Data Logging and Upload section)

Now Tasker will be able to read your car’s data reading that log file using the following task which you could import into it.

<TaskerData sr="" dvi="1" tv="5.2.bf6">
  <Task sr="task43">
    <cdate>1432225457845</cdate>
    <edate>1531597970703</edate>
    <id>43</id>
    <nme>ReadCarEngineCoolantTemperature</nme>
    <pri>100</pri>
    <Action sr="act0" ve="7">
      <code>342</code>
      <Int sr="arg0" val="5"/>
      <Str sr="arg1" ve="3">/storage/emulated/0/torqueLogs/trackLog.csv</Str>
      <Str sr="arg2" ve="3">%TORQUE_LOG_EXISTS</Str>
      <Int sr="arg3" val="0"/>
    </Action>
    <Action sr="act1" ve="7">
      <code>123</code>
      <se>false</se>
      <Str sr="arg0" ve="3">tail -1 /storage/emulated/0/torqueLogs/trackLog.csv</Str>
      <Int sr="arg1" val="0"/>
      <Int sr="arg2" val="0"/>
      <Str sr="arg3" ve="3">%OBDLOG</Str>
      <Str sr="arg4" ve="3"/>
      <Str sr="arg5" ve="3"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act2" ve="7">
      <code>590</code>
      <Str sr="arg0" ve="3">%OBDLOG</Str>
      <Str sr="arg1" ve="3">,</Str>
      <Int sr="arg2" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act3" ve="7">
      <code>547</code>
      <Str sr="arg0" ve="3">%EngineCoolantTemp</Str>
      <Str sr="arg1" ve="3">%OBDLOG2</Str>
      <Int sr="arg2" val="0"/>
      <Int sr="arg3" val="0"/>
      <Int sr="arg4" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act4" ve="7">
      <code>559</code>
      <Str sr="arg0" ve="3">Engine coolant temperature is %EngineCoolantTemp degrees</Str>
      <Str sr="arg1" ve="3">default:default</Str>
      <Int sr="arg2" val="3"/>
      <Int sr="arg3" val="5"/>
      <Int sr="arg4" val="5"/>
      <Int sr="arg5" val="1"/>
      <Int sr="arg6" val="0"/>
      <Int sr="arg7" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act5" ve="7">
      <code>559</code>
      <Str sr="arg0" ve="3">E C U connection not established!</Str>
      <Str sr="arg1" ve="3">default:default</Str>
      <Int sr="arg2" val="3"/>
      <Int sr="arg3" val="5"/>
      <Int sr="arg4" val="5"/>
      <Int sr="arg5" val="1"/>
      <Int sr="arg6" val="0"/>
      <Int sr="arg7" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>false</rhs>
        </Condition>
      </ConditionList>
    </Action>
  </Task>
</TaskerData>

This task will check for that log file on your external memory and:

  • if it founds it, it make your speakers say the last engine coolant temperature read by Torque
  • if it doesn’t found it, it make your speakers say there is no connection between Torque and you car ECU (your OBD2 adapter).

Another task that you will need is one that deletes that log file when the Torque app closes, so the next time you ask Tasker to read that file your are sure the data is new, and if there is no file, a new connection with your car wasn’t established.

<TaskerData sr="" dvi="1" tv="5.2.bf6">
  <Task sr="task47">
    <cdate>1525023939110</cdate>
    <edate>1529001057169</edate>
    <id>47</id>
    <nme>DeleteTorqueLog</nme>
    <pri>100</pri>
    <Action sr="act0" ve="7">
      <code>406</code>
      <Str sr="arg0" ve="3">torqueLogs/trackLog.csv</Str>
      <Int sr="arg1" val="0"/>
      <Int sr="arg2" val="0"/>
    </Action>
  </Task>
</TaskerData>

GOOGLE ASSISTANT AND IFTTT AND TASKER AND TORQUE INTEGRATION

Now the last part! We have all the pieces, let’s put them together!

Login to IFTTT and create a new applet!

Click on this and look for Google Assistant, then click on it. Select the Say a simple phrase trigger and write your custom question(s) and response; click then on Create trigger

Click now on that and look for Webhooks, then click on it. Select the Make a web request action and use the URL you generated as explained in the joaomgcd’s tutorial. Click on Create action, then on Finish and your recipe is ready!

Open your Google Assistant and say the phrase you just put in the IFTTT recipe, your device should say you’re not connected to the ECU, you got the push notification, that’s great!

Now go to your car, turn it on, make Torque pair with your OBD2 adapter…. when it’s ready ask Google Assistant again to read your engine coolant temperature and now you should hear it! Wonderful, it works!!!

FURTHER IMPROVEMENTS OF YOUR SMART CAR

The recipe on IFTTT and the task on Tasker could be improved by using parameters and so making you able to ask for and listen to different details of your car, which could now be called a smart car!

Android, Development

AndroidAutoAlerts – Simple alerts app for Android Auto

AndroidAutoAlerts is a very simple app for Android Auto I have developed to test my Android Auto unit. Please read through the article to check it out!

What it does and how it works

As I said, the app is very simple and at the moment it has only one functionality: it will notify the driver if the speed limit is exceeded. Wow!

The driver can set the maximum speed limit through the app’s settings (you can also choose the name of your virtual assistant!)

androidautoalerts settings

Then, go back to the main screen and start the notification service. A service will run (and will keep running in the foreground using a system notification when you exit the app or attach your device to your Android Auto unit).

androidautoalerts mainscreenandroidautoalerts foreground notification

So, now, using the speed taken from the GPS sensor, when the app catches you going faster a notification will be displayed on your Android Auto unit from your virtual assistant! Tap it and listen to what it has to say to you! Please slow down, you have exceeded your speed limit!!

androidautoalerts notification

 

Where to get it

At the moment, the app is not following all of the Android Auto requirements (see them here) and Google only allows music and messaging Android Auto apps. For this reason I had to create the notification like a messaging app, like you are going to reply to it, even if you are not. Furthermore, I’m not going to publish it on Google Play at the moment (till apps of this kink will be accepted on it), but you can check its source code which I shared here on github.

Future development

Let’s wait for Google to accept other kinds of apps on the Android Auto platform and let’s hope this platform will become more and more interactive, full of useful apps (maybe some which can control car’s features, would be amazing!) and used by more and more people! I already love it, can’t live without it!

Android, Development

Android Contextual Action Bar

In this post you will learn how to implement a Contextual Action Bar (CAB) which will be useful to do actions on multiple items you have selected in a RecyclerView.

The Contextual Action Mode represents a contextual mode of the user interface and focuses user interaction toward performing contextual actions. In the case of a RecyclerView which shows a list of item, Contextual Action Mode is triggered after a long press on one of this items: this causes the Contextual Action Bar to appear at the top of the screen so then the user can interact with its actions.

Let’s follow these steps!

Initial setup

First of all, create a RecyclerView and its adapter in the usual way you do it. Here in this example I will use a RecyclerView which shows Authentication items (it is a class I made for a project, it doesn’t matter for the purpose of the example, use whatever you want, and change the names accordingly).

Theme changes

Open your styles.xml file and add the following lines:

<!--  It should be true otherwise action mode will not overlay toolbar -->
       <item name="windowActionModeOverlay">true</item>
       <!--  For Custom Action Mode Background Color/Drawable -->
       <item name="actionModeBackground">@color/colorAccent</item>

Comments should be self-explanatory.

Menu file

We need a menu file which contains the available actions for that RecyclerView. Let’s create a file into the res/menu directory like the following:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_delete"
        android:icon="@drawable/ic_delete_white_24dp"
        android:title="@string/action_delete"
        app:showAsAction="always" />

</menu>

We added a delete action that will be shown on the CAB.

Adapter changes

Our adapter we created in the first step needs to be able to keep trace of the elements we select. To do this, let’s modify our adapter and create a new field

private final SparseBooleanArray selectedItemsIds;

and initialize it in our constructor

selectedItemsIds = new SparseBooleanArray();

Now, let’s create an Interface which will be useful for all the Adapters which need the CAB, and make our Adapter implements it. Please note this is a generic interface, so adapt it to the model you are displaying in your adapter.

public interface ActionModeAdapterCallbacks<T> {

    void toggleSelection(int position);

    void clearSelections();

    int getSelectedCount();

    List<T> getSelectedItems();
}

This is what the implementation should look like:

@Override
public void toggleSelection(final int position) {
    if (selectedItemsIds.get(position)) {
        selectedItemsIds.delete(position);
    } else {
        selectedItemsIds.put(position, true);
    }
    notifyItemChanged(position);
}

@Override
public void clearSelections() {
    selectedItemsIds.clear();
    notifyDataSetChanged();
}

@Override
public int getSelectedCount() {
    return selectedItemsIds.size();
}

@Override
public List<Authentication> getSelectedItems() {
    final List<Authentication> selectedItemList = new LinkedList<>();
    for (int i = 0; i < selectedItemsIds.size(); i++) {
        selectedItemList.add(authenticationList.get(selectedItemsIds.keyAt(i)));
    }
    return selectedItemList;
}

Now, we also need the Adapter to change the view state when an item gets selected to show the user a visual feedback. To do this we can use a StateListDrawable and in the onBindViewHolder() method add this line.

holder.itemView.setActivated(selectedItemsIds.get(position));

Create a new xml file inside the res/drawable directory:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorPrimaryDark" android:state_activated="true" />
    <item android:drawable="@android:color/transparent" />
</selector>

and assign it to the root view element of the layout file which represents your adapter’s row item:

android:background="@drawable/statelist_item_background"

Doing so, each time an item is selected or deselected, the adapter will change its color accordingly.

Fragment/Activity changes

Now, we need to make our Fragment/Activity class aware that we can trigger the Contextual Action Mode. To do so, let’s create an interface and call it ActionModeViewCallbacks.

public interface ActionModeViewCallbacks {

    void onListItemSelect(final int position);

    void onDestroyActionMode();

}

This interface has 2 methods:

  • onListItemSelect(final int position): to be used after a long click on an item to trigger the Action Mode (or after a single click on an item, if Action Mode was already triggered, to select this new item, too);
  • onDestroyActionMode(): reset actionMode variable to null;

Now, let’s create another interface which extends from this one:

public interface ListAuthenticationActionModeViewCallbacks extends ActionModeViewCallbacks {

    void onDeleteActionClicked();

}

This new interface has another method, onDeleteActionClicked(), which is where the presenter gets called (check MVP if you are not aware what MVP and a Presenter are) and asked to delete the items the user has selected (if you have more than a single action, you need to create more methods, each for any action you have).

This interface must be implemented by the Fragment/Activity which wants to use the Contextual Action Mode. The implementation looks like this:

@Override
    public void onListItemSelect(final int position) {
        listAuthenticationAdapter.toggleSelection(position);

        final boolean hasCheckedItems = listAuthenticationAdapter.getSelectedCount() > 0;

        if (hasCheckedItems && actionMode == null) {
            // there are some selected items, start the actionMode
            actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new ListAuthenticationToolbarActionModeCallback(this, this, listAuthenticationAdapter));
        } else if (!hasCheckedItems && actionMode != null) {
            // there no selected items, finish the actionMode
            actionMode.finish();
        }

       if (actionMode != null) {
            //set action mode title on item selection
            actionMode.setTitle(getString(R.string.cab_selected, listAuthenticationAdapter.getSelectedCount()));
        }
        
    }

    @Override
    public void onDestroyActionMode() 
        actionMode = null;
    }


@Override
public void onDeleteActionClicked() {
 presenter.delete(listAuthenticationAdapter.getSelectedItems());
}

where R.string.cab_selected is

<string name="cab_selected">%1$d selected</string>

In doing startSupportActionMode() we need to pass a ActionMode.Callback item that I haven’t explained yet. Let’s create another class which implements that interface.

public class ListAuthenticationToolbarActionModeCallback implements ActionMode.Callback {

    private final ActionModeViewCallbacks actionModeViewCallbacks;
    private final ListAuthenticationAdapter listAuthenticationAdapter;

    public ListAuthenticationToolbarActionModeCallback(final ActionModeViewCallbacks actionModeViewCallbacks, final ListAuthenticationAdapter listAuthenticationAdapter) {
        this.actionModeViewCallbacks = actionModeViewCallbacks;
        this.listAuthenticationAdapter = listAuthenticationAdapter;
    }

    @Override
    public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
        mode.getMenuInflater().inflate(R.menu.listauthentication, menu);
        return true;
    }

    @Override
    public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
        menu.findItem(R.id.action_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        return true;
    }

    @Override
    public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_delete:
                actionModeViewCallbacks.onDeleteActionClicked();
                mode.finish();
                return true;
        }
        return false;
    }

    @Override
    public void onDestroyActionMode(final ActionMode mode) {
        actionModeViewCallbacks.onDestroyActionMode();
        listAuthenticationAdapter.clearSelections();
    }
}

Please have a look on the official documentation here to understand how ActionMode.Callback works.

Screenshots

See the following screenshots:

  • first one shows the initial situation before Action Mode is triggered;
  • second one shows the situation after Action Mode was triggered by long press an item;
  • third one shows the final situation after the item “Auth 2” was deleted.

contextual action bar first contextual action bar selected contextual action bar after delete

Conclusions

Your Contextual Action Mode should work now! I suggest you to create different menu files, Fragment/ActivityToolbarActionModeCallback and Fragment/ActivityActionModeViewCallbacks to keep everything separated.

Please note: if you rotate the device, Contextual Action Mode is lost, you need to save its state and restore it. Another post will follow, hopefully soon!

Android, Development

Android NoteApp with Dagger and Retrofit

Today I show a simple Android project I wrote which shows the use of Dagger, Retrofit and some other stuff.

The full source code of the project is available for you here

The project’s main purposes are the following:

  • Show Dagger2 dependency injection;
  • Show MVP architecture (made through Dagger2)
  • Show the repository pattern used to cache server data
  • Show the creation and use of a CustomView

Furthermore, I have used ButterKnife to remove the boilerplate code needed to bind classes/views, and GreenDao to automatically create the SQLite repository/model classes.

This project is based on the Google Android Architecture samples which are available here

Android

Custom Fragment Backstack in Android

Dealing with Fragments is always a pain: after some time I found a way to deal with them in a less painful way, creating a custom Fragment Backstack.

How to create your custom Fragment Backstack navigation

Put this code in your main Activity:

public class MainActivity extends AppCompatActivity {

    private Stack<Fragment> fragmentStack;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fragmentStack = new Stack<>();
    }

    @Override
    public void onBackPressed() {
        fragmentStack.pop();
        if (fragmentStack.size() == 0) {
            super.onBackPressed();
        } else {
            showFragment(fragmentStack.lastElement(), false);
        }
    }

    public void showFragment(Fragment fragment, boolean addToStack) {
        if (addToStack) {
            fragmentStack.push(fragment);
        }
        getFragmentManager().beginTransaction().replace(R.id.container_fragment, fragment).commit();
    }

}

We have a fragmentStack field which is a Stack of Fragment: we use this to save the Fragments we show in our app and we initialize it in the onCreate() method of the main Activity.

In the onBackPressed() method we remove the current Fragment from the Stack and then if there is another one, we show it, otherwise we close the app because there is nothing more to be shown.

The method showFragment() is the one we use to show a Fragment on the screen; we invoke it every time we need to change the visible Fragment. If we want to save the current transaction being able to come back to the current Fragment we pass true as addToStack value, otherwise we pass false.

Have a look at the full source code on my GitHub here.

Android

Auto hide keyboard using Android EditText

Do you want to automatically hide your keyboard whenever you touch outside it? Please continue reading and you will find out how to do it!

First, we will create our AutoHideKeyboardEditText class like the following:

public class AutoHideKeyboardEditText extends EditText {

    public AutoHideKeyboardEditText(Context context) {
        super(context);
        init();
    }

    public AutoHideKeyboardEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AutoHideKeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    hideKeyboard(v);
                }
            }
        });
    }

    private void hideKeyboard(View view) {
        InputMethodManager inputMethodManager = (InputMethodManager)
            view.getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
}

Whatever Constructor is used, we set an onFocusChangeListener to hide the keyboard using the proper method if we touch outside the EditText area (if the focus is not anymore on it).

Afterwards, create a layout similar to this

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical">

being sure, whatever layout you want to use, to add clickable and focusableInTouchMode attributes with true value.

Now just add our AutoHideKeyboardTextView inside that layout. You can simply add it using XML like the following

<com.w3ma.androidshowcase.autohidekeyboard.AutoHideKeyboardEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

 

You are done! Whenever you touch outside the EditText the keyboard will be automatically dismissed!

Please note: you should add the clickable and focusableInTouchMode attributes to the outermost view but if it is a ScrollView this might not work. For such case, those attributes could be added to the view directly under the ScrollView.

Full source code on my GitHub repository here.

Source:https://stackoverflow.com/questions/4165414/how-to-hide-soft-keyboard-on-android-after-clicking-outside-edittext/19828165#19828165

I don’t know why this wasn’t voted as the best answer because it is indeed.

Android

Android Studio gitignore to avoid useless files

Create a perfect Android Studio .gitignore file is sometimes tedious, so I have gathered some information on the Internet, mainly on StackOverflow, I made some experiments with my sample project over and over and finally I obtained a perfect Android Studio .gitignore file.

Usually, I create my project inside a parent folder which I initialize as my Git repository.

So, for example, this is a sample structure (folders are bolded):

AndroidShowcaseRepository

  • .git
  • AndroidShowCase
    • .gradle
    • .idea
    • app
    • build
    • gradle
    • .gitignore
    • AndroidShowcase.iml
    • build.gradle
    • gradlew
    • gradlew.bat
    • local.properties
    • settings.gradle
  • .gitignore
  • LICENSE
  • README.md

I use the following as content for the .gitignore file in the AndroidShowcaseRepository folder

# Built application files
*.apk
*.ap_

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/

# Gradle files
.gradle/
build/
/*/build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

and the following as content for the .gitignore file in the AndroidShowcase folder

.gradle
/local.properties
.DS_Store
/build
/captures
.idea
gradlew
gradlew.bat
gradle

Using these two .gitignore files (and the default ones created by Android Studio inside the app folder and into other folders you could have) you will likely save only the necessary source code to your repository and not files which could be generated by Android Studio itself.

Check w3ma showcase repository for the complete source code and for more explanation!

Android

Making TextView with clickable link in Android

I will explain how to create a TextView with inside a clickable link in Android.

Do you want to open a static link without doing other operations? So just follow the first part of this tutorial! Otherwise, if you want to have more control on the link customization and operations after the user taps on it, follow the second part of the tutorial.

STATIC CLICKABLE LINK INSIDE STRINGS.XML RESOURCE

Let’s create an example TextView in your layout like this one:

<TextView
    android:id="@+id/messageWithLinkTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:gravity="center"
    android:text="@string/messageWithLink" />

Create this string in strings.xml:

<string name="messageWithLink">This is a message with a link inside!\n<a href="https://www.google.com/">Tap here to open it!</a></string>

Write this code in your activity/fragment:

@Bind(R.id.messageWithLinkTextView)
TextView messageWithLinkTextView;

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ButterKnife.bind(this, view);
    messageWithLinkTextView.setMovementMethod(LinkMovementMethod.getInstance());
}

Please note: I’m using ButterKnife library to bind fields and views (I suggest you to have a look at this fantastic library!)

You are done! You should get a TextView like the one in the following screenshot and when you tap on the hyperlink the browser should open! Cool!

textview_with_clickable_link

P.S. unfortunately I didn’t find a way of achieving this only using XML properties. If anyone knows how to do it, please let me know in the comments!

CUSTOM CLICKABLE LINK AND CUSTOM ACTIONS

Create another TextView in your layout:

<TextView
   android:id="@+id/messageWithSpannableLinkTextView"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerInParent="true"
   android:gravity="center"
   tools:text="@string/messageWithSpannableLink" />

Create another string resource in strings.xml:

<string name="messageWithSpannableLink">This is a message with a spannable link inside!\nTap here to open it!</string>

Please note: this time there is no <a href/> tag inside the string!

Now write this code in your activity/fragment:

@Bind(R.id.messageWithSpannableLinkTextView)
TextView messageWithSpannableLinkTextView;

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ButterKnife.bind(this, view);
    SpannableString spannableString = new SpannableString(getString(R.string.messageWithSpannableLink));
    ClickableSpan clickableSpan = new ClickableSpan() {
        @Override
        public void onClick(View textView) {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com")));
        }
    };
    spannableString.setSpan(clickableSpan, spannableString.length() - 20,
            spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    messageWithSpannableLinkTextView.setText(spannableString, TextView.BufferType.SPANNABLE);
    messageWithSpannableLinkTextView.setMovementMethod(LinkMovementMethod.getInstance());
}

You are done! You should get a TextView like the one in the following screenshot and when you tap on the hyperlink the ClickableSpan->onClick() method should be called, so you can do your operations and let the browser open the link! Cool!

textview_with_spannable_clickable_link

Check out the complete source code on my GitHub here!