diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java
index 122885612..349f71fa0 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java
@@ -199,16 +199,26 @@ public class AppProvider extends FDroidProvider {
         public AppQuerySelection add(AppQuerySelection query) {
             QuerySelection both = super.add(query);
             AppQuerySelection bothWithJoin = new AppQuerySelection(both.getSelection(), both.getArgs());
-            if (this.naturalJoinToInstalled() || query.naturalJoinToInstalled()) {
-                bothWithJoin.requireNaturalInstalledTable();
-            }
-
-            if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
-                bothWithJoin.requireLeftJoinPrefs();
-            }
+            ensureJoinsCopied(query, bothWithJoin);
             return bothWithJoin;
         }
 
+        public AppQuerySelection not(AppQuerySelection query) {
+            QuerySelection both = super.not(query);
+            AppQuerySelection bothWithJoin = new AppQuerySelection(both.getSelection(), both.getArgs());
+            ensureJoinsCopied(query, bothWithJoin);
+            return bothWithJoin;
+        }
+
+        private void ensureJoinsCopied(AppQuerySelection toAdd, AppQuerySelection newlyCreated) {
+            if (this.naturalJoinToInstalled() || toAdd.naturalJoinToInstalled()) {
+                newlyCreated.requireNaturalInstalledTable();
+            }
+
+            if (this.leftJoinToPrefs() || toAdd.leftJoinToPrefs()) {
+                newlyCreated.requireLeftJoinPrefs();
+            }
+        }
     }
 
     protected class Query extends QueryBuilder {
@@ -564,7 +574,8 @@ public class AppProvider extends FDroidProvider {
         final String ignoreAll = "COALESCE(prefs." + AppPrefsTable.Cols.IGNORE_ALL_UPDATES + ", 0) != 1";
 
         final String ignore = " (" + ignoreCurrent + " AND " + ignoreAll + ") ";
-        final String where = ignore + " AND " + app + "." + Cols.SUGGESTED_VERSION_CODE + " > installed." + InstalledAppTable.Cols.VERSION_CODE;
+        final String nullChecks = app + "." + Cols.SUGGESTED_VERSION_CODE + " IS NOT NULL AND installed." + InstalledAppTable.Cols.VERSION_CODE + " IS NOT NULL ";
+        final String where = nullChecks + " AND " + ignore + " AND " + app + "." + Cols.SUGGESTED_VERSION_CODE + " > installed." + InstalledAppTable.Cols.VERSION_CODE;
 
         return new AppQuerySelection(where).requireNaturalInstalledTable().requireLeftJoinPrefs();
     }
@@ -576,7 +587,7 @@ public class AppProvider extends FDroidProvider {
     }
 
     private AppQuerySelection queryInstalled() {
-        return new AppQuerySelection().requireNaturalInstalledTable();
+        return new AppQuerySelection().requireNaturalInstalledTable().not(queryCanUpdate());
     }
 
     private AppQuerySelection querySearch(String query) {
diff --git a/app/src/main/java/org/fdroid/fdroid/data/QuerySelection.java b/app/src/main/java/org/fdroid/fdroid/data/QuerySelection.java
index 5c32395a7..d5461799f 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/QuerySelection.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/QuerySelection.java
@@ -78,4 +78,8 @@ public class QuerySelection {
         return new QuerySelection(s, a);
     }
 
+    public QuerySelection not(QuerySelection querySelection) {
+        String where = " NOT (" + querySelection.getSelection() + ") ";
+        return add(where, querySelection.getArgs());
+    }
 }
diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java
new file mode 100644
index 000000000..3da0e8428
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java
@@ -0,0 +1,148 @@
+package org.fdroid.fdroid.views.apps;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.support.v4.util.Pair;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.nostra13.universalimageloader.core.DisplayImageOptions;
+import com.nostra13.universalimageloader.core.ImageLoader;
+
+import org.fdroid.fdroid.AppDetails;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.Utils;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.installer.InstallManagerService;
+
+public class AppListItemController extends RecyclerView.ViewHolder {
+
+    private final Activity activity;
+
+    private final Button installButton;
+    private final ImageView icon;
+    private final TextView name;
+    private final TextView status;
+    private final DisplayImageOptions displayImageOptions;
+
+    private App currentApp;
+
+    public AppListItemController(Activity activity, View itemView) {
+        super(itemView);
+        this.activity = activity;
+
+        installButton = (Button) itemView.findViewById(R.id.install);
+        installButton.setOnClickListener(onInstallClicked);
+
+        icon = (ImageView) itemView.findViewById(R.id.icon);
+        name = (TextView) itemView.findViewById(R.id.app_name);
+        status = (TextView) itemView.findViewById(R.id.status);
+
+        displayImageOptions = Utils.getImageLoadingOptions().build();
+
+        itemView.setOnClickListener(onAppClicked);
+    }
+
+    public void bindModel(@NonNull App app) {
+        currentApp = app;
+        name.setText(Utils.formatAppNameAndSummary(app.name, app.summary));
+
+        ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions);
+
+        configureStatusText(app);
+        configureInstallButton(app);
+    }
+
+    /**
+     * Sets the text/visibility of the {@link R.id#status} {@link TextView} based on whether the app:
+     *  * Is compatible with the users device
+     *  * Is installed
+     *  * Can be updated
+     *
+     * TODO: This button also needs to be repurposed to support the "Downloaded but not installed" state.
+     */
+    private void configureStatusText(@NonNull App app) {
+        if (status == null) {
+            return;
+        }
+
+        if (!app.compatible) {
+            status.setText(activity.getString(R.string.app_incompatible));
+            status.setVisibility(View.VISIBLE);
+        } else if (app.isInstalled()) {
+            if (app.canAndWantToUpdate(activity)) {
+                String upgradeFromTo = activity.getString(R.string.app_version_x_available, app.getSuggestedVersionName());
+                status.setText(upgradeFromTo);
+            } else {
+                String installed = activity.getString(R.string.app_version_x_installed, app.installedVersionName);
+                status.setText(installed);
+            }
+
+            status.setVisibility(View.VISIBLE);
+        } else {
+            status.setVisibility(View.INVISIBLE);
+        }
+
+    }
+
+    /**
+     * The install button is shown when an app:
+     *  * Is compatible with the users device.
+     *  * Has not been filtered due to anti-features/root/etc.
+     *  * Is either not installed or installed but can be updated.
+     *
+     * TODO: This button also needs to be repurposed to support the "Downloaded but not installed" state.
+     */
+    private void configureInstallButton(@NonNull App app) {
+        if (installButton == null) {
+            return;
+        }
+
+        boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled();
+        boolean shouldAllow = app.compatible && !app.isFiltered();
+
+        if (shouldAllow && installable) {
+            installButton.setVisibility(View.VISIBLE);
+        } else {
+            installButton.setVisibility(View.GONE);
+        }
+    }
+
+    private final View.OnClickListener onAppClicked = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (currentApp == null) {
+                return;
+            }
+
+            Intent intent = new Intent(activity, AppDetails.class);
+            intent.putExtra(AppDetails.EXTRA_APPID, currentApp.packageName);
+            if (Build.VERSION.SDK_INT >= 21) {
+                Pair<View, String> iconTransitionPair = Pair.create((View) icon, activity.getString(R.string.transition_app_item_icon));
+                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle();
+                activity.startActivity(intent, bundle);
+            } else {
+                activity.startActivity(intent);
+            }
+        }
+    };
+
+    private final View.OnClickListener onInstallClicked = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (currentApp == null) {
+                return;
+            }
+
+            InstallManagerService.queue(activity, currentApp, ApkProvider.Helper.findApkFromAnyRepo(activity, currentApp.packageName, currentApp.suggestedVersionCode));
+        }
+    };
+}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainViewController.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainViewController.java
index 8d894584c..668a44f9a 100644
--- a/app/src/main/java/org/fdroid/fdroid/views/main/MainViewController.java
+++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainViewController.java
@@ -8,6 +8,7 @@ import android.widget.Button;
 import android.widget.FrameLayout;
 
 import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.views.myapps.MyAppsViewBinder;
 import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
 
 /**
@@ -38,7 +39,11 @@ class MainViewController extends RecyclerView.ViewHolder {
         new WhatsNewViewBinder(activity, frame);
     }
 
+    /**
+     * @see MyAppsViewBinder
+     */
     public void bindMyApps() {
+        new MyAppsViewBinder(activity, frame);
     }
 
     public void bindCategoriesView() {
diff --git a/app/src/main/java/org/fdroid/fdroid/views/myapps/InstalledHeaderController.java b/app/src/main/java/org/fdroid/fdroid/views/myapps/InstalledHeaderController.java
new file mode 100644
index 000000000..c8adba2ce
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/views/myapps/InstalledHeaderController.java
@@ -0,0 +1,10 @@
+package org.fdroid.fdroid.views.myapps;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class InstalledHeaderController extends RecyclerView.ViewHolder {
+    public InstalledHeaderController(View itemView) {
+        super(itemView);
+    }
+}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/myapps/MyAppsAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/myapps/MyAppsAdapter.java
new file mode 100644
index 000000000..4c29069c8
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/views/myapps/MyAppsAdapter.java
@@ -0,0 +1,76 @@
+package org.fdroid.fdroid.views.myapps;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.views.apps.AppListItemController;
+
+/**
+ * Wraps a cursor which should have a list of "apps which can be updated". Also includes a header
+ * as the first element which allows for all items to be updated.
+ */
+public class MyAppsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+
+    private Cursor updatesCursor;
+    private final Activity activity;
+
+    public MyAppsAdapter(Activity activity) {
+        this.activity = activity;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LayoutInflater inflater = activity.getLayoutInflater();
+        switch (viewType) {
+            case R.id.my_apps__header:
+                return new UpdatesHeaderController(activity, inflater.inflate(R.layout.my_apps_updates_header, parent, false));
+
+            case R.id.my_apps__app:
+                return new AppListItemController(activity, inflater.inflate(R.layout.app_list_item, parent, false));
+
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return updatesCursor == null ? 0 : updatesCursor.getCount() + 1;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        switch (getItemViewType(position)) {
+            case R.id.my_apps__header:
+                ((UpdatesHeaderController) holder).bindModel(updatesCursor.getCount());
+                break;
+
+            case R.id.my_apps__app:
+                updatesCursor.moveToPosition(position - 1); // Subtract one to account for the header.
+                ((AppListItemController) holder).bindModel(new App(updatesCursor));
+                break;
+
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (position == 0) {
+            return R.id.my_apps__header;
+        } else {
+            return R.id.my_apps__app;
+        }
+    }
+
+    public void setApps(Cursor cursor) {
+        updatesCursor = cursor;
+        notifyDataSetChanged();
+    }
+}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/myapps/MyAppsViewBinder.java b/app/src/main/java/org/fdroid/fdroid/views/myapps/MyAppsViewBinder.java
new file mode 100644
index 000000000..8e3ec2c73
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/views/myapps/MyAppsViewBinder.java
@@ -0,0 +1,77 @@
+package org.fdroid.fdroid.views.myapps;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.data.AppProvider;
+import org.fdroid.fdroid.data.Schema;
+
+public class MyAppsViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
+
+    private final MyAppsAdapter adapter;
+
+    private final Activity activity;
+
+    public MyAppsViewBinder(AppCompatActivity activity, FrameLayout parent) {
+        this.activity = activity;
+
+        View myAppsView = activity.getLayoutInflater().inflate(R.layout.main_tabs, parent, true);
+
+        adapter = new MyAppsAdapter(activity);
+
+        RecyclerView list = (RecyclerView) myAppsView.findViewById(R.id.list);
+        list.setHasFixedSize(true);
+        list.setLayoutManager(new LinearLayoutManager(activity));
+        list.setAdapter(adapter);
+
+        LoaderManager loaderManager = activity.getSupportLoaderManager();
+        loaderManager.initLoader(0, null, this);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        return new CursorLoader(
+                activity,
+                AppProvider.getCanUpdateUri(),
+                new String[]{
+                        Schema.AppMetadataTable.Cols._ID, // Required for cursor loader to work.
+                        Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME,
+                        Schema.AppMetadataTable.Cols.NAME,
+                        Schema.AppMetadataTable.Cols.SUMMARY,
+                        Schema.AppMetadataTable.Cols.IS_COMPATIBLE,
+                        Schema.AppMetadataTable.Cols.LICENSE,
+                        Schema.AppMetadataTable.Cols.ICON,
+                        Schema.AppMetadataTable.Cols.ICON_URL,
+                        Schema.AppMetadataTable.Cols.InstalledApp.VERSION_CODE,
+                        Schema.AppMetadataTable.Cols.InstalledApp.VERSION_NAME,
+                        Schema.AppMetadataTable.Cols.SuggestedApk.VERSION_NAME,
+                        Schema.AppMetadataTable.Cols.SUGGESTED_VERSION_CODE,
+                        Schema.AppMetadataTable.Cols.REQUIREMENTS, // Needed for filtering apps that require root.
+                        Schema.AppMetadataTable.Cols.ANTI_FEATURES, // Needed for filtering apps that require anti-features.
+                },
+                null,
+                null,
+                null
+        );
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+        adapter.setApps(cursor);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        adapter.setApps(null);
+    }
+}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/myapps/UpdatesHeaderController.java b/app/src/main/java/org/fdroid/fdroid/views/myapps/UpdatesHeaderController.java
new file mode 100644
index 000000000..1bbf61210
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/views/myapps/UpdatesHeaderController.java
@@ -0,0 +1,38 @@
+package org.fdroid.fdroid.views.myapps;
+
+import android.app.Activity;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.UpdateService;
+
+public class UpdatesHeaderController extends RecyclerView.ViewHolder {
+
+    private final Activity activity;
+    private final TextView updatesHeading;
+
+    public UpdatesHeaderController(Activity activity, View itemView) {
+        super(itemView);
+        this.activity = activity;
+
+        Button updateAll = (Button) itemView.findViewById(R.id.update_all_button);
+        updateAll.setOnClickListener(onUpdateAll);
+
+        updatesHeading = (TextView) itemView.findViewById(R.id.updates_heading);
+        updatesHeading.setText(activity.getString(R.string.updates));
+    }
+
+    public void bindModel(int numAppsToUpdate) {
+        updatesHeading.setText(activity.getResources().getQuantityString(R.plurals.my_apps_header_number_of_updateable, numAppsToUpdate, numAppsToUpdate));
+    }
+
+    private final View.OnClickListener onUpdateAll = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            UpdateService.autoDownloadUpdates(activity);
+        }
+    };
+}
diff --git a/app/src/main/res/drawable/app_list_item_divider.xml b/app/src/main/res/drawable/app_list_item_divider.xml
new file mode 100644
index 000000000..4e5abfa49
--- /dev/null
+++ b/app/src/main/res/drawable/app_list_item_divider.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Used to separate two sepparate R.layout.app_list_item views in a list.
+  As these are not cards, they don't have their own drop shadow or other features that help
+  separate different list items.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size android:width="1dp" android:height="1dp" />
+    <solid android:color="#ffe3e3e3" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/download_button.xml b/app/src/main/res/drawable/download_button.xml
new file mode 100644
index 000000000..4faebbd9f
--- /dev/null
+++ b/app/src/main/res/drawable/download_button.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Shown in the app list item as a shortcut for the user to be able to download/install an app.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_download_button" />
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_download_button.xml b/app/src/main/res/drawable/ic_download_button.xml
new file mode 100644
index 000000000..562b5f20e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_download_button.xml
@@ -0,0 +1,13 @@
+<vector android:height="32dp" android:viewportHeight="74.53289"
+    android:viewportWidth="74.53289" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillAlpha="1" android:fillColor="#0066cc"
+        android:pathData="m37.27,0c-20.57,0 -37.27,16.7 -37.27,37.27 0,20.57 16.7,37.27 37.27,37.27 20.57,0 37.27,-16.7 37.27,-37.27 0,-20.57 -16.7,-37.27 -37.27,-37.27zM37.27,2c19.49,0 35.27,15.78 35.27,35.27 0,19.49 -15.78,35.27 -35.27,35.27 -19.49,0 -35.27,-15.78 -35.27,-35.27 0,-19.49 15.78,-35.27 35.27,-35.27z"
+        android:strokeAlpha="1" android:strokeColor="#00000000"
+        android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="2"/>
+    <path android:fillAlpha="1" android:fillColor="#0066cc"
+        android:pathData="M23.05,49.12h27.97v4.04h-27.97z"
+        android:strokeAlpha="1" android:strokeColor="#00000000" android:strokeWidth="2"/>
+    <path android:fillAlpha="1" android:fillColor="#0066cc"
+        android:pathData="m31.07,19.19 l0,12.18 -7.71,0 13.86,13.57 13.86,-13.57 -7.83,0 0,-12.18z"
+        android:strokeAlpha="1" android:strokeColor="#00000000" android:strokeWidth="2"/>
+</vector>
diff --git a/app/src/main/res/layout/app_list_item.xml b/app/src/main/res/layout/app_list_item.xml
new file mode 100644
index 000000000..172df3d53
--- /dev/null
+++ b/app/src/main/res/layout/app_list_item.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.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="wrap_content">
+
+    <ImageView
+        android:id="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:contentDescription="@string/app_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        tools:src="@drawable/ic_launcher"
+        android:scaleType="fitCenter"
+        android:layout_marginStart="16dp"
+        android:layout_marginLeft="16dp"
+        android:layout_marginTop="8dp" />
+
+    <TextView
+        android:id="@+id/app_name"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        tools:text="F-Droid Application manager with a long name that will wrap and then ellipsize"
+        android:textSize="18sp"
+        android:textColor="#424242"
+        android:lines="2"
+        android:ellipsize="end"
+        app:layout_constraintStart_toEndOf="@+id/icon"
+        app:layout_constraintTop_toTopOf="@+id/icon"
+        android:layout_marginLeft="8dp"
+        android:layout_marginStart="8dp"
+        app:layout_constraintEnd_toStartOf="@+id/install"
+        android:layout_marginEnd="8dp"
+        android:layout_marginRight="8dp" />
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        tools:text="Installed"
+        android:textStyle="italic"
+        android:textSize="14sp"
+        android:textColor="#424242"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:fontFamily="sans-serif-light"
+        app:layout_constraintTop_toBottomOf="@+id/app_name"
+        app:layout_constraintStart_toEndOf="@+id/icon"
+        android:layout_marginStart="8dp"
+        android:layout_marginLeft="8dp" />
+
+    <Button
+        android:id="@+id/install"
+        android:background="@drawable/download_button"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:padding="4dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/icon" />
+
+    <ImageView
+        android:layout_width="0dp"
+        android:layout_height="1dp"
+        android:scaleType="fitXY"
+        android:src="@drawable/app_list_item_divider"
+        app:layout_constraintTop_toBottomOf="@+id/status"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="8dp"
+        tools:ignore="ContentDescription" />
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/main_tabs.xml b/app/src/main/res/layout/main_tabs.xml
new file mode 100644
index 000000000..d47fb717d
--- /dev/null
+++ b/app/src/main/res/layout/main_tabs.xml
@@ -0,0 +1,19 @@
+<?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="match_parent">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/list"
+        tools:listitem="@layout/app_list_item"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scrollbars="vertical"
+        app:layout_constraintTop_toBottomOf="@+id/update_all_button"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/my_apps_updates_header.xml b/app/src/main/res/layout/my_apps_updates_header.xml
new file mode 100644
index 000000000..f283b406e
--- /dev/null
+++ b/app/src/main/res/layout/my_apps_updates_header.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="16dp"
+    android:paddingStart="16dp"
+    android:paddingRight="16dp"
+    android:paddingEnd="16dp"
+    android:layout_marginTop="16dp"
+    android:paddingBottom="8dp"
+    android:clipToPadding="false">
+
+    <Button
+        android:id="@+id/update_all_button"
+        android:text="@string/my_apps_btn_update_all"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true" />
+
+    <TextView
+        android:id="@+id/updates_heading"
+        tools:text="2 Updates"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:layout_alignBaseline="@+id/update_all_button" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
index 4eaa17c4b..c2fc8ac10 100644
--- a/app/src/main/res/values/ids.xml
+++ b/app/src/main/res/values/ids.xml
@@ -7,4 +7,7 @@
   <item type="id" name="whats_new_large_tile" />
   <item type="id" name="whats_new_small_tile" />
   <item type="id" name="whats_new_regular_list" />
+
+  <item type="id" name="my_apps__header" />
+  <item type="id" name="my_apps__app" />
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ea8e5e513..4ee0d76f1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -63,9 +63,17 @@
     <string name="app_not_installed">Not Installed</string>
     <string name="app_inst_known_source">Installed (from %s)</string>
     <string name="app_inst_unknown_source">Installed (from unknown source)</string>
+    <string name="app_version_x_available">Version %1$s available</string>
+    <string name="app_version_x_installed">Version %1$s</string>
 
     <string name="added_on">Added on %s</string>
 
+    <string name="my_apps_btn_update_all">Update all</string>
+    <plurals name="my_apps_header_number_of_updateable">
+        <item quantity="one">%1$d Update</item>
+        <item quantity="other">%1$d Updates</item>
+    </plurals>
+
     <string name="ok">OK</string>
 
     <string name="yes">Yes</string>
diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java
index 844e81dd9..86c738c78 100644
--- a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java
@@ -114,6 +114,9 @@ public class AppProviderTest extends FDroidProviderTest {
         App notInstalled = AppProvider.Helper.findSpecificApp(r, "not installed", 1, Cols.ALL);
         assertFalse(notInstalled.canAndWantToUpdate(context));
 
+        assertResultCount(contentResolver, 2, AppProvider.getCanUpdateUri(), PROJ);
+        assertResultCount(contentResolver, 7, AppProvider.getInstalledUri(), PROJ);
+
         App installedOnlyOneVersionAvailable   = AppProvider.Helper.findSpecificApp(r, "installed, only one version available", 1, Cols.ALL);
         App installedAlreadyLatestNoIgnore     = AppProvider.Helper.findSpecificApp(r, "installed, already latest, no ignore", 1, Cols.ALL);
         App installedAlreadyLatestIgnoreAll    = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore all", 1, Cols.ALL);
@@ -206,12 +209,14 @@ public class AppProviderTest extends FDroidProviderTest {
         insertApps(100);
 
         assertResultCount(contentResolver, 100, AppProvider.getContentUri(), PROJ);
+        assertResultCount(contentResolver, 0, AppProvider.getCanUpdateUri(), PROJ);
         assertResultCount(contentResolver, 0, AppProvider.getInstalledUri(), PROJ);
 
         for (int i = 10; i < 20; i++) {
             InstalledAppTestUtils.install(context, "com.example.test." + i, i, "v1");
         }
 
+        assertResultCount(contentResolver, 0, AppProvider.getCanUpdateUri(), PROJ);
         assertResultCount(contentResolver, 10, AppProvider.getInstalledUri(), PROJ);
     }