The Makefile in the AOSP 8.1 source code top directory, will include the build/core/main.mk:

### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###

And in build/core/main.mk, there is a verify script snippet:

$(foreach lt,$(ALL_LINK_TYPES),\
  $(foreach d,$($(lt).DEPS),\
    $(if $($(d).TYPE),\
      $(call verify-link-type,$(lt),$(d)),\
      $(call link-type-missing,$(lt),$(d)))))

The definition of verify-link-type is also in the build/core/main.mk:

# Verify that $(1) can link against $(2)
# Both $(1) and $(2) are the link type prefix defined above
define verify-link-type
$(foreach t,$($(2).TYPE),\
  $(if $(filter-out $($(1).ALLOWED),$(t)),\
    $(if $(filter $(t),$($(1).WARN)),\
      $(call link-type-warning,$(1),$(2),$(t)),\
      $(call link-type-error,$(1),$(2),$(t)))))
endef

ifdef link_type_error
  $(error exiting from previous errors)
endif

a. If the module dependency TYPE is in the module’s TYPE allowed linked type list, it will pass the verify.

b. If the module dependency TYPE is not in the module’s TYPE allowed linked type, but it is in the WARN linked type list, it will print the warning info.

c. If the module dependency TYPE is not in the above both list, it will print the error info, and exit the building.

The ALLOWED, TYPE, WARN are initialized by build/make/core/link_type.mk:

# Inputs:
#   LOCAL_MODULE_CLASS, LOCAL_MODULE, LOCAL_MODULE_MAKEFILE, LOCAL_BUILT_MODULE
#   from base_rules.mk: my_kind, my_host_cross
#   my_common: empty or COMMON, like the argument to intermediates-dir-for
#   my_2nd_arch_prefix: usually LOCAL_2ND_ARCH_VAR_PREFIX, separate for JNI installation
#
#   my_link_type: the tags to apply to this module
#   my_warn_types: the tags to warn about in our dependencies
#   my_allowed_types: the tags to allow in our dependencies
#   my_link_deps: the dependencies, in the form of <MODULE_CLASS>:<name>
#

my_link_prefix := LINK_TYPE:$(call find-idf-prefix,$(my_kind),$(my_host_cross))$(if $(filter AUX,$(my_kind)),-$(AUX_OS_VARIANT)):$(if $(my_common),$(my_common):_,_:$(if $(my_2nd_arch_prefix),$(my_2nd_arch_prefix),_))
link_type := $(my_link_prefix):$(LOCAL_MODULE_CLASS):$(LOCAL_MODULE)
ALL_LINK_TYPES := $(ALL_LINK_TYPES) $(link_type)
$(link_type).TYPE := $(my_link_type)
$(link_type).MAKEFILE := $(LOCAL_MODULE_MAKEFILE)
$(link_type).WARN := $(my_warn_types)
$(link_type).ALLOWED := $(my_allowed_types)
$(link_type).DEPS := $(addprefix $(my_link_prefix):,$(my_link_deps))
$(link_type).BUILT := $(LOCAL_BUILT_MODULE)

link_type :=
my_allowed_types :=
my_link_prefix :=
my_link_type :=
my_warn_types :=

It’s very simple. The invoker should assign the my_link_type, my_warn_types, my_allowed_types, and then include link_type.mk. For java module, the build/core/java_common.mk will do the assign work:

ifndef LOCAL_IS_HOST_MODULE
ifeq ($(LOCAL_SDK_VERSION),system_current)
my_link_type := java:system
my_warn_types := java:platform
my_allowed_types := java:sdk java:system
else ifneq ($(LOCAL_SDK_VERSION),)
my_link_type := java:sdk
my_warn_types := java:system java:platform
my_allowed_types := java:sdk
else
my_link_type := java:platform
my_warn_types :=
my_allowed_types := java:sdk java:system java:platform
endif

my_link_deps := $(addprefix JAVA_LIBRARIES:,$(LOCAL_STATIC_JAVA_LIBRARIES))
my_link_deps += $(addprefix APPS:,$(apk_libraries))

my_2nd_arch_prefix := $(LOCAL_2ND_ARCH_VAR_PREFIX)
my_common := COMMON
include $(BUILD_SYSTEM)/link_type.mk

If the module is not host module, it will assign the link type to the module based on the LOCAL_SDK_VERSION value.

a. If the LOCAL_SDK_VERSION is system_current, the module link type is java:system, and it can link to java:sdk and java:system without warning info. And it also can link to java:platform with warning info.

b. If the LOCAL_SDK_VERSION is other non-empty value, the module link type is java:sdk, and it can link to java:sdk without warning info. And it also can link to java:system and java:platform with warning info.

c. If the LOCAL_SDK_VERSION is empty or not defined, the module linke type is java:platform, and it can link to java:sdk, java:system and java:platform without warning info.

In the AOSP 9.0 source code, there are more restricted rules:

###########################################################
# Verify that all libraries are safe to use
###########################################################
ifndef LOCAL_IS_HOST_MODULE
ifeq ($(LOCAL_SDK_VERSION),system_current)
my_link_type := java:system
my_warn_types :=
my_allowed_types := java:sdk java:system java:core
else ifneq (,$(call has-system-sdk-version,$(LOCAL_SDK_VERSION)))
my_link_type := java:system
my_warn_types :=
my_allowed_types := java:sdk java:system java:core
else ifeq ($(LOCAL_SDK_VERSION),core_current)
my_link_type := java:core
my_warn_types :=
my_allowed_types := java:core
else ifneq ($(LOCAL_SDK_VERSION),)
my_link_type := java:sdk
my_warn_types :=
my_allowed_types := java:sdk java:core
else
my_link_type := java:platform
my_warn_types :=
my_allowed_types := java:sdk java:system java:platform java:core
endif

ifdef LOCAL_AAPT2_ONLY
my_link_type += aapt2_only
endif
ifdef LOCAL_USE_AAPT2
my_allowed_types += aapt2_only
endif

my_link_deps := $(addprefix JAVA_LIBRARIES:,$(LOCAL_STATIC_JAVA_LIBRARIES) $(LOCAL_JAVA_LIBRARIES))
my_link_deps += $(addprefix APPS:,$(apk_libraries))

my_2nd_arch_prefix := $(LOCAL_2ND_ARCH_VAR_PREFIX)
my_common := COMMON
include $(BUILD_SYSTEM)/link_type.mk
endif  # !LOCAL_IS_HOST_MODULE

From the preceding code snippet, we can find AOSP 9.0 remove the warning type list, so a java module only can link to module with the link type in its allowed type list. Also it add a new link type called java:core and aapt2_only.

Okay, every build, the build system will ensure every module can only link to dependency with the allowed link type. It will help to separate the system module and normal module. For example, if an apk module assigns the LOCAL_SDK_VERSION with current, it should be built with Android SDK without error, and it can’t link to system module with link type java:platform to use the (hidden) system API. The app with link type java:sdk can used to many AOSP code base have different version, such AOSP 8.1, AOSP 9.0, and etc. It looks like a normal app, it just needs to process the Android SDK behavior change. But for system app, it uses the system API, not exposed by Android SDK, so it should evolve with the AOSP code base, what has more changes than Android SDK.

For system app developer, we should use less non-exposed system API to implement the function, and use standard Android SDK API to implement. We can use LOCAL_SDK_VERSION := current to check our API usage. It will reduce the difficulty when porting system app to new AOSP version.