Android's permission management can be divided majorly in two parts: Linux kernel and Android framework.
DISCRETIONARY ACCESS CONTROL:
Discretionary Access Control (DAC) based on users, groups and access mode predates back to early days of UNIX. Every file (including directories) and process has a user owner (UID) and a group owner (GID). So a process is allowed to Read Write and eXecute a resource depending on its access mode bits (
RWX) for User, Group and Others (
UGO). These permissions attributes are saved along with files on filesystem.
0 is special for Linux kernel; it's the super user (administrator) that is never denied from doing anything. All other users in range
1-65534 are non-privileged IDs which an OS can use in whatever way it wants to control access.
Android reserves UIDs in range
1000-9999 for system use unlike Linux distros where 1000 is usually the first UID assigned to human users. Android is designed for single human user but every Java app, whether preinstalled (as system or user app) or installed by user, is treated as a user. UID range
10000-19999 is reserved for these apps. At first boot (for preinstalled apps) or when installing a new app, a unique UID/GID is assigned to the app which is static unless the app is uninstalled. This app vs. UID mapping can be seen in file
/data/system/packages.list or using command
id (1) if the app provides a shell or can execute native binaries.
All of these UID's have hard-coded names on Android which are predefined, unlike Linux distros, where there are almost standardized naming conventions for users and groups which can be added / removed in
/etc/group. Android shares only
root user name with Linux. However these names are only for the ease of human users, for kernel only numbers (UID/GID) mean.
Access Control Lists (ACLs) can be used to extend DAC for more granular privileges but those are rarely used on Linux and (AFAIK) not used on Android.
Root user's authorities are further divided into subgroups called capabilities. Instead of using UID 0 whenever an elevated task is to be performed, a non-privileged UID can be granted only the required capability. File capabilities which are attached to files using Extended Attributes (XATTR), can also be used to elevate process capabilities whenever required. Android makes use of File Capabilities as well as Ambient Capabilities. See this thread for more details.
MANDATORY ACCESS CONTROL:
Mandatory Access Control (MAC) was introduced later to supplement DAC for better security. Android uses SELinux as part of its security implementations. SELinux also makes use of Extended Attributes to label files with a context while every process is also run with a SELinux context. Then a policy is defined which includes thousands of rules allowing one context access the other, denied otherwise by default. See this answer for more details on SELinux and an example how Android makes use of DAC, capabilities and MAC.
By making use of Linux kernel's DAC, MAC and capabilities, Android launches every app in a sandboxed virtual machine forked from
zygote process, with its unique ID and with all capabilities dropped, so that forced to access only it's own files in
/data/data/<app_pkg_name> or in external storage. See this answer for more details.
To have a further fine-grained control of system resources, in addition to Linux's access control mechanisms at kernel level, Android enforces it's own Manifest Permissions to installed apps within its core framework. These permissions are of three types: Normal, Dangerous and Signature.
All normal permission are granted to a requesting app without human interaction. For instance a user can't restrict an app from accessing internet if the app is requesting
android.permission.INTERNET. Some of such permissions are controlled by using third party tools.
Dangerous permissions - such as
READ_CONTACTS - aren't granted to an app without user's approval. Some of Signature permissions treated as Special permissions - such as
REQUEST_INSTALL_PACKAGES - can also be controlled from
Settings app > Apps & notifications > Advanced > Special app access menu on newer Android versions.
Additionally some of the permissions for which user doesn't have independent control such as GET_ACCOUNTS, or the operations which don't have corresponding permissions at all such as RUN_IN_BACKGROUND, can be controlled from a hidden permissions manager of Android: AppOps. But this is meant only for OS use and not for end users.
PERMISSION CHECKING AND ENFORCING:
Permission preferences are stored by Android Package Manager in different files such as
/data/system/users/0/runtime-permissions.xml at app install time or at runtime. These permission checks are then enforced on multiple events such as when accessing intents, starting an activity, sending/receiving broadcast, accessing a content provider, starting/binding to a service etc.
Though the whole permission checking and enforcing process happens under
zygote in which ActivityManager and PackageManager - which are part of
system_server - play major role, the core framework doesn't handle every permission check on its own. It again takes help from kernel to make permission checks more robust and efficient.
For instance READ/WRITE_EXTERNAL_STORAGE permissions are enforced through UIDs/GIDs by bind-mounting different views of Storage in a mount namespace for every app. See this answer for further details.
PERMISSION <--> GID MAPPING:
Another special case where Android relies on Linux kernel for permission check is
android.permission.INTERNET. As stated earlier, only UID/GID
0 is special for standard Linux kernel. However Android applied a special patch PARANOID_NETWORKING to Linux kernel which maps certain capabilities to certain group IDs in range
3000-3999 making them special. One of those is INET (3003) group which allows its members to access internet (create PACKET sockets) which isn't possible without capability NET_RAW otherwise. All
permission <-> GID mappings can be found in file /system/etc/permissions/platform.xml.
Coming to your question:
since mapping of permissions with UIDs is done at install time, PackageManager should be able to map the permission directly with the APK's UID instead of adding inet GID to your APK package
So the answer is,
PackageManager doesn't handle
android.permission.INTERNET on its own. It just adds the app to INET group and saves the configuration. Let's take example of Termux:
~# grep termux /data/system/packages.list
com.termux 10142 0 /data/user/0/com.termux default:targetSdkVersion=28 3003
Next time the app is launched with 3003 in its supplementary groups so that kernel doesn't restrict the app from accessing internet when enforcing DAC:
~# ps -p $(pgrep com.termux) -o cmd,uid,gid,supgrp
CMD UID GID SUPGRP
com.termux 10142 10142 3003,9997,20142,50142