diff --git a/README.md b/README.md
index 47fe7a99aa8a58045f15c40c585ead224231556a..8bc7658a2dcf5a943a4a15041fb40ae92cb3e620 100644
--- a/README.md
+++ b/README.md
@@ -19,12 +19,13 @@ This app is a native client for openHAB which allows easy access to your sitemap
 <a href="https://github.com/openhab/openhab-android/releases"><img src="assets/direct-apk-download.png" alt="Get it on GitHub" height="80"></a>
 
 ## Features
-* Control your openHAB server and [openHAB Cloud instance](https://github.com/openhab/openhab-cloud)
-* Receive notifications from openHAB Cloud
+* Control your openHAB server and/or [openHAB Cloud instance](https://github.com/openhab/openhab-cloud), e.g., an account with [myopenHAB](http://www.myopenhab.org/)
+* Receive notifications through an openHAB Cloud connection, [read more](https://www.openhab.org/docs/configuration/actions.html#cloud-notification-actions)
 * Change items via NFC tags
 * Send voice commands to openHAB
-* Send alarm clock time to openHAB
-* Supports wall mounted tablets
+* [Send alarm clock time to openHAB](https://www.openhab.org/docs/apps/android.html#alarm-clock)
+* [Supports wall mounted tablets](https://www.openhab.org/docs/apps/android.html#permanent-deployment)
+* [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm) action plugin included
 
 <img src="docs/images/main-menu.png" alt="Demo Overview" width=200px> <img src="docs/images/widget-overview.png" alt="Widget Overview" width=200px> <img src="docs/images/maps.png" alt="Google Maps Widget" width=200px>
 
diff --git a/assets/store_descriptions/en-US/strings.xml b/assets/store_descriptions/en-US/strings.xml
index 06429f9df50ed5fd2f5cb3f13d7389d3679c3e1d..4b2ce3052a8f238d6b7be74c36dc5c9de3fb7a34 100644
--- a/assets/store_descriptions/en-US/strings.xml
+++ b/assets/store_descriptions/en-US/strings.xml
@@ -13,6 +13,7 @@
     <security>• Security: ZoneMinder, DSC, ...</security>
     <open_protocols>• Open Protocols: HTTP, TCP/UDP, MQTT, Serial, ...</open_protocols>
     <special_useCases>• Special UseCases: Minecraft, Tesla Car, Weather Services, ...</special_useCases>
+    <automation_apps>• Automation apps: Includes plugins for Tasker and Locale</automation_apps>
 
     <oss_community>Open Source Community</oss_community>
     <forum>The openHAB open source initiative strongly supports its vibrant community. The forum with over 13,000 registered users is a place to find guidance, help and inspiration. Join the openHAB community forum over at https://community.openhab.org</forum>
diff --git a/assets/store_descriptions/generate_and_validate.py b/assets/store_descriptions/generate_and_validate.py
index 03939cd4023ff57d39289ff2f1d0391db4237213..38444be2074775436f53249d95aab1dc4735a92c 100755
--- a/assets/store_descriptions/generate_and_validate.py
+++ b/assets/store_descriptions/generate_and_validate.py
@@ -21,7 +21,7 @@ def getString(key):
         string = root.findall(key)[0].text
         if emptyStringPattern.match(string):
             string = getEnglishString(key)
-    except TypeError:
+    except (TypeError, IndexError):
         string = getEnglishString(key)
     return(string)
 
@@ -57,6 +57,7 @@ for file in appStoreStringsFiles:
     fullDescription += getString('home_entertainment') + "\n"
     fullDescription += getString('security') + "\n"
     fullDescription += getString('open_protocols') + "\n"
+    fullDescription += getString('automation_apps') + "\n"
     fullDescription += getString('special_useCases') + "\n\n"
     fullDescription += "<b>" + getString('oss_community') + "</b>\n\n"
     fullDescription += getString('forum') + "\n"
diff --git a/docs/USAGE.md b/docs/USAGE.md
index 058b8cc4c1eb4ee7b0b51016f169f2f916370f3a..47c3acb4f43300f99cb8136ea54e53d3619966d9 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -28,6 +28,7 @@ The app follows the basic principles of the other openHAB UIs, like Basic UI, an
 * Send voice commands to openHAB
 * [Send alarm clock time to openHAB](#alarm-clock)
 * [Supports wall mounted tablets](#permanent-deployment)
+* [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm) action plugin included
 
 <div class="row">
   <img src="images/main-menu.png" alt="Demo Overview" width=200px> <img src="images/widget-overview.png" alt="Widget Overview" width=200px> <img src="images/maps.png" alt="Google Maps Widget" width=200px>
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index ea81ce73c152d38c8339391f1d14be05cfd1b63a..fc2072f0e49beaca53c6df5eae2d85cf24cb1c5e 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -77,6 +77,13 @@
             android:name="de.duenndns.ssl.MemorizingActivity"
             android:excludeFromRecents="true"
             android:theme="@style/Theme.AppCompat.Translucent" />
+        <activity android:name=".ui.ItemPickerActivity"
+            android:label="@string/item_picker" >
+            <intent-filter>
+                <action android:name="com.twofortyfouram.locale.intent.action.EDIT_CONDITION" />
+                <action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
+            </intent-filter>
+        </activity>
 
         <service
             android:name=".core.VoiceService"
@@ -119,6 +126,10 @@
             <intent-filter>
                 <action android:name="android.intent.action.LOCALE_CHANGED" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="com.twofortyfouram.locale.intent.action.QUERY_CONDITION" />
+                <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
+            </intent-filter>
         </receiver>
 
         <activity
diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.java b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.java
index 9113279df6abb096714bf1b07747e92177bcb0da..b892224dd154756c7c7a949c623a7e8e14865834 100644
--- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.java
+++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.java
@@ -6,6 +6,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.preference.PreferenceManager;
@@ -22,8 +23,10 @@ import androidx.work.WorkManager;
 
 import org.openhab.habdroid.R;
 import org.openhab.habdroid.model.NfcTag;
+import org.openhab.habdroid.ui.ItemPickerActivity;
 import org.openhab.habdroid.ui.widget.ItemUpdatingPreference;
 import org.openhab.habdroid.util.Constants;
+import org.openhab.habdroid.util.TaskerIntent;
 import org.openhab.habdroid.util.Util;
 
 import java.util.Arrays;
@@ -39,6 +42,7 @@ public class BackgroundTasksManager extends BroadcastReceiver {
 
     private static final String WORKER_TAG_ITEM_UPLOADS = "itemUploads";
     static final String WORKER_TAG_PREFIX_NFC = "nfc-";
+    private static final String WORKER_TAG_PREFIX_TASKER = "tasker-";
 
     static final List<String> KNOWN_KEYS = Arrays.asList(
         Constants.PREFERENCE_ALARM_CLOCK
@@ -66,19 +70,37 @@ public class BackgroundTasksManager extends BroadcastReceiver {
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
         Log.d(TAG, "onReceive() with intent " + intent.getAction());
 
-        if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(intent.getAction())) {
+        if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(action)) {
             Log.d(TAG, "Alarm clock changed");
             scheduleWorker(context, Constants.PREFERENCE_ALARM_CLOCK);
-        } else if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             Log.d(TAG, "Locale changed, recreate notification channels");
             NotificationUpdateObserver.createNotificationChannels(context);
-        } else if (ACTION_RETRY_UPLOAD.equals(intent.getAction())) {
+        } else if (ACTION_RETRY_UPLOAD.equals(action)) {
             List<RetryInfo> retryInfos = intent.getParcelableArrayListExtra(EXTRA_RETRY_INFOS);
             for (RetryInfo info : retryInfos) {
                 enqueueItemUpload(info.mTag, info.mItemName, info.mValue);
             }
+        } else if (TaskerIntent.ACTION_QUERY_CONDITION.equals(action)
+                || TaskerIntent.ACTION_FIRE_SETTING.equals(action)) {
+            if (!PreferenceManager.getDefaultSharedPreferences(context)
+                    .getBoolean(Constants.PREFERENCE_TASKER_PLUGIN_ENABLED, false)) {
+                Log.d(TAG, "Tasker plugin is disabled");
+                return;
+            }
+            Bundle bundle = intent.getBundleExtra(TaskerIntent.EXTRA_BUNDLE);
+            if (bundle == null) {
+                return;
+            }
+            String itemName = bundle.getString(ItemPickerActivity.EXTRA_ITEM_NAME);
+            String state = bundle.getString(ItemPickerActivity.EXTRA_ITEM_STATE);
+            if (TextUtils.isEmpty(itemName) || TextUtils.isEmpty(state)) {
+                return;
+            }
+            enqueueItemUpload(WORKER_TAG_PREFIX_TASKER + itemName, itemName, state);
         }
     }
 
diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.java b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.java
index e8af6577262d113f9e7d262b08e6ef649663459e..2c3634a5e0f1a7d87b01dd100684122f274802bf 100644
--- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.java
+++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.java
@@ -17,6 +17,7 @@ import android.security.KeyChain;
 import android.security.KeyChainException;
 import android.util.Log;
 import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
 import androidx.core.util.Pair;
 
 import de.duenndns.ssl.MemorizingTrustManager;
@@ -159,6 +160,7 @@ public final class ConnectionFactory extends BroadcastReceiver implements
      *
      * It MUST NOT be called from the main thread.
      */
+    @WorkerThread
     public static void waitForInitialization() {
         sInstance.triggerConnectionUpdateIfNeededAndPending();
         synchronized (sInstance.mInitializationLock) {
diff --git a/mobile/src/main/java/org/openhab/habdroid/model/Item.java b/mobile/src/main/java/org/openhab/habdroid/model/Item.java
index e2908ac87eb2e961c9a730b08b0c4bca2b2db051..17291ca4ec33f990af1d97d0395e5cbea860ac69 100644
--- a/mobile/src/main/java/org/openhab/habdroid/model/Item.java
+++ b/mobile/src/main/java/org/openhab/habdroid/model/Item.java
@@ -47,6 +47,8 @@ public abstract class Item implements Parcelable {
 
     public abstract String name();
     public abstract String label();
+    @Nullable
+    public abstract String category();
     public abstract Type type();
     @Nullable
     public abstract Type groupType();
@@ -67,6 +69,7 @@ public abstract class Item implements Parcelable {
     abstract static class Builder {
         public abstract Builder name(String name);
         public abstract Builder label(String label);
+        public abstract Builder category(String category);
         public abstract Builder type(Type type);
         public abstract Builder groupType(Type type);
         public abstract Builder state(@Nullable ParsedState state);
@@ -197,6 +200,7 @@ public abstract class Item implements Parcelable {
                 .name(name)
                 .label(jsonObject.optString("label", name))
                 .link(jsonObject.optString("link", null))
+                .category(jsonObject.optString("category", null))
                 .members(members)
                 .options(options)
                 .state(ParsedState.from(state, numberPattern))
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.java b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.java
index 3f9bb081fa2d7929ef553cb6afb0ad1c174fccfd..144a9268f53f6ecff8858555eba84c958d2bc0ab 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.java
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.java
@@ -16,6 +16,7 @@ import org.openhab.habdroid.util.Util;
 
 public abstract class AbstractBaseActivity extends AppCompatActivity {
     private static final String TAG = AbstractBaseActivity.class.getSimpleName();
+    private boolean mForceNonFullscreen = false;
 
     @Override
     @CallSuper
@@ -41,6 +42,15 @@ public abstract class AbstractBaseActivity extends AppCompatActivity {
         checkFullscreen();
     }
 
+    /**
+     * Activities, that aren't called from an app component directly, e.g. through a third-party app
+     * can use this function to avoid being shown in full screen. Must be called before
+     * {@link #onCreate(Bundle)}
+     */
+    protected void forceNonFullscreen() {
+        mForceNonFullscreen = true;
+    }
+
     protected void checkFullscreen() {
         checkFullscreen(isFullscreenEnabled());
     }
@@ -50,7 +60,7 @@ public abstract class AbstractBaseActivity extends AppCompatActivity {
         final int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                 | View.SYSTEM_UI_FLAG_FULLSCREEN;
-        if (isEnabled) {
+        if (isEnabled && !mForceNonFullscreen) {
             uiOptions |= flags;
         } else {
             uiOptions &= ~flags;
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerActivity.java b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe2635ba31a3cd8b4923d40f37a2738ac3212c0a
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerActivity.java
@@ -0,0 +1,322 @@
+package org.openhab.habdroid.ui;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+import androidx.annotation.StringRes;
+import androidx.appcompat.widget.SearchView;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.view.MenuItemCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import okhttp3.Call;
+import okhttp3.Headers;
+import okhttp3.Request;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.openhab.habdroid.R;
+import org.openhab.habdroid.core.connection.Connection;
+import org.openhab.habdroid.core.connection.ConnectionFactory;
+import org.openhab.habdroid.core.connection.exception.ConnectionException;
+import org.openhab.habdroid.model.Item;
+import org.openhab.habdroid.ui.widget.DividerItemDecoration;
+import org.openhab.habdroid.util.AsyncHttpClient;
+import org.openhab.habdroid.util.Constants;
+import org.openhab.habdroid.util.SuggestedCommandsFactory;
+import org.openhab.habdroid.util.TaskerIntent;
+import org.openhab.habdroid.util.Util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ItemPickerActivity extends AbstractBaseActivity
+        implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener,
+        ItemPickerAdapter.ItemClickListener, SearchView.OnQueryTextListener {
+    private static final String TAG = ItemPickerActivity.class.getSimpleName();
+
+    public static final String EXTRA_ITEM_NAME = "itemName";
+    public static final String EXTRA_ITEM_STATE = "itemState";
+
+    private Call mRequestHandle;
+    private String mInitialHightlightItemName;
+    private ItemPickerAdapter mItemPickerAdapter;
+    private RecyclerView mRecyclerView;
+    private LinearLayoutManager mLayoutManager;
+    private SwipeRefreshLayout mSwipeLayout;
+    private View mEmptyView;
+    private TextView mEmptyMessage;
+    private TextView mRetryButton;
+    private SuggestedCommandsFactory mSuggestedCommandsFactory;
+    private boolean mIsDisabled;
+    private SharedPreferences mSharedPreferences;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        forceNonFullscreen();
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_item_picker);
+
+        Toolbar toolbar = findViewById(R.id.openhab_toolbar);
+        setSupportActionBar(toolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+        mSwipeLayout = findViewById(R.id.swipe_container);
+        mSwipeLayout.setOnRefreshListener(this);
+        Util.applySwipeLayoutColors(mSwipeLayout, R.attr.colorPrimary, R.attr.colorAccent);
+
+        mRecyclerView = findViewById(android.R.id.list);
+        mEmptyView = findViewById(android.R.id.empty);
+        mEmptyMessage = findViewById(R.id.empty_message);
+        mRetryButton = findViewById(R.id.retry_button);
+        mRetryButton.setOnClickListener(this);
+
+        mItemPickerAdapter = new ItemPickerAdapter(this, this);
+        mLayoutManager = new LinearLayoutManager(this);
+
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(this));
+        mRecyclerView.setAdapter(mItemPickerAdapter);
+
+        Bundle editItem = getIntent().getBundleExtra(TaskerIntent.EXTRA_BUNDLE);
+        if (editItem != null && !TextUtils.isEmpty(editItem.getString(EXTRA_ITEM_NAME))) {
+            mInitialHightlightItemName = editItem.getString(EXTRA_ITEM_NAME);
+        }
+
+        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+        if (!mSharedPreferences.getBoolean(Constants.PREFERENCE_TASKER_PLUGIN_ENABLED, false)) {
+            mIsDisabled = true;
+            updateViewVisibility(false, false, true);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume()");
+        loadItems();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        Log.d(TAG, "onPause()");
+        // Cancel request for items if there was any
+        if (mRequestHandle != null) {
+            mRequestHandle.cancel();
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.item_picker, menu);
+
+        final MenuItem searchItem = menu.findItem(R.id.app_bar_search);
+        final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+        searchView.setOnQueryTextListener(this);
+
+        return true;
+    }
+
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Log.d(TAG, "onOptionsItemSelected()");
+        if (item.getItemId() == android.R.id.home) {
+            finish(false, null, null);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void loadItems() {
+        if (mIsDisabled) {
+            return;
+        }
+        Connection connection;
+        try {
+            connection = ConnectionFactory.getUsableConnection();
+        } catch (ConnectionException e) {
+            connection = null;
+        }
+        if (connection == null) {
+            updateViewVisibility(false, true, false);
+            return;
+        }
+
+        mItemPickerAdapter.clear();
+        updateViewVisibility(true, false, false);
+
+        final AsyncHttpClient client = connection.getAsyncHttpClient();
+        mRequestHandle = client.get("rest/items", new AsyncHttpClient.StringResponseHandler() {
+            @Override
+            public void onSuccess(String responseBody, Headers headers) {
+                try {
+                    ArrayList<Item> items = new ArrayList<>();
+                    JSONArray jsonArray = new JSONArray(responseBody);
+                    for (int i = 0; i < jsonArray.length(); i++) {
+                        JSONObject itemJson = jsonArray.getJSONObject(i);
+                        Item item = Item.fromJson(itemJson);
+                        if (!item.readOnly()) {
+                            items.add(item);
+                        }
+                    }
+                    Log.d(TAG, "Item request success, got " + items.size() + " items");
+                    mItemPickerAdapter.setItems(items);
+                    handleInitialHighlight();
+                    updateViewVisibility(false, false, false);
+                } catch (JSONException e) {
+                    Log.d(TAG, "Item response could not be parsed", e);
+                    updateViewVisibility(false, true, false);
+                }
+            }
+
+            @Override
+            public void onFailure(Request request, int statusCode, Throwable error) {
+                updateViewVisibility(false, true, false);
+                Log.e(TAG, "Item request failure", error);
+            }
+        });
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mRetryButton) {
+            if (mIsDisabled) {
+                mSharedPreferences
+                        .edit()
+                        .putBoolean(Constants.PREFERENCE_TASKER_PLUGIN_ENABLED, true)
+                        .apply();
+                mIsDisabled = false;
+            }
+            loadItems();
+        }
+    }
+
+    @Override
+    public void onItemClicked(Item item) {
+        if (item == null) {
+            return;
+        }
+
+        if (mSuggestedCommandsFactory == null) {
+            mSuggestedCommandsFactory = new SuggestedCommandsFactory(this, true);
+        }
+
+        SuggestedCommandsFactory.SuggestedCommands suggestedCommands =
+                mSuggestedCommandsFactory.fill(item);
+
+        List<String> labels = suggestedCommands.labels;
+        List<String> commands = suggestedCommands.commands;
+
+        if (suggestedCommands.shouldShowCustom) {
+            labels.add(getString(R.string.item_picker_custom));
+        }
+
+        final String[] labelArray = labels.toArray(new String[0]);
+        new AlertDialog.Builder(this)
+                .setTitle(R.string.item_picker_dialog_title)
+                .setItems(labelArray, (dialog, which) -> {
+                    if (which == labelArray.length - 1 && suggestedCommands.shouldShowCustom) {
+                        final EditText input = new EditText(this);
+                        input.setInputType(suggestedCommands.inputTypeFlags);
+                        AlertDialog customDialog = new AlertDialog.Builder(this)
+                                .setTitle(getString(R.string.item_picker_custom))
+                                .setView(input)
+                                .setPositiveButton(android.R.string.ok, (dialog1, which1) -> {
+                                    finish(true, item, input.getText().toString());
+                                })
+                                .setNegativeButton(android.R.string.cancel, null)
+                                .show();
+                        input.setOnFocusChangeListener((v, hasFocus) -> {
+                            int mode = hasFocus
+                                    ? WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
+                                    : WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+                            customDialog.getWindow().setSoftInputMode(mode);
+                        });
+                    } else {
+                        finish(true, item, commands.get(which));
+                    }
+                })
+                .show();
+    }
+
+    private void finish(boolean success, Item item, String state) {
+        Intent intent = new Intent();
+
+        if (success) {
+            String blurb = getString(R.string.item_picker_blurb, item.label(), item.name(), state);
+            intent.putExtra(TaskerIntent.EXTRA_STRING_BLURB, blurb);
+
+            Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_ITEM_NAME, item.name());
+            bundle.putString(EXTRA_ITEM_STATE, state);
+            intent.putExtra(TaskerIntent.EXTRA_BUNDLE, bundle);
+        }
+
+        int resultCode = success ? RESULT_OK : RESULT_CANCELED;
+        setResult(resultCode, intent);
+        finish();
+    }
+
+    @Override
+    public void onRefresh() {
+        loadItems();
+    }
+
+    private void handleInitialHighlight() {
+        if (TextUtils.isEmpty(mInitialHightlightItemName)) {
+            return;
+        }
+
+        final int position = mItemPickerAdapter.findPositionForName(mInitialHightlightItemName);
+        if (position >= 0) {
+            mLayoutManager.scrollToPositionWithOffset(position, 0);
+            mRecyclerView.postDelayed(() -> mItemPickerAdapter.highlightItem(position), 600);
+        }
+
+        mInitialHightlightItemName = null;
+    }
+
+    private void updateViewVisibility(boolean loading, boolean loadError, boolean isDisabled) {
+        boolean showEmpty =  isDisabled
+                || (!loading && (mItemPickerAdapter.getItemCount() == 0 || loadError));
+        mRecyclerView.setVisibility(showEmpty ? View.GONE : View.VISIBLE);
+        mEmptyView.setVisibility(showEmpty ? View.VISIBLE : View.GONE);
+        mSwipeLayout.setRefreshing(loading);
+        @StringRes int message;
+        if (loadError) {
+            message = R.string.item_picker_list_error;
+        } else if (isDisabled) {
+            message = R.string.settings_tasker_plugin_summary;
+        } else {
+            message = R.string.item_picker_list_empty;
+        }
+        mEmptyMessage.setText(message);
+        mRetryButton.setText(isDisabled ? R.string.turn_on : R.string.try_again_button);
+        mRetryButton.setVisibility(loadError || isDisabled ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        return false;
+    }
+
+    @Override
+    public boolean onQueryTextChange(String newText) {
+        mItemPickerAdapter.filter(newText);
+        return true;
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.java b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9ef0d7b8a5cf9b7e23c2a2ad2c5debf6f7f3225
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.java
@@ -0,0 +1,174 @@
+package org.openhab.habdroid.ui;
+
+import android.content.Context;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.openhab.habdroid.R;
+import org.openhab.habdroid.core.connection.Connection;
+import org.openhab.habdroid.core.connection.ConnectionFactory;
+import org.openhab.habdroid.core.connection.exception.ConnectionException;
+import org.openhab.habdroid.model.Item;
+import org.openhab.habdroid.ui.widget.WidgetImageView;
+import org.openhab.habdroid.util.Constants;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+
+public class ItemPickerAdapter extends RecyclerView.Adapter<ItemPickerAdapter.ItemViewHolder>
+        implements View.OnClickListener {
+    public interface ItemClickListener {
+        void onItemClicked(Item item);
+    }
+
+    private ArrayList<Item> mFilteredItems = new ArrayList<>();
+    private ArrayList<Item> mAllItems = new ArrayList<>();
+    private ItemClickListener mItemClickListener;
+    private final LayoutInflater mInflater;
+    private int mHighlightedPosition = -1;
+    private static String mIconFormat;
+
+    public ItemPickerAdapter(Context context, ItemClickListener itemClickListener) {
+        super();
+        mIconFormat = PreferenceManager.getDefaultSharedPreferences(context)
+                .getString(Constants.PREFERENCE_ICON_FORMAT, "");
+        mInflater = LayoutInflater.from(context);
+        mItemClickListener = itemClickListener;
+    }
+
+    public void setItems(List<Item> items) {
+        mFilteredItems.clear();
+        mFilteredItems.addAll(items);
+        Collections.sort(mFilteredItems, new ItemNameComparator());
+        mAllItems.clear();
+        mAllItems.addAll(mFilteredItems);
+        notifyDataSetChanged();
+    }
+
+    public void filter(String filter) {
+        mFilteredItems.clear();
+        String searchTerm = filter.toLowerCase();
+        for (Item item : mAllItems) {
+            if (item.name().toLowerCase().contains(searchTerm)
+                    || item.label().toLowerCase().contains(searchTerm)
+                    || item.type().toString().toLowerCase().contains(searchTerm)) {
+                mFilteredItems.add(item);
+            }
+        }
+        notifyDataSetChanged();
+    }
+
+    private class ItemNameComparator implements Comparator<Item> {
+        public int compare(Item left, Item right) {
+            return left.name().compareToIgnoreCase(right.name());
+        }
+    }
+
+    public void clear() {
+        mFilteredItems.clear();
+        notifyDataSetChanged();
+    }
+
+    public int findPositionForName(String name) {
+        for (int i = 0; i < mFilteredItems.size(); i++) {
+            if (TextUtils.equals(mFilteredItems.get(i).name(), name)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public void highlightItem(int position) {
+        mHighlightedPosition = position;
+        notifyItemChanged(position);
+    }
+
+    @NonNull
+    @Override
+    public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new ItemViewHolder(mInflater, parent);
+    }
+
+    @Override
+    public void onBindViewHolder(ItemViewHolder holder, int position) {
+        holder.bind(mFilteredItems.get(position));
+        holder.itemView.setOnClickListener(mItemClickListener != null ? this : null);
+
+        if (position == mHighlightedPosition) {
+            final View v = holder.itemView;
+            v.post(() -> {
+                if (v.getBackground() != null) {
+                    final int centerX = v.getWidth() / 2;
+                    final int centerY = v.getHeight() / 2;
+                    DrawableCompat.setHotspot(v.getBackground(), centerX, centerY);
+                }
+                v.setPressed(true);
+                v.setPressed(false);
+                mHighlightedPosition = -1;
+            });
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mFilteredItems.size();
+    }
+
+    public static class ItemViewHolder extends RecyclerView.ViewHolder {
+        final TextView mItemNameView;
+        final TextView mItemLabelView;
+        final WidgetImageView mIconView;
+        final TextView mItemTypeView;
+
+        public ItemViewHolder(LayoutInflater inflater, ViewGroup parent) {
+            super(inflater.inflate(R.layout.itempickerlist_item, parent, false));
+            mItemNameView = itemView.findViewById(R.id.itemName);
+            mItemLabelView = itemView.findViewById(R.id.itemLabel);
+            mItemTypeView = itemView.findViewById(R.id.itemType);
+            mIconView = itemView.findViewById(R.id.itemIcon);
+            itemView.setTag(this);
+        }
+
+        public void bind(Item item) {
+            mItemNameView.setText(item.name());
+            mItemLabelView.setText(item.label());
+            mItemTypeView.setText(item.type().toString());
+
+            Connection connection;
+            try {
+                connection = ConnectionFactory.getUsableConnection();
+            } catch (ConnectionException e) {
+                connection = null;
+            }
+            if (item.category() != null && connection != null) {
+                String iconUrl = String.format(Locale.US, "images/%s.%s",
+                        Uri.encode(item.category()),
+                        TextUtils.isEmpty(mIconFormat) ? "png" : mIconFormat);
+                mIconView.setImageUrl(connection, iconUrl, mIconView.getResources()
+                        .getDimensionPixelSize(R.dimen.notificationlist_icon_size), 2000);
+            } else {
+                mIconView.setImageResource(R.drawable.ic_openhab_appicon_24dp);
+            }
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        ItemPickerAdapter.ItemViewHolder holder = (ItemPickerAdapter.ItemViewHolder) view.getTag();
+        int position = holder.getAdapterPosition();
+        if (position != RecyclerView.NO_POSITION) {
+            mItemClickListener.onItemClicked(mFilteredItems.get(position));
+        }
+    }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.java b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.java
index a4d9be700acffb1727491734a2439e8f79946a61..5fbb4597a2b1586df574bf210dee9c30c5b068f5 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.java
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.java
@@ -12,6 +12,7 @@ package org.openhab.habdroid.ui;
 import android.app.FragmentManager;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -252,6 +253,8 @@ public class PreferencesActivity extends AbstractBaseActivity {
             final Preference alarmClockPrefCat =
                     findPreference(Constants.PREFERENCE_SEND_DEVICE_INFO_CAT);
             final Preference alarmClockPref = findPreference(Constants.PREFERENCE_ALARM_CLOCK);
+            final Preference taskerPref =
+                    findPreference(Constants.PREFERENCE_TASKER_PLUGIN_ENABLED);
             final Preference vibrationPref =
                     findPreference(Constants.PREFERENCE_NOTIFICATION_VIBRATION);
             final Preference ringtoneVibrationPref =
@@ -319,6 +322,10 @@ public class PreferencesActivity extends AbstractBaseActivity {
                 return true;
             });
 
+            if (!getPreferenceBool(taskerPref, false) && !isAutomationAppInstalled()) {
+                getParent(taskerPref).removePreference(taskerPref);
+            }
+
             ringtonePref.setOnPreferenceChangeListener((pref, newValue) -> {
                 updateRingtonePreferenceSummary(pref, newValue);
                 return true;
@@ -408,6 +415,23 @@ public class PreferencesActivity extends AbstractBaseActivity {
             }
         }
 
+        private boolean isAutomationAppInstalled() {
+            String[] packageNames = {"net.dinglisch.android.taskerm", "com.twofortyfouram.locale"};
+
+            for (String packageName : packageNames) {
+                try {
+                    if (getActivity().getPackageManager().getApplicationInfo(packageName, 0)
+                            .enabled) {
+                        return true;
+                    }
+                } catch (PackageManager.NameNotFoundException ignored) {
+                    // ignored
+                }
+            }
+
+            return false;
+        }
+
         /**
          * @author https://stackoverflow.com/a/17633389
          */
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.java b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.java
index 83fda2c51f37e6b85435c371e6133ae21a628e84..b7ed798a3053e0a7b5986aa43da57010e9451998 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.java
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.java
@@ -44,14 +44,13 @@ import org.openhab.habdroid.core.connection.exception.ConnectionException;
 import org.openhab.habdroid.model.Item;
 import org.openhab.habdroid.model.LabeledValue;
 import org.openhab.habdroid.model.LinkedPage;
-import org.openhab.habdroid.model.ParsedState;
 import org.openhab.habdroid.model.Widget;
 import org.openhab.habdroid.ui.widget.RecyclerViewSwipeRefreshLayout;
 import org.openhab.habdroid.util.CacheManager;
 import org.openhab.habdroid.util.Constants;
+import org.openhab.habdroid.util.SuggestedCommandsFactory;
 import org.openhab.habdroid.util.Util;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -76,6 +75,7 @@ public class WidgetListFragment extends Fragment
     private String mTitle;
     private RecyclerViewSwipeRefreshLayout mRefreshLayout;
     private String mHighlightedPageLink;
+    private SuggestedCommandsFactory mSuggestedCommandsFactory;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -127,78 +127,14 @@ public class WidgetListFragment extends Fragment
 
     @Override
     public boolean onItemLongClicked(final Widget widget) {
-        ArrayList<String> labels = new ArrayList<>();
-        ArrayList<String> commands = new ArrayList<>();
-
-        if (widget.item() != null) {
-            // If the widget has mappings, we will populate names and commands with
-            // values from those mappings
-            if (widget.hasMappingsOrItemOptions()) {
-                for (LabeledValue mapping : widget.getMappingsOrItemOptions()) {
-                    labels.add(mapping.label());
-                    commands.add(mapping.value());
-                }
-                // Else we only can do it for Switch widget with On/Off/Toggle commands
-            } else if (widget.type() == Widget.Type.Switch) {
-                Item item = widget.item();
-                if (item.isOfTypeOrGroupType(Item.Type.Switch)) {
-                    labels.add(getString(R.string.nfc_action_on));
-                    commands.add("ON");
-                    labels.add(getString(R.string.nfc_action_off));
-                    commands.add("OFF");
-                    labels.add(getString(R.string.nfc_action_toggle));
-                    commands.add("TOGGLE");
-                } else if (item.isOfTypeOrGroupType(Item.Type.Rollershutter)) {
-                    labels.add(getString(R.string.nfc_action_up));
-                    commands.add("UP");
-                    labels.add(getString(R.string.nfc_action_down));
-                    commands.add("DOWN");
-                    labels.add(getString(R.string.nfc_action_toggle));
-                    commands.add("TOGGLE");
-                }
-            } else if (widget.type() == Widget.Type.Colorpicker) {
-                labels.add(getString(R.string.nfc_action_on));
-                commands.add("ON");
-                labels.add(getString(R.string.nfc_action_off));
-                commands.add("OFF");
-                labels.add(getString(R.string.nfc_action_toggle));
-                commands.add("TOGGLE");
-                if (widget.state() != null) {
-                    labels.add(getString(R.string.nfc_action_current_color));
-                    commands.add(widget.state().asString());
-                }
-            } else if (widget.type() == Widget.Type.Setpoint
-                    || widget.type() == Widget.Type.Slider) {
-                if (widget.state() != null && widget.state().asNumber() != null) {
-                    ParsedState.NumberState state = widget.state().asNumber();
-
-                    String currentState = state.toString();
-                    labels.add(currentState);
-                    commands.add(currentState);
-
-                    String minValue = ParsedState.NumberState.withValue(state, widget.minValue())
-                            .toString();
-                    if (!currentState.equals(minValue)) {
-                        labels.add(minValue);
-                        commands.add(minValue);
-                    }
-
-                    String maxValue = ParsedState.NumberState.withValue(state, widget.maxValue())
-                            .toString();
-                    if (!currentState.equals(maxValue)) {
-                        labels.add(maxValue);
-                        commands.add(maxValue);
-                    }
-
-                    if (widget.switchSupport()) {
-                        labels.add(getString(R.string.nfc_action_on));
-                        commands.add("ON");
-                        labels.add(getString(R.string.nfc_action_off));
-                        commands.add("OFF");
-                    }
-                }
-            }
+        if (mSuggestedCommandsFactory == null) {
+            mSuggestedCommandsFactory = new SuggestedCommandsFactory(getContext(), false);
         }
+        SuggestedCommandsFactory.SuggestedCommands suggestedCommands =
+                mSuggestedCommandsFactory.fill(widget);
+
+        List<String> labels = suggestedCommands.labels;
+        List<String> commands = suggestedCommands.commands;
 
         if (widget.linkedPage() != null) {
             labels.add(getString(R.string.nfc_action_to_sitemap_page));
diff --git a/mobile/src/main/java/org/openhab/habdroid/util/Constants.java b/mobile/src/main/java/org/openhab/habdroid/util/Constants.java
index fec435fbc8232988c67b1d69bd8f7e2efb8df665..aee46b12b931de152c8559be310e9b041c760aff 100644
--- a/mobile/src/main/java/org/openhab/habdroid/util/Constants.java
+++ b/mobile/src/main/java/org/openhab/habdroid/util/Constants.java
@@ -42,6 +42,7 @@ public class Constants {
     public static final String PREFERENCE_ALARM_CLOCK         = "alarmClock";
     public static final String PREFERENCE_SEND_DEVICE_INFO_PREFIX = "sendDeviceInfoPrefix";
     public static final String PREFERENCE_SEND_DEVICE_INFO_CAT = "sendDeviceInfoCat";
+    public static final String PREFERENCE_TASKER_PLUGIN_ENABLED = "taskerPlugin";
 
     public static final String PREV_SERVER_FLAGS              = "prevServerFlags";
 
diff --git a/mobile/src/main/java/org/openhab/habdroid/util/SuggestedCommandsFactory.java b/mobile/src/main/java/org/openhab/habdroid/util/SuggestedCommandsFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e888178f25c32bd2b190b1f8906101e78455674
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/util/SuggestedCommandsFactory.java
@@ -0,0 +1,174 @@
+package org.openhab.habdroid.util;
+
+import android.content.Context;
+import android.text.InputType;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import org.openhab.habdroid.R;
+import org.openhab.habdroid.model.Item;
+import org.openhab.habdroid.model.LabeledValue;
+import org.openhab.habdroid.model.ParsedState;
+import org.openhab.habdroid.model.Widget;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SuggestedCommandsFactory {
+    private static final int INPUT_TYPE_DECIMAL_NUMBER = InputType.TYPE_CLASS_NUMBER
+            | InputType.TYPE_NUMBER_FLAG_DECIMAL;
+    private static final int INPUT_TYPE_SINGED_DECIMAL_NUMBER = INPUT_TYPE_DECIMAL_NUMBER
+            | InputType.TYPE_NUMBER_FLAG_SIGNED;
+    private Context mContext;
+    private boolean mShowUndef;
+
+    public SuggestedCommandsFactory(Context context, boolean showUndef) {
+        mContext = context;
+        mShowUndef = showUndef;
+    }
+    
+    public SuggestedCommands fill(@Nullable Widget widget) {
+        SuggestedCommands suggestedCommands = new SuggestedCommands();
+        if (widget == null || widget.item() == null) {
+            return suggestedCommands;
+        }
+
+        if (widget.hasMappingsOrItemOptions()) {
+            for (LabeledValue mapping : widget.getMappingsOrItemOptions()) {
+                add(suggestedCommands, mapping.value(), mapping.label());
+            }
+        }
+
+        if (widget.type() == Widget.Type.Setpoint || widget.type() == Widget.Type.Slider) {
+            if (widget.state() != null && widget.state().asNumber() != null) {
+                ParsedState.NumberState state = widget.state().asNumber();
+                add(suggestedCommands, state.toString());
+                add(suggestedCommands, ParsedState.NumberState.withValue(state,
+                        widget.minValue()).toString());
+                add(suggestedCommands, ParsedState.NumberState.withValue(state,
+                        widget.maxValue()).toString());
+                if (widget.switchSupport()) {
+                    addOnOffCommands(suggestedCommands);
+                }
+            }
+        }
+
+        return fill(widget.item(), suggestedCommands);
+    }
+
+    public SuggestedCommands fill(@Nullable Item item) {
+        return fill(item, null);
+    }
+
+    private SuggestedCommands fill(@Nullable Item item, SuggestedCommands suggestedCommands) {
+        if (suggestedCommands == null) {
+            suggestedCommands = new SuggestedCommands();
+        }
+        if (item == null) {
+            return suggestedCommands;
+        }
+
+        if (item.isOfTypeOrGroupType(Item.Type.Color)) {
+            addOnOffCommands(suggestedCommands);
+            addIncreaseDecreaseCommands(suggestedCommands);
+            if (item.state() != null) {
+                add(suggestedCommands, item.state().asString(), R.string.nfc_action_current_color);
+            }
+            addCommonPercentCommands(suggestedCommands);
+        } else if (item.isOfTypeOrGroupType(Item.Type.Contact)) {
+            // Contact items cannot receive commands
+            suggestedCommands.shouldShowCustom = false;
+        } else if (item.isOfTypeOrGroupType(Item.Type.Dimmer)) {
+            addOnOffCommands(suggestedCommands);
+            addIncreaseDecreaseCommands(suggestedCommands);
+            addCommonPercentCommands(suggestedCommands);
+            suggestedCommands.inputTypeFlags = INPUT_TYPE_SINGED_DECIMAL_NUMBER;
+        } else if (item.isOfTypeOrGroupType(Item.Type.Number)) {
+            // Don't suggest numbers that might be totally out of context if there's already
+            // at least one command
+            if (suggestedCommands.commands.isEmpty()) {
+                addCommonNumberCommands(suggestedCommands);
+            }
+            suggestedCommands.inputTypeFlags = INPUT_TYPE_SINGED_DECIMAL_NUMBER;
+        } else if (item.isOfTypeOrGroupType(Item.Type.NumberWithDimension)) {
+            if (item.state() != null && item.state().asNumber() != null) {
+                add(suggestedCommands, item.state().asNumber().toString());
+            }
+        } else if (item.isOfTypeOrGroupType(Item.Type.Player)) {
+            add(suggestedCommands, "PLAY", R.string.nfc_action_play);
+            add(suggestedCommands, "PAUSE", R.string.nfc_action_pause);
+            add(suggestedCommands, "NEXT", R.string.nfc_action_next);
+            add(suggestedCommands, "PREVIOUS", R.string.nfc_action_previous);
+            add(suggestedCommands, "REWIND", R.string.nfc_action_rewind);
+            add(suggestedCommands, "FASTFORWARD", R.string.nfc_action_fastforward);
+            suggestedCommands.shouldShowCustom = false;
+        } else if (item.isOfTypeOrGroupType(Item.Type.Rollershutter)) {
+            add(suggestedCommands, "UP", R.string.nfc_action_up);
+            add(suggestedCommands, "DOWN", R.string.nfc_action_down);
+            add(suggestedCommands, "TOGGLE", R.string.nfc_action_toggle);
+            add(suggestedCommands, "MOVE", R.string.nfc_action_move);
+            add(suggestedCommands, "STOP", R.string.nfc_action_stop);
+            addCommonPercentCommands(suggestedCommands);
+            suggestedCommands.inputTypeFlags = INPUT_TYPE_DECIMAL_NUMBER;
+        } else if (item.isOfTypeOrGroupType(Item.Type.StringItem)) {
+            if (mShowUndef) {
+                add(suggestedCommands, "", R.string.nfc_action_empty_string);
+                add(suggestedCommands, "UNDEF", R.string.nfc_action_undefined);
+            }
+        } else if (item.isOfTypeOrGroupType(Item.Type.Switch)) {
+            addOnOffCommands(suggestedCommands);
+            suggestedCommands.shouldShowCustom = false;
+        } else {
+            if (mShowUndef) {
+                add(suggestedCommands, "UNDEF", R.string.nfc_action_undefined);
+            }
+        }
+
+        return suggestedCommands;
+    }
+    
+    private void add(SuggestedCommands suggestedCommands, String commandAndLabel) {
+        add(suggestedCommands, commandAndLabel, commandAndLabel);
+    }
+
+    private void add(SuggestedCommands suggestedCommands, String command, @StringRes int label) {
+        add(suggestedCommands, command, mContext.getString(label));
+    }
+
+    private void add(SuggestedCommands suggestedCommands, String command, String label) {
+        if (!suggestedCommands.commands.contains(command)) {
+            suggestedCommands.commands.add(command);
+            suggestedCommands.labels.add(label);
+        }
+    }
+
+    private void addCommonNumberCommands(SuggestedCommands suggestedCommands) {
+        for (String command : new String[]{"0", "33", "50", "66", "100"}) {
+            add(suggestedCommands, command);
+        }
+    }
+
+    private void addCommonPercentCommands(SuggestedCommands suggestedCommands) {
+        for (String command : new String[]{"0", "33", "50", "66", "100"}) {
+            add(suggestedCommands, command, String.format("%s %%", command));
+        }
+    }
+
+    private void addOnOffCommands(SuggestedCommands suggestedCommands) {
+        add(suggestedCommands, "ON", R.string.nfc_action_on);
+        add(suggestedCommands, "OFF", R.string.nfc_action_off);
+        add(suggestedCommands, "TOGGLE", R.string.nfc_action_toggle);
+    }
+
+    private void addIncreaseDecreaseCommands(SuggestedCommands suggestedCommands) {
+        add(suggestedCommands, "INCREASE", R.string.nfc_action_increase);
+        add(suggestedCommands, "DECREASE", R.string.nfc_action_decrease);
+    }
+
+    public class SuggestedCommands {
+        public List<String> commands = new ArrayList<>();
+        public List<String> labels = new ArrayList<>();
+        public boolean shouldShowCustom = true;
+        public int inputTypeFlags = InputType.TYPE_CLASS_TEXT;
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/util/TaskerIntent.java b/mobile/src/main/java/org/openhab/habdroid/util/TaskerIntent.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed60889f4735cb214dc02dfd4a693013442b10e5
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/util/TaskerIntent.java
@@ -0,0 +1,292 @@
+/*
+ * android-plugin-api-for-locale https://github.com/twofortyfouram/android-plugin-api-for-locale
+ * Copyright 2014 two forty four a.m. LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openhab.habdroid.util;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Contains Intent constants necessary for interacting with the plug-in API for Locale.
+ */
+public class TaskerIntent {
+    /**
+     * <p>{@code Intent} action sent by the host to create or
+     * edit a plug-in condition. When the host sends this {@code Intent}, it
+     * will be explicit (i.e. sent directly to the package and class of the plug-in's
+     * {@code Activity}).</p>
+     * <p>The {@code Intent} MAY contain
+     * {@link #EXTRA_BUNDLE} and {@link #EXTRA_STRING_BLURB} that was previously set by the {@code
+     * Activity} result of ACTION_EDIT_CONDITION.</p>
+     * <p>There SHOULD be only one {@code Activity} per APK that implements this
+     * {@code Intent}. If a single APK wishes to export multiple plug-ins, it
+     * MAY implement multiple Activity instances that implement this
+     * {@code Intent}, however there must only be a single
+     * {@link #ACTION_QUERY_CONDITION} receiver. In such a scenario, it is the
+     * responsibility of the Activity to store enough data in
+     * {@link #EXTRA_BUNDLE} to allow this receiver to disambiguate which
+     * "plug-in" is being queried. To avoid user confusion, it is recommended
+     * that only a single plug-in be implemented per APK.</p>
+     *
+     * @see TaskerIntent#EXTRA_BUNDLE
+     * @see TaskerIntent#EXTRA_STRING_BREADCRUMB
+     */
+    @NonNull
+    public static final String ACTION_EDIT_CONDITION
+            = "com.twofortyfouram.locale.intent.action.EDIT_CONDITION"; //$NON-NLS-1$
+
+    /**
+     * <p>Ordered {@code Intent} action broadcast by the host to query
+     * a plug-in condition. When the host broadcasts this {@code Intent}, it will
+     * be explicit (i.e. directly to the package and class of the plug-in's
+     * {@code BroadcastReceiver}).</p>
+     * <p>The {@code Intent} MUST contain a
+     * {@link #EXTRA_BUNDLE} that was previously set by the {@code Activity}
+     * result of {@link #ACTION_EDIT_CONDITION}.
+     * </p>
+     * <p>
+     * Since this is an ordered broadcast, the plug-in's receiver MUST set an
+     * appropriate result code from {@link #RESULT_CONDITION_SATISFIED},
+     * {@link #RESULT_CONDITION_UNSATISFIED}, or
+     * {@link #RESULT_CONDITION_UNKNOWN}.</p>
+     * <p>
+     * There MUST be only one {@code BroadcastReceiver} per APK that implements
+     * an Intent-filter for this action.
+     * </p>
+     *
+     * @see TaskerIntent#EXTRA_BUNDLE
+     * @see TaskerIntent#RESULT_CONDITION_SATISFIED
+     * @see TaskerIntent#RESULT_CONDITION_UNSATISFIED
+     * @see TaskerIntent#RESULT_CONDITION_UNKNOWN
+     */
+    @NonNull
+    public static final String ACTION_QUERY_CONDITION
+            = "com.twofortyfouram.locale.intent.action.QUERY_CONDITION"; //$NON-NLS-1$
+
+    /**
+     * <p>
+     * {@code Intent} action sent by the host to create or
+     * edit a plug-in setting. When the host sends this {@code Intent}, it
+     * will be sent explicit (i.e. sent directly to the package and class of the plug-in's
+     * {@code Activity}).</p>
+     * <p>The {@code Intent} MAY contain a {@link #EXTRA_BUNDLE} and {@link
+     * #EXTRA_STRING_BLURB}
+     * that was previously set by the {@code Activity} result of
+     * ACTION_EDIT_SETTING.</p>
+     * <p>
+     * There SHOULD be only one {@code Activity} per APK that implements this
+     * {@code Intent}. If a single APK wishes to export multiple plug-ins, it
+     * MAY implement multiple Activity instances that implement this
+     * {@code Intent}, however there must only be a single
+     * {@link #ACTION_FIRE_SETTING} receiver. In such a scenario, it is the
+     * responsibility of the Activity to store enough data in
+     * {@link #EXTRA_BUNDLE} to allow this receiver to disambiguate which
+     * "plug-in" is being fired. To avoid user confusion, it is recommended that
+     * only a single plug-in be implemented per APK.
+     * </p>
+     *
+     * @see TaskerIntent#EXTRA_BUNDLE
+     * @see TaskerIntent#EXTRA_STRING_BREADCRUMB
+     */
+    @NonNull
+    public static final String ACTION_EDIT_SETTING
+            = "com.twofortyfouram.locale.intent.action.EDIT_SETTING"; //$NON-NLS-1$
+
+    /**
+     * <p>
+     * {@code Intent} action broadcast by the host to fire a
+     * plug-in setting. When the host broadcasts this {@code Intent}, it will be
+     * explicit (i.e. sent directly to the package and class of the plug-in's
+     * {@code BroadcastReceiver}).</p>
+     * <p>The {@code Intent} MUST contain a
+     * {@link #EXTRA_BUNDLE} that was previously set by the {@code Activity}
+     * result of {@link #ACTION_EDIT_SETTING}.</p>
+     * <p>There MUST be only one {@code BroadcastReceiver} per APK that implements
+     * an Intent-filter for this action.</p>
+     *
+     * @see TaskerIntent#EXTRA_BUNDLE
+     */
+    @NonNull
+    public static final String ACTION_FIRE_SETTING
+            = "com.twofortyfouram.locale.intent.action.FIRE_SETTING"; //$NON-NLS-1$
+
+    /**
+     * <p>Implicit broadcast {@code Intent} action to notify the host(s) that a plug-in
+     * condition is requesting a query it via
+     * {@link #ACTION_QUERY_CONDITION}. This merely serves as a hint to the host
+     * that a condition wants to be queried. There is no guarantee as to when or
+     * if the plug-in will be queried after this action is broadcast. If
+     * the host does not respond to the plug-in condition after a
+     * ACTION_REQUEST_QUERY Intent is sent, the plug-in SHOULD shut
+     * itself down and stop requesting requeries. A lack of response from the host
+     * indicates that the host is not currently interested in this plug-in. When
+     * the host becomes interested in the plug-in again, the host will send
+     * {@link #ACTION_QUERY_CONDITION}.</p>
+     * <p>
+     * The extra {@link #EXTRA_STRING_ACTIVITY_CLASS_NAME} MUST be included, otherwise the host will
+     * ignore this {@code Intent}.
+     * </p>
+     * <p>
+     * Plug-in conditions SHOULD NOT use this unless there is some sort of
+     * asynchronous event that has occurred, such as a broadcast {@code Intent}
+     * being received by the plug-in. Plug-ins SHOULD NOT periodically request a
+     * requery as a way of implementing polling behavior.
+     * </p>
+     * <p>
+     * Hosts MAY throttle plug-ins that request queries too frequently.
+     * </p>
+     *
+     * @see TaskerIntent#EXTRA_STRING_ACTIVITY_CLASS_NAME
+     */
+    @NonNull
+    public static final String ACTION_REQUEST_QUERY
+            = "com.twofortyfouram.locale.intent.action.REQUEST_QUERY"; //$NON-NLS-1$
+
+    /**
+     * <p>
+     * Type: {@code String}.
+     * </p>
+     * <p>
+     * Maps to a {@code String} that represents the {@code Activity} bread crumb
+     * path.
+     * </p>
+     */
+    @NonNull
+    public static final String EXTRA_STRING_BREADCRUMB
+            = "com.twofortyfouram.locale.intent.extra.BREADCRUMB"; //$NON-NLS-1$
+
+    /**
+     * <p>
+     * Type: {@code String}.
+     * </p>
+     * <p>
+     * Maps to a {@code String} that represents a blurb. This is returned as an
+     * {@code Activity} result extra from the Activity started with {@link #ACTION_EDIT_CONDITION}
+     * or
+     * {@link #ACTION_EDIT_SETTING}.
+     * </p>
+     * <p>
+     * The blurb is a concise description displayed to the user of what the
+     * plug-in is configured to do.
+     * </p>
+     */
+    @NonNull
+    public static final String EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB";
+    //$NON-NLS-1$
+
+    /**
+     * <p>
+     * Type: {@code Bundle}.
+     * </p>
+     * <p>
+     * Maps to a {@code Bundle} that contains all of a plug-in's extras to later be used when
+     * querying or firing the plug-in.
+     * </p>
+     * <p>
+     * Plug-ins MUST NOT store {@link android.os.Parcelable} objects in this {@code Bundle}
+     * , because {@code Parcelable} is not a long-term storage format.</p>
+     * <p>
+     * Plug-ins MUST NOT store any serializable object that is not exposed by
+     * the Android SDK.  Plug-ins SHOULD NOT store any serializable object that is not available
+     * across all Android API levels that the plug-in supports.  Doing could cause previously saved
+     * plug-ins to fail during backup and restore.
+     * </p>
+     * <p>
+     * When the Bundle is serialized by the host, the maximum size of the serialized Bundle MUST be
+     * less than 25 kilobytes (base-10).  While the serialization mechanism used by the host is
+     * opaque to the plug-in, in general plug-ins should just make their Bundle reasonably compact.
+     * In Android, Intent extras are limited to about 500 kilobytes, although the exact
+     * size is not specified by the Android public API.  If an Intent exceeds that size, the extras
+     * will be silently dropped by Android. In Android 4.4 KitKat, the maximum amount of data that
+     * can be written to a ContentProvider during a ContentProviderOperation was reduced to
+     * less than 300 kilobytes. The maximum bundle size here was chosen to allow several large
+     * plug-ins to be added to a single batch of operations before overflow occurs.
+     * </p>
+     * <p>If a plug-in needs to store large amounts of data, the plug-in should consider
+     * implementing its own internal storage mechanism.  The Bundle can then contain a small token
+     * that the plug-in uses as a lookup key in its own internal storage mechanism.</p>
+     */
+    @NonNull
+    public static final String EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE";
+    //$NON-NLS-1$
+
+    /**
+     * <p>
+     * Type: {@code String}.
+     * </p>
+     * <p>
+     * Maps to a {@code String} that is the fully qualified class name of a plug-in's
+     * {@code Activity}.
+     * </p>
+     *
+     * @see TaskerIntent#ACTION_REQUEST_QUERY
+     */
+    @NonNull
+    public static final String EXTRA_STRING_ACTIVITY_CLASS_NAME =
+            "com.twofortyfouram.locale.intent.extra.ACTIVITY";
+    //$NON-NLS-1$
+
+    /**
+     * Ordered broadcast result code indicating that a plug-in condition's state
+     * is satisfied (true).
+     *
+     * @see TaskerIntent#ACTION_QUERY_CONDITION
+     */
+    public static final int RESULT_CONDITION_SATISFIED = 16;
+
+    /**
+     * Ordered broadcast result code indicating that a plug-in condition's state
+     * is not satisfied (false).
+     *
+     * @see TaskerIntent#ACTION_QUERY_CONDITION
+     */
+    public static final int RESULT_CONDITION_UNSATISFIED = 17;
+
+    /**
+     * <p>
+     * Ordered broadcast result code indicating that a plug-in condition's state
+     * is unknown (neither true nor false).
+     * </p>
+     * <p>
+     * If a condition returns UNKNOWN, then the host will use the last known
+     * return value on a best-effort basis. Best-effort means that the host may
+     * not persist known values forever (e.g. last known values could
+     * hypothetically be cleared after a device reboot or a restart of the
+     * host's process. If there is no last known return value, then unknown is
+     * treated as not satisfied (false).
+     * </p>
+     * <p>
+     * The purpose of an UNKNOWN result is to allow a plug-in condition more
+     * than 10 seconds to process a query. A {@code BroadcastReceiver} MUST
+     * return within 10 seconds, otherwise it will be killed by Android. A
+     * plug-in that needs more than 10 seconds might initially return
+     * RESULT_CONDITION_UNKNOWN, subsequently request a requery, and
+     * then return either {@link #RESULT_CONDITION_SATISFIED} or
+     * {@link #RESULT_CONDITION_UNSATISFIED}.
+     * </p>
+     *
+     * @see TaskerIntent#ACTION_QUERY_CONDITION
+     */
+    public static final int RESULT_CONDITION_UNKNOWN = 18;
+
+    /**
+     * Private constructor prevents instantiation.
+     *
+     * @throws UnsupportedOperationException because this class cannot be
+     *                                       instantiated.
+     */
+    private TaskerIntent() {
+        throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
+    }
+}
\ No newline at end of file
diff --git a/mobile/src/main/res/drawable/ic_search_white_24dp.xml b/mobile/src/main/res/drawable/ic_search_white_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c927d3d226214bab4c9d3c29eb406b16abd7fddb
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_search_white_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
+</vector>
diff --git a/mobile/src/main/res/layout/activity_item_picker.xml b/mobile/src/main/res/layout/activity_item_picker.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6bcc42524441117bf1a2449d658c7f7f7059c3b4
--- /dev/null
+++ b/mobile/src/main/res/layout/activity_item_picker.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/openhab_toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="?attr/colorPrimary"
+        android:elevation="8dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
+        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+        android:id="@+id/swipe_container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/openhab_toolbar">
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@android:id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                tools:visibility="gone" />
+
+            <LinearLayout
+                android:id="@android:id/empty"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:orientation="vertical"
+                tools:visibility="visible">
+
+                <androidx.appcompat.widget.AppCompatImageView
+                    android:id="@+id/watermark"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:padding="16dp"
+                    app:tint="@color/empty_list_text_color"
+                    app:tintMode="src_in"
+                    app:srcCompat="@drawable/ic_connection_error" />
+
+                <TextView
+                    android:id="@+id/empty_message"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:padding="16dp"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:textColor="@color/empty_list_text_color"
+                    android:textAlignment="center"
+                    tools:text="Some error occured" />
+
+                <TextView
+                    android:id="@+id/retry_button"
+                    style="?attr/buttonBarButtonStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    tools:text="@string/try_again_button" />
+
+            </LinearLayout>
+        </FrameLayout>
+    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/mobile/src/main/res/layout/itempickerlist_item.xml b/mobile/src/main/res/layout/itempickerlist_item.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f773a988061aaacb7ea1d97154170c65fff9387e
--- /dev/null
+++ b/mobile/src/main/res/layout/itempickerlist_item.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/listPreferredItemHeight"
+    android:background="?attr/selectableItemBackground"
+    android:focusable="false"
+    android:orientation="horizontal">
+
+    <org.openhab.habdroid.ui.widget.WidgetImageView
+        android:id="@+id/itemIcon"
+        android:layout_width="@dimen/notificationlist_icon_size"
+        android:layout_height="@dimen/notificationlist_icon_size"
+        android:layout_marginLeft="16dp"
+        android:layout_marginRight="16dp"
+        android:layout_gravity="center_vertical"
+        android:scaleType="centerInside"
+        app:progressIndicator="@drawable/ic_openhab_appicon_24dp"
+        app:fallback="@drawable/ic_openhab_appicon_24dp"
+        tools:src="@drawable/ic_openhab_appicon_24dp" />
+
+    <RelativeLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:layout_marginRight="16dp"
+        android:layout_marginEnd="16dp">
+
+        <TextView
+            android:id="@+id/itemName"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+            tools:text="AndroidAlarmClock" />
+
+        <TextView
+            android:id="@+id/itemLabel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/itemName"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="@style/TextAppearance.AppCompat.Caption"
+            tools:text="Alarm clock" />
+
+        <TextView
+            android:id="@+id/itemType"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/itemName"
+            android:layout_toEndOf="@id/itemLabel"
+            android:layout_toRightOf="@id/itemLabel"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentRight="true"
+            android:gravity="end|right"
+            android:textAppearance="@style/TextAppearance.AppCompat.Caption"
+            android:textStyle="bold"
+            tools:text="Number" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/mobile/src/main/res/menu/item_picker.xml b/mobile/src/main/res/menu/item_picker.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f1791ec6b0e0ebdb7d09d690354576c4c349c80b
--- /dev/null
+++ b/mobile/src/main/res/menu/item_picker.xml
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/app_bar_search"
+        android:icon="@drawable/ic_search_white_24dp"
+        android:title="@string/search"
+        app:showAsAction="always"
+        app:actionViewClass="androidx.appcompat.widget.SearchView" />
+</menu>
\ No newline at end of file
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index c68fa3cf977783f3ea5a1c26434ece5f9d652deb..8fbab974f65cc2db35cfe6ce5ec03e1825f698c8 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -207,6 +207,18 @@
     <string name="nfc_action_off">Off</string>
     <string name="nfc_action_down">Down</string>
     <string name="nfc_action_toggle">Toggle</string>
+    <string name="nfc_action_undefined">Undefined</string>
+    <string name="nfc_action_empty_string">Empty string</string>
+    <string name="nfc_action_increase">Increase</string>
+    <string name="nfc_action_decrease">Decrease</string>
+    <string name="nfc_action_play">Play</string>
+    <string name="nfc_action_pause">Pause</string>
+    <string name="nfc_action_next">Next</string>
+    <string name="nfc_action_previous">Previous</string>
+    <string name="nfc_action_rewind">Rewind</string>
+    <string name="nfc_action_fastforward">Fast forward</string>
+    <string name="nfc_action_move">Move</string>
+    <string name="nfc_action_stop">Stop</string>
     <string name="nfc_activate">Activate</string>
     <string name="nfc_action_current_color">Current color</string>
     <string name="nfc_action_to_sitemap_page">Navigate to Sitemap page</string>
@@ -288,4 +300,16 @@
     <string name="app_intro_skip_button">SKIP</string>
     <!-- Intro "DONE" button -->
     <string name="app_intro_done_button">DONE</string>
+
+    <!-- Item picker -->
+    <string name="item_picker_list_empty">No Items found</string>
+    <string name="item_picker_list_error">An error occurred while loading Items</string>
+    <string name="item_picker_custom">Custom</string>
+    <string name="item_picker_dialog_title">Select state</string>
+    <string name="item_picker">openHAB Items</string>
+    <string name="item_picker_blurb">Set \"%s\" (%s) to \"%s\"</string>
+    <string name="search">Search</string>
+    <string name="settings_tasker_plugin">Tasker integration</string>
+    <string name="settings_tasker_plugin_summary">Enable Tasker action plugin which allows updating Items via Tasker or other compatible apps</string>
+    <string name="turn_on">Turn on</string>
 </resources>
diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml
index b7117b10550bbf89964ed75791f5182a113c97f9..092e8ca7b067cf833315fa7fa3dbac57b94cf860 100644
--- a/mobile/src/main/res/xml/preferences.xml
+++ b/mobile/src/main/res/xml/preferences.xml
@@ -101,13 +101,19 @@
             android:icon="@drawable/ic_alarm_grey_24dp" />
     </PreferenceCategory>
     <PreferenceCategory android:title="@string/settings_misc_title">
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="taskerPlugin"
+            android:title="@string/settings_tasker_plugin"
+            android:summary="@string/settings_tasker_plugin_summary"
+            android:icon="@drawable/ic_none" />
         <RingtonePreference
             android:key="default_openhab_alertringtone"
             android:persistent="true"
             android:ringtoneType="ringtone|notification"
             android:showSilent="true"
             android:title="@string/settings_ringtone"
-            android:icon="@drawable/ic_bell_ring_outline_grey_24dp" />c
+            android:icon="@drawable/ic_bell_ring_outline_grey_24dp" />
         <ListPreference
             android:key="default_openhab_notification_vibration"
             android:title="@string/settings_notification_vibration"