diff --git a/lib/private/Activity/LegacyFilter.php b/lib/private/Activity/LegacyFilter.php
new file mode 100644
index 0000000000000000000000000000000000000000..4641151245b6956c6719e9144d38f1a468297423
--- /dev/null
+++ b/lib/private/Activity/LegacyFilter.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Activity;
+
+use OCP\Activity\IFilter;
+use OCP\Activity\IManager;
+
+class LegacyFilter implements IFilter {
+
+	/** @var IManager */
+	protected $manager;
+	/** @var string */
+	protected $identifier;
+	/** @var string */
+	protected $name;
+	/** @var bool */
+	protected $isTopFilter;
+
+	/**
+	 * LegacySetting constructor.
+	 *
+	 * @param IManager $manager
+	 * @param string $identifier
+	 * @param string $name
+	 * @param bool $isTopFilter
+	 */
+	public function __construct(IManager $manager,
+								$identifier,
+								$name,
+								$isTopFilter) {
+		$this->manager = $manager;
+		$this->identifier = $identifier;
+		$this->name = $name;
+		$this->isTopFilter = $isTopFilter;
+	}
+
+	/**
+	 * @return string Lowercase a-z and underscore only identifier
+	 * @since 9.2.0
+	 */
+	public function getIdentifier() {
+		return $this->identifier;
+	}
+
+	/**
+	 * @return string A translated string
+	 * @since 9.2.0
+	 */
+	public function getName() {
+		return $this->name;
+	}
+
+	/**
+	 * @return int whether the filter should be rather on the top or bottom of
+	 * the admin section. The filters are arranged in ascending order of the
+	 * priority values. It is required to return a value between 0 and 100.
+	 * @since 9.2.0
+	 */
+	public function getPriority() {
+		return $this->isTopFilter ? 40 : 50;
+	}
+
+	/**
+	 * @return string Full URL to an icon, empty string when none is given
+	 * @since 9.2.0
+	 */
+	public function getIcon() {
+		// Old API was CSS class, so we can not use this...
+		return '';
+	}
+
+	/**
+	 * @param string[] $types
+	 * @return string[] An array of allowed apps from which activities should be displayed
+	 * @since 9.2.0
+	 */
+	public function filterTypes(array $types) {
+		return $this->manager->filterNotificationTypes($types, $this->getIdentifier());
+	}
+
+	/**
+	 * @return string[] An array of allowed apps from which activities should be displayed
+	 * @since 9.2.0
+	 */
+	public function allowedApps() {
+		return [];
+	}
+}
+
diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php
index 455bb3b8ee83c7e9d8984fa22071f73f963555a0..a18dace88ee27c311a61857be04a14d178afc6d1 100644
--- a/lib/private/Activity/Manager.php
+++ b/lib/private/Activity/Manager.php
@@ -27,6 +27,7 @@ namespace OC\Activity;
 use OCP\Activity\IConsumer;
 use OCP\Activity\IEvent;
 use OCP\Activity\IExtension;
+use OCP\Activity\IFilter;
 use OCP\Activity\IManager;
 use OCP\IConfig;
 use OCP\IRequest;
@@ -235,7 +236,7 @@ class Manager implements IManager {
 	 * In order to improve lazy loading a closure can be registered which will be called in case
 	 * activity consumers are actually requested
 	 *
-	 * $callable has to return an instance of OCA\Activity\IConsumer
+	 * $callable has to return an instance of OCA\Activity\IExtension
 	 *
 	 * @param \Closure $callable
 	 * @return void
@@ -245,6 +246,76 @@ class Manager implements IManager {
 		$this->extensions = [];
 	}
 
+	/** @var IFilter[] */
+	protected $filterClasses;
+
+	/** @var IFilter[] */
+	protected $filters;
+
+	/** @var bool */
+	protected $loadedLegacyFilters = false;
+
+	/**
+	 * @param string $filter Class must implement OCA\Activity\IFilter
+	 * @return void
+	 */
+	public function registerFilter($filter) {
+		$this->filterClasses[$filter] = false;
+	}
+
+	/**
+	 * @return IFilter[]
+	 * @throws \InvalidArgumentException
+	 */
+	public function getFilters() {
+		if (!$this->loadedLegacyFilters) {
+			$legacyFilters = $this->getNavigation();
+
+			foreach ($legacyFilters['top'] as $filter => $data) {
+				$this->filters[$filter] = new LegacyFilter(
+					$this, $filter, $data['name'], true
+				);
+			}
+
+			foreach ($legacyFilters['apps'] as $filter => $data) {
+				$this->filters[$filter] = new LegacyFilter(
+					$this, $filter, $data['name'], false
+				);
+			}
+			$this->loadedLegacyFilters = true;
+		}
+
+		foreach ($this->filterClasses as $class => $false) {
+			/** @var IFilter $filter */
+			$filter = \OC::$server->query($class);
+
+			if (!$filter instanceof IFilter) {
+				throw new \InvalidArgumentException('Invalid activity filter registered');
+			}
+
+			$this->filters[$filter->getIdentifier()] = $filter;
+
+			unset($this->filterClasses[$class]);
+		}
+		return $this->filters;
+	}
+
+	/**
+	 * @param string $id
+	 * @return IFilter
+	 * @throws \InvalidArgumentException when the filter was not found
+	 * @since 9.2.0
+	 */
+	public function getFilterById($id) {
+		$filters = $this->getFilters();
+
+		if (isset($filters[$id])) {
+			return $filters[$id];
+		}
+
+		throw new \InvalidArgumentException('Requested filter does not exist');
+	}
+
 	/**
 	 * Will return additional notification types as specified by other apps
 	 *
@@ -390,8 +461,63 @@ class Manager implements IManager {
 		return false;
 	}
 
+	/**
+	 * Set the user we need to use
+	 *
+	 * @param string|null $currentUserId
+	 * @throws \UnexpectedValueException If the user is invalid
+	 */
+	public function setCurrentUserId($currentUserId) {
+		if (!is_string($currentUserId) && $currentUserId !== null) {
+			throw new \UnexpectedValueException('The given current user is invalid');
+		}
+		$this->currentUserId = $currentUserId;
+	}
+
+	/**
+	 * Get the user we need to use
+	 *
+	 * Either the user is logged in, or we try to get it from the token
+	 *
+	 * @return string
+	 * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
+	 */
+	public function getCurrentUserId() {
+		if ($this->currentUserId !== null) {
+			return $this->currentUserId;
+		} else if (!$this->session->isLoggedIn()) {
+			return $this->getUserFromToken();
+		} else {
+			return $this->session->getUser()->getUID();
+		}
+	}
+
+	/**
+	 * Get the user for the token
+	 *
+	 * @return string
+	 * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
+	 */
+	protected function getUserFromToken() {
+		$token = (string) $this->request->getParam('token', '');
+		if (strlen($token) !== 30) {
+			throw new \UnexpectedValueException('The token is invalid');
+		}
+
+		$users = $this->config->getUsersForUserValue('activity', 'rsstoken', $token);
+
+		if (sizeof($users) !== 1) {
+			// No unique user found
+			throw new \UnexpectedValueException('The token is invalid');
+		}
+
+		// Token found login as that user
+		return array_shift($users);
+	}
+
 	/**
 	 * @return array
+	 * @deprecated 9.2.0 - Use getFilters() instead
 	 */
 	public function getNavigation() {
 		$entries = array(
@@ -412,6 +538,7 @@ class Manager implements IManager {
 	/**
 	 * @param string $filterValue
 	 * @return boolean
+	 * @deprecated 9.2.0 - Use getFilterById() instead
 	 */
 	public function isFilterValid($filterValue) {
 		if (isset($this->validFilters[$filterValue])) {
@@ -433,6 +560,7 @@ class Manager implements IManager {
 	 * @param array $types
 	 * @param string $filter
 	 * @return array
+	 * @deprecated 9.2.0 - Use getFilterById()->filterTypes() instead
 	 */
 	public function filterNotificationTypes($types, $filter) {
 		if (!$this->isFilterValid($filter)) {
@@ -451,6 +579,7 @@ class Manager implements IManager {
 	/**
 	 * @param string $filter
 	 * @return array
+	 * @deprecated 9.2.0 - Use getFilterById() instead
 	 */
 	public function getQueryForFilter($filter) {
 		if (!$this->isFilterValid($filter)) {
@@ -477,58 +606,4 @@ class Manager implements IManager {
 
 		return array(' and ((' . implode(') or (', $conditions) . '))', $parameters);
 	}
-
-	/**
-	 * Set the user we need to use
-	 *
-	 * @param string|null $currentUserId
-	 * @throws \UnexpectedValueException If the user is invalid
-	 */
-	public function setCurrentUserId($currentUserId) {
-		if (!is_string($currentUserId) && $currentUserId !== null) {
-			throw new \UnexpectedValueException('The given current user is invalid');
-		}
-		$this->currentUserId = $currentUserId;
-	}
-
-	/**
-	 * Get the user we need to use
-	 *
-	 * Either the user is logged in, or we try to get it from the token
-	 *
-	 * @return string
-	 * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
-	 */
-	public function getCurrentUserId() {
-		if ($this->currentUserId !== null) {
-			return $this->currentUserId;
-		} else if (!$this->session->isLoggedIn()) {
-			return $this->getUserFromToken();
-		} else {
-			return $this->session->getUser()->getUID();
-		}
-	}
-
-	/**
-	 * Get the user for the token
-	 *
-	 * @return string
-	 * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
-	 */
-	protected function getUserFromToken() {
-		$token = (string) $this->request->getParam('token', '');
-		if (strlen($token) !== 30) {
-			throw new \UnexpectedValueException('The token is invalid');
-		}
-
-		$users = $this->config->getUsersForUserValue('activity', 'rsstoken', $token);
-
-		if (sizeof($users) !== 1) {
-			// No unique user found
-			throw new \UnexpectedValueException('The token is invalid');
-		}
-
-		// Token found login as that user
-		return array_shift($users);
-	}
 }
diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php
index 44f495534c9746e03fe809e4bcdeca5a45744f0f..2a6909c484c19440a68080f923334da6657a9f68 100644
--- a/lib/private/App/InfoParser.php
+++ b/lib/private/App/InfoParser.php
@@ -110,6 +110,12 @@ class InfoParser {
 		if (!array_key_exists('commands', $array)) {
 			$array['commands'] = [];
 		}
+		if (!array_key_exists('activity', $array)) {
+			$array['activity'] = [];
+		}
+		if (!array_key_exists('filters', $array['activity'])) {
+			$array['activity']['filters'] = [];
+		}
 
 		if (array_key_exists('types', $array)) {
 			if (is_array($array['types'])) {
@@ -144,6 +150,9 @@ class InfoParser {
 		if (isset($array['commands']['command']) && is_array($array['commands']['command'])) {
 			$array['commands'] = $array['commands']['command'];
 		}
+		if (isset($array['activity']['filters']['filter']) && is_array($array['activity']['filters']['filter'])) {
+			$array['activity']['filters'] = $array['activity']['filters']['filter'];
+		}
 
 		if(!is_null($this->cache)) {
 			$this->cache->set($fileCacheKey, json_encode($array));
diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php
index a89a4650c5d335573ddb7eaeaf93752d92f6fec9..746dcd4f7ccc842965382d177b7dbfe92581a6e4 100644
--- a/lib/private/legacy/app.php
+++ b/lib/private/legacy/app.php
@@ -162,6 +162,13 @@ class OC_App {
 			}
 			\OC::$server->getEventLogger()->end('load_app_' . $app);
 		}
+
+		$info = self::getAppInfo($app);
+		if (!empty($info['activity']['filters'])) {
+			foreach ($info['activity']['filters'] as $filter) {
+				\OC::$server->getActivityManager()->registerFilter($filter);
+			}
+		}
 	}
 
 	/**
diff --git a/lib/public/Activity/IExtension.php b/lib/public/Activity/IExtension.php
index aaa4c869561ae8af2327d26fb4c04b5e51ed8603..21d1bd150ac437dca7d4da5ce7ea7531ebdc173b 100644
--- a/lib/public/Activity/IExtension.php
+++ b/lib/public/Activity/IExtension.php
@@ -129,6 +129,7 @@ interface IExtension {
 	 *
 	 * @return array|false
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Register an IFilter instead
 	 */
 	public function getNavigation();
 
@@ -138,6 +139,7 @@ interface IExtension {
 	 * @param string $filterValue
 	 * @return boolean
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Register an IFilter instead
 	 */
 	public function isFilterValid($filterValue);
 
@@ -149,6 +151,7 @@ interface IExtension {
 	 * @param string $filter
 	 * @return array|false
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Register an IFilter instead
 	 */
 	public function filterNotificationTypes($types, $filter);
 
@@ -161,6 +164,7 @@ interface IExtension {
 	 * @param string $filter
 	 * @return array|false
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Register an IFilter instead
 	 */
 	public function getQueryForFilter($filter);
 }
diff --git a/lib/public/Activity/IFilter.php b/lib/public/Activity/IFilter.php
new file mode 100644
index 0000000000000000000000000000000000000000..f3c57c14e9702966e3b76f29e984630d841665ed
--- /dev/null
+++ b/lib/public/Activity/IFilter.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCP\Activity;
+
+/**
+ * Interface IFilter
+ *
+ * @package OCP\Activity
+ * @since 9.2.0
+ */
+interface IFilter {
+
+	/**
+	 * @return string Lowercase a-z and underscore only identifier
+	 * @since 9.2.0
+	 */
+	public function getIdentifier();
+
+	/**
+	 * @return string A translated string
+	 * @since 9.2.0
+	 */
+	public function getName();
+
+	/**
+	 * @return int whether the filter should be rather on the top or bottom of
+	 * the admin section. The filters are arranged in ascending order of the
+	 * priority values. It is required to return a value between 0 and 100.
+	 * @since 9.2.0
+	 */
+	public function getPriority();
+
+	/**
+	 * @return string Full URL to an icon, empty string when none is given
+	 * @since 9.2.0
+	 */
+	public function getIcon();
+
+	/**
+	 * @param string[] $types
+	 * @return string[] An array of allowed apps from which activities should be displayed
+	 * @since 9.2.0
+	 */
+	public function filterTypes(array $types);
+
+	/**
+	 * @return string[] An array of allowed apps from which activities should be displayed
+	 * @since 9.2.0
+	 */
+	public function allowedApps();
+}
+
diff --git a/lib/public/Activity/IManager.php b/lib/public/Activity/IManager.php
index c1476e1a2ae404c1ec8277d1eeac96ca2f877cf0..387a1d8514415b4675aeee8220505a8f17e9009d 100644
--- a/lib/public/Activity/IManager.php
+++ b/lib/public/Activity/IManager.php
@@ -110,6 +110,27 @@ interface IManager {
 	 */
 	public function registerExtension(\Closure $callable);
 
+	/**
+	 * @param string $filter Class must implement OCA\Activity\IFilter
+	 * @return void
+	 * @since 9.2.0
+	 */
+	public function registerFilter($filter);
+
+	/**
+	 * @return IFilter[]
+	 * @since 9.2.0
+	 */
+	public function getFilters();
+
+	/**
+	 * @param string $id
+	 * @return IFilter
+	 * @throws \InvalidArgumentException when the filter was not found
+	 * @since 9.2.0
+	 */
+	public function getFilterById($id);
+
 	/**
 	 * Will return additional notification types as specified by other apps
 	 *
@@ -177,9 +198,31 @@ interface IManager {
 	 */
 	public function getGroupParameter($activity);
 
+
+	/**
+	 * Set the user we need to use
+	 *
+	 * @param string|null $currentUserId
+	 * @throws \UnexpectedValueException If the user is invalid
+	 * @since 9.0.1
+	 */
+	public function setCurrentUserId($currentUserId);
+
+	/**
+	 * Get the user we need to use
+	 *
+	 * Either the user is logged in, or we try to get it from the token
+	 *
+	 * @return string
+	 * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
+	 * @since 8.1.0
+	 */
+	public function getCurrentUserId();
+
 	/**
 	 * @return array
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Use getFilters() instead
 	 */
 	public function getNavigation();
 
@@ -187,6 +230,7 @@ interface IManager {
 	 * @param string $filterValue
 	 * @return boolean
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Use getFilterById() instead
 	 */
 	public function isFilterValid($filterValue);
 
@@ -195,6 +239,7 @@ interface IManager {
 	 * @param string $filter
 	 * @return array
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Use getFilterById()->filterTypes() instead
 	 */
 	public function filterNotificationTypes($types, $filter);
 
@@ -202,27 +247,7 @@ interface IManager {
 	 * @param string $filter
 	 * @return array
 	 * @since 8.0.0
+	 * @deprecated 9.2.0 - Use getFilterById() instead
 	 */
 	public function getQueryForFilter($filter);
-
-
-	/**
-	 * Set the user we need to use
-	 *
-	 * @param string|null $currentUserId
-	 * @throws \UnexpectedValueException If the user is invalid
-	 * @since 9.0.1
-	 */
-	public function setCurrentUserId($currentUserId);
-
-	/**
-	 * Get the user we need to use
-	 *
-	 * Either the user is logged in, or we try to get it from the token
-	 *
-	 * @return string
-	 * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
-	 * @since 8.1.0
-	 */
-	public function getCurrentUserId();
 }