diff --git a/Makefile.am b/Makefile.am
index 7fce2e7c04ad..5a2b65783591 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -297,7 +297,8 @@ src_bluetoothd_SOURCES = $(builtin_sources) \
 			src/dbus-common.c src/dbus-common.h \
 			src/eir.h src/eir.c \
 			src/adv_monitor.h src/adv_monitor.c \
-			src/battery.h src/battery.c
+			src/battery.h src/battery.c \
+			src/admin_policy.h src/admin_policy.c
 src_bluetoothd_LDADD = lib/libbluetooth-internal.la \
 			gdbus/libgdbus-internal.la \
 			src/libshared-glib.la \
diff --git a/src/adapter.c b/src/adapter.c
index 2fa06b73c240..5531ed0e2246 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -66,6 +66,7 @@
 #include "gatt-database.h"
 #include "advertising.h"
 #include "adv_monitor.h"
+#include "admin_policy.h"
 #include "eir.h"
 #include "battery.h"
 
@@ -260,6 +261,8 @@ struct btd_adapter {
 
 	struct btd_battery_provider_manager *battery_provider_manager;
 
+	struct btd_admin_policy *admin_policy;
+
 	gboolean initialized;
 
 	GSList *pin_callbacks;
@@ -3480,6 +3483,32 @@ static DBusMessage *connect_device(DBusConnection *conn,
 	return NULL;
 }
 
+static void disconnect_device(gpointer data, gpointer user_data)
+{
+	struct btd_device *device = data;
+
+	if (btd_device_is_connected(device))
+		device_request_disconnect(device, NULL);
+}
+
+void btd_adapter_disconnect_all_devices(struct btd_adapter *adapter)
+{
+	g_slist_foreach(adapter->connections, disconnect_device, NULL);
+}
+
+static void update_device_is_blocked_by_policy(void *data, void *user_data)
+{
+	struct btd_device *device = data;
+
+	btd_device_update_is_blocked_by_policy(device);
+}
+
+void btd_adapter_refresh_is_blocked_by_policy(struct btd_adapter *adapter)
+{
+	g_slist_foreach(adapter->devices, update_device_is_blocked_by_policy,
+									NULL);
+}
+
 static const GDBusMethodTable adapter_methods[] = {
 	{ GDBUS_ASYNC_METHOD("StartDiscovery", NULL, NULL, start_discovery) },
 	{ GDBUS_METHOD("SetDiscoveryFilter",
@@ -4688,7 +4717,7 @@ static void probe_profile(struct btd_profile *profile, void *data)
 	struct btd_adapter *adapter = data;
 	int err;
 
-	if (profile->adapter_probe == NULL)
+	if (profile->adapter_probe == NULL || profile->is_blocked_by_policy)
 		return;
 
 	err = profile->adapter_probe(profile, adapter);
@@ -6372,6 +6401,9 @@ static void adapter_remove(struct btd_adapter *adapter)
 	btd_battery_provider_manager_destroy(adapter->battery_provider_manager);
 	adapter->battery_provider_manager = NULL;
 
+	btd_admin_policy_destroy(adapter->admin_policy);
+	adapter->admin_policy = NULL;
+
 	g_slist_free(adapter->pin_callbacks);
 	adapter->pin_callbacks = NULL;
 
@@ -6873,6 +6905,14 @@ struct agent *adapter_get_agent(struct btd_adapter *adapter)
 	return agent_get(NULL);
 }
 
+bool btd_adapter_uuid_is_allowed(struct btd_adapter *adapter, const char *uuid)
+{
+	if (!adapter || !adapter->admin_policy)
+		return true;
+
+	return btd_admin_policy_uuid_is_allowed(adapter->admin_policy, uuid);
+}
+
 static void adapter_remove_connection(struct btd_adapter *adapter,
 						struct btd_device *device,
 						uint8_t bdaddr_type)
@@ -8660,6 +8700,9 @@ static int adapter_register(struct btd_adapter *adapter)
 		agent_unref(agent);
 	}
 
+	if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)
+		adapter->admin_policy = btd_admin_policy_create(adapter);
+
 	/* Don't start GATT database and advertising managers on
 	 * non-LE controllers.
 	 */
diff --git a/src/adapter.h b/src/adapter.h
index 60b5e3bcca34..8e8c61b7bdf1 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -97,6 +97,8 @@ void adapter_service_remove(struct btd_adapter *adapter, uint32_t handle);
 
 struct agent *adapter_get_agent(struct btd_adapter *adapter);
 
+bool btd_adapter_uuid_is_allowed(struct btd_adapter *adapter, const char *uuid);
+
 struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter);
 void btd_adapter_unref(struct btd_adapter *adapter);
 
@@ -240,3 +242,5 @@ enum kernel_features {
 };
 
 bool btd_has_kernel_features(uint32_t feature);
+void btd_adapter_disconnect_all_devices(struct btd_adapter *adapter);
+void btd_adapter_refresh_is_blocked_by_policy(struct btd_adapter *adapter);
diff --git a/src/admin_policy.c b/src/admin_policy.c
new file mode 100644
index 000000000000..eeae2722cc0a
--- /dev/null
+++ b/src/admin_policy.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2021 Google LLC
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus/gdbus.h>
+
+#include "lib/bluetooth.h"
+#include "lib/uuid.h"
+
+#include "adapter.h"
+#include "dbus-common.h"
+#include "log.h"
+#include "metrics.h"
+#include "src/error.h"
+
+#include "admin_policy.h"
+#include "profile.h"
+
+#define ADMIN_POLICY_INTERFACE		"org.bluez.AdminPolicy1"
+
+struct btd_admin_policy {
+	struct btd_adapter *adapter;
+	uint16_t adapter_id;
+	GHashTable *allowed_uuid_set;	/* Set of allowed service uuids*/
+};
+
+static GHashTable *uuid_set_create(void)
+{
+	return g_hash_table_new_full(bt_uuid_hash, bt_uuid_equal, g_free, NULL);
+}
+
+static bool parse_allow_service_list(struct btd_adapter *adapter,
+					GHashTable **uuids,
+					DBusMessage *msg)
+{
+	DBusMessageIter iter, arriter;
+
+	dbus_message_iter_init(msg, &iter);
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
+		return false;
+
+	*uuids = uuid_set_create();
+
+	if (!*uuids) {
+		error("Failed to create UUID allowed set");
+		return false;
+	}
+
+	dbus_message_iter_recurse(&iter, &arriter);
+	do {
+		const int type = dbus_message_iter_get_arg_type(&arriter);
+		char *uuid_param;
+		bt_uuid_t *uuid;
+
+		if (type == DBUS_TYPE_INVALID)
+			break;
+
+		if (type != DBUS_TYPE_STRING)
+			goto failed;
+
+		dbus_message_iter_get_basic(&arriter, &uuid_param);
+		uuid = g_try_malloc(sizeof(*uuid));
+
+		if (!uuid)
+			goto failed;
+
+		if (bt_string_to_uuid(uuid, uuid_param)) {
+			g_free(uuid);
+			goto failed;
+		}
+
+		g_hash_table_add(*uuids, uuid);
+		dbus_message_iter_next(&arriter);
+	} while (true);
+
+	return true;
+
+failed:
+	g_hash_table_destroy(*uuids);
+	*uuids = NULL;
+	return false;
+}
+
+GHashTable *btd_admin_policy_allowlist_get(
+					struct btd_admin_policy *admin_policy)
+{
+	return admin_policy ? admin_policy->allowed_uuid_set : NULL;
+}
+
+void btd_admin_policy_allowlist_set(struct btd_admin_policy *admin_policy,
+							GHashTable *uuids)
+{
+	if (!admin_policy)
+		return;
+
+	admin_policy->allowed_uuid_set = uuids;
+
+	/* This would add/remove profiles to the adapter according to the new
+	 * policy.
+	 */
+	btd_profile_policy_update(admin_policy->adapter);
+
+	/* Update auto-connect status to all devices */
+	btd_adapter_refresh_is_blocked_by_policy(admin_policy->adapter);
+}
+
+static DBusMessage *set_service_allowlist(DBusConnection *conn,
+					DBusMessage *msg, void *user_data)
+{
+	struct btd_admin_policy *admin_policy = user_data;
+	struct btd_adapter *adapter = admin_policy->adapter;
+	GHashTable *uuid_set;
+	const char *sender = dbus_message_get_sender(msg);
+	bool connectable;
+
+	DBG("sender %s", sender);
+
+	/* Parse parameters */
+	if (!parse_allow_service_list(adapter, &uuid_set, msg)) {
+		btd_error(admin_policy->adapter_id,
+				"Failed on parsing allowed service list");
+		return btd_error_invalid_args(msg);
+	}
+
+	/* Diconnect all the existing connections in case some of them are not
+	 * allowed to use.
+	 */
+	btd_adapter_disconnect_all_devices(adapter);
+
+	/* Clear existing allowlist */
+	g_hash_table_destroy(admin_policy->allowed_uuid_set);
+
+	btd_admin_policy_allowlist_set(admin_policy, uuid_set);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static const GDBusMethodTable admin_policy_methods[] = {
+	{ GDBUS_EXPERIMENTAL_METHOD("SetServiceAllowList",
+					GDBUS_ARGS({ "UUIDs", "as" }),
+					NULL, set_service_allowlist) },
+	{ }
+};
+
+bool btd_admin_policy_uuid_is_allowed(struct btd_admin_policy *admin_policy,
+							const char *uuid_str)
+{
+	bt_uuid_t uuid;
+
+	if (!admin_policy)
+		return true;
+
+	if (bt_string_to_uuid(&uuid, uuid_str)) {
+		DBG("Failed to parse UUID string '%s'", uuid_str);
+		return false;
+	}
+
+	return !g_hash_table_size(admin_policy->allowed_uuid_set) ||
+		g_hash_table_contains(admin_policy->allowed_uuid_set, &uuid);
+}
+
+static struct btd_admin_policy *admin_policy_new(struct btd_adapter *adapter)
+{
+	struct btd_admin_policy *admin_policy;
+
+	admin_policy = g_try_malloc(sizeof(*admin_policy));
+
+	if (!admin_policy) {
+		error("Failed to allocate memory for admin_policy");
+		return NULL;
+	}
+
+	admin_policy->adapter = adapter;
+	admin_policy->adapter_id = btd_adapter_get_index(adapter);
+	admin_policy->allowed_uuid_set = uuid_set_create();
+
+	if (!admin_policy->allowed_uuid_set) {
+		error("Failed to create UUID allowed set");
+		g_free(admin_policy);
+		return NULL;
+	}
+
+	return admin_policy;
+}
+
+struct btd_admin_policy *btd_admin_policy_create(struct btd_adapter *adapter)
+{
+	struct btd_admin_policy *admin_policy;
+
+	admin_policy = admin_policy_new(adapter);
+	if (!admin_policy)
+		return NULL;
+
+	if (!g_dbus_register_interface(btd_get_dbus_connection(),
+					adapter_get_path(admin_policy->adapter),
+					ADMIN_POLICY_INTERFACE,
+					admin_policy_methods, NULL, NULL,
+					admin_policy, NULL)) {
+		btd_error(admin_policy->adapter_id,
+				"Failed to register "
+				ADMIN_POLICY_INTERFACE);
+		g_free(admin_policy);
+		return NULL;
+	}
+
+	DBG("register interface success!");
+	return admin_policy;
+}
+
+void btd_admin_policy_destroy(struct btd_admin_policy *admin_policy)
+{
+	if (!admin_policy)
+		return;
+
+	btd_info(admin_policy->adapter_id, "Destroy Admin Policy");
+
+	g_dbus_unregister_interface(btd_get_dbus_connection(),
+					adapter_get_path(admin_policy->adapter),
+					ADMIN_POLICY_INTERFACE);
+	g_hash_table_destroy(admin_policy->allowed_uuid_set);
+	g_free(admin_policy);
+}
diff --git a/src/admin_policy.h b/src/admin_policy.h
new file mode 100644
index 000000000000..5cb97f7ec81e
--- /dev/null
+++ b/src/admin_policy.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2021  Google LLC
+ *
+ *
+ */
+
+#include <glib.h>
+
+struct btd_adapter;
+struct btd_admin_policy;
+
+struct btd_admin_policy *btd_admin_policy_create(struct btd_adapter *adapter);
+void btd_admin_policy_destroy(struct btd_admin_policy *admin_policy);
+GHashTable *btd_admin_policy_allowlist_get(
+					struct btd_admin_policy *admin_policy);
+void btd_admin_policy_allowlist_set(struct btd_admin_policy *admin_policy,
+							GHashTable *uuids);
+bool btd_admin_policy_uuid_is_allowed(struct btd_admin_policy *admin_policy,
+							const char *uuid_str);
diff --git a/src/device.c b/src/device.c
index 7a6f7643afec..2192346d5f8d 100644
--- a/src/device.c
+++ b/src/device.c
@@ -1929,16 +1929,51 @@ static int service_prio_cmp(gconstpointer a, gconstpointer b)
 	return p2->priority - p1->priority;
 }
 
+void btd_device_update_is_blocked_by_policy(struct btd_device *dev)
+{
+	struct btd_adapter *adapter = dev->adapter;
+	struct btd_service *service;
+	struct btd_profile *profile;
+	GSList *l;
+	bool auto_connect = false;
+
+	/* If service discover is ongoing, let the service discover complete
+	 * callback call this function.
+	 */
+	if (dev->browse)
+		return;
+
+	for (l = dev->services; l != NULL; l = g_slist_next(l)) {
+		service = l->data;
+		profile = btd_service_get_profile(service);
+
+		if (!profile->auto_connect)
+			continue;
+
+		if (profile->accept &&
+			!btd_service_is_blocked_by_policy(service))
+			auto_connect = true;
+	}
+
+	if (!dev->disable_auto_connect)
+		device_set_auto_connect(dev, auto_connect);
+}
+
 static GSList *create_pending_list(struct btd_device *dev, const char *uuid)
 {
 	struct btd_service *service;
 	struct btd_profile *p;
 	GSList *l;
+	bool is_blocked;
 
 	if (uuid) {
 		service = find_connectable_service(dev, uuid);
-		if (service)
+		is_blocked = !btd_adapter_uuid_is_allowed(dev->adapter,	uuid);
+
+		if (service && !is_blocked)
 			return g_slist_prepend(dev->pending, service);
+		else if (is_blocked)
+			info("service %s is blocked by policy", uuid);
 
 		return dev->pending;
 	}
@@ -1946,10 +1981,17 @@ static GSList *create_pending_list(struct btd_device *dev, const char *uuid)
 	for (l = dev->services; l != NULL; l = g_slist_next(l)) {
 		service = l->data;
 		p = btd_service_get_profile(service);
+		is_blocked = !btd_adapter_uuid_is_allowed(dev->adapter,
+								p->remote_uuid);
 
 		if (!p->auto_connect)
 			continue;
 
+		if (is_blocked) {
+			info("service %s is blocked by policy", p->remote_uuid);
+			continue;
+		}
+
 		if (g_slist_find(dev->pending, service))
 			continue;
 
@@ -2633,6 +2675,8 @@ static void device_svc_resolved(struct btd_device *dev, uint8_t browse_type,
 							dev->svc_callbacks);
 		g_free(cb);
 	}
+
+	btd_device_update_is_blocked_by_policy(dev);
 }
 
 static struct bonding_req *bonding_request_new(DBusMessage *msg,
@@ -4627,9 +4671,10 @@ static struct btd_service *probe_service(struct btd_device *device,
 	}
 
 	/* Only set auto connect if profile has set the flag and can really
-	 * accept connections.
+	 * accept connections and is not blocked by policy.
 	 */
-	if (profile->auto_connect && profile->accept)
+	if (profile->auto_connect && profile->accept &&
+				!btd_service_is_blocked_by_policy(service))
 		device_set_auto_connect(device, TRUE);
 
 	return service;
diff --git a/src/device.h b/src/device.h
index 4ae9abe0dbb4..3ca4f2c56566 100644
--- a/src/device.h
+++ b/src/device.h
@@ -175,5 +175,6 @@ uint32_t btd_device_get_current_flags(struct btd_device *dev);
 void btd_device_flags_changed(struct btd_device *dev, uint32_t supported_flags,
 			      uint32_t current_flags);
 
+void btd_device_update_is_blocked_by_policy(struct btd_device *dev);
 void btd_device_init(void);
 void btd_device_cleanup(void);
diff --git a/src/profile.c b/src/profile.c
index 5e460b639c19..ae21a079a4e3 100644
--- a/src/profile.c
+++ b/src/profile.c
@@ -2559,6 +2559,45 @@ bool btd_profile_remove_custom_prop(const char *uuid, const char *name)
 	return false;
 }
 
+static void update_profile_policy(struct btd_profile *p,
+						struct btd_adapter *adapter)
+{
+	bool is_blocked;
+
+	is_blocked = !btd_adapter_uuid_is_allowed(adapter, p->remote_uuid);
+
+	if (!is_blocked && p->is_blocked_by_policy) {
+		p->is_blocked_by_policy = false;
+		adapter_add_profile(adapter, p);
+
+		info("service %s is allowed by policy", p->remote_uuid);
+
+	} else if (is_blocked && !p->is_blocked_by_policy) {
+		p->is_blocked_by_policy = true;
+		adapter_remove_profile(adapter, p);
+
+		info("service %s is blocked by policy", p->remote_uuid);
+	}
+}
+
+void btd_profile_policy_update(struct btd_adapter *adapter)
+{
+	GSList *l;
+
+	for (l = profiles; l; l = l->next) {
+		struct btd_profile *p = l->data;
+
+		update_profile_policy(p, adapter);
+	}
+
+	for (l = ext_profiles; l; l = l->next) {
+		struct ext_profile *ext_p = l->data;
+		struct btd_profile *p = &ext_p->p;
+
+		update_profile_policy(p, adapter);
+	}
+}
+
 void btd_profile_init(void)
 {
 	g_dbus_register_interface(btd_get_dbus_connection(),
diff --git a/src/profile.h b/src/profile.h
index 6827f848148c..ddd4c94ec943 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -28,6 +28,8 @@ struct btd_profile {
 	 */
 	bool external;
 
+	bool is_blocked_by_policy;
+
 	int (*device_probe) (struct btd_service *service);
 	void (*device_remove) (struct btd_service *service);
 
@@ -40,6 +42,8 @@ struct btd_profile {
 						struct btd_adapter *adapter);
 	void (*adapter_remove) (struct btd_profile *p,
 						struct btd_adapter *adapter);
+
+	bool (*mandatory_services_are_allowed)(struct btd_service *service);
 };
 
 void btd_profile_foreach(void (*func)(struct btd_profile *p, void *data),
@@ -63,6 +67,7 @@ bool btd_profile_add_custom_prop(const char *uuid, const char *type,
 					btd_profile_prop_get get,
 					void *user_data);
 bool btd_profile_remove_custom_prop(const char *uuid, const char *name);
+void btd_profile_policy_update(struct btd_adapter *adapter);
 
 void btd_profile_init(void);
 void btd_profile_cleanup(void);
diff --git a/src/service.c b/src/service.c
index 21a52762e637..75e71549ae41 100644
--- a/src/service.c
+++ b/src/service.c
@@ -166,6 +166,20 @@ void service_remove(struct btd_service *service)
 	btd_service_unref(service);
 }
 
+bool btd_service_is_blocked_by_policy(struct btd_service *service)
+{
+	struct btd_adapter *adapter;
+
+	if (!service->profile->mandatory_services_are_allowed) {
+		adapter = device_get_adapter(service->device);
+
+		return !btd_adapter_uuid_is_allowed(adapter,
+						service->profile->remote_uuid);
+	}
+
+	return !service->profile->mandatory_services_are_allowed(service);
+}
+
 int service_accept(struct btd_service *service)
 {
 	char addr[18];
@@ -186,6 +200,13 @@ int service_accept(struct btd_service *service)
 	if (!service->profile->accept)
 		return -ENOSYS;
 
+	if (btd_service_is_blocked_by_policy(service)) {
+		info("service %s is blocked by policy",
+						service->profile->remote_uuid);
+
+		return -ECONNABORTED;
+	}
+
 	err = service->profile->accept(service);
 	if (!err)
 		goto done;
diff --git a/src/service.h b/src/service.h
index 88530cc17d53..e2960164fb17 100644
--- a/src/service.h
+++ b/src/service.h
@@ -35,6 +35,7 @@ struct btd_service *service_create(struct btd_device *device,
 int service_probe(struct btd_service *service);
 void service_remove(struct btd_service *service);
 
+bool btd_service_is_blocked_by_policy(struct btd_service *service);
 int service_accept(struct btd_service *service);
 int service_set_connecting(struct btd_service *service);
 
