From 1c5a0554db1840d4e930fe44504859ae3ec61f8d Mon Sep 17 00:00:00 2001 From: roger2015 Date: Mon, 11 Aug 2025 21:25:39 +0800 Subject: [PATCH 1/3] add superuser --- aosp/external/superuser/Android.mk | 28 + aosp/external/superuser/su/config.h.in | 3 + aosp/external/superuser/su/daemon.c | 662 ++++++++++++++++++ aosp/external/superuser/su/kauditd.rc | 10 + aosp/external/superuser/su/pts.c | 336 ++++++++++ aosp/external/superuser/su/pts.h | 116 ++++ aosp/external/superuser/su/su.c | 894 +++++++++++++++++++++++++ aosp/external/superuser/su/su.h | 198 ++++++ aosp/external/superuser/su/utils.c | 129 ++++ aosp/external/superuser/su/utils.h | 30 + 10 files changed, 2406 insertions(+) create mode 100644 aosp/external/superuser/Android.mk create mode 100644 aosp/external/superuser/su/config.h.in create mode 100644 aosp/external/superuser/su/daemon.c create mode 100644 aosp/external/superuser/su/kauditd.rc create mode 100644 aosp/external/superuser/su/pts.c create mode 100644 aosp/external/superuser/su/pts.h create mode 100644 aosp/external/superuser/su/su.c create mode 100644 aosp/external/superuser/su/su.h create mode 100644 aosp/external/superuser/su/utils.c create mode 100644 aosp/external/superuser/su/utils.h diff --git a/aosp/external/superuser/Android.mk b/aosp/external/superuser/Android.mk new file mode 100644 index 000000000..4a2276dda --- /dev/null +++ b/aosp/external/superuser/Android.mk @@ -0,0 +1,28 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := kauditd +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES) +LOCAL_C_INCLUDES := $(local-generated-sources-dir) +LOCAL_SHARED_LIBRARIES := libcutils libc +LOCAL_SRC_FILES := su/su.c su/utils.c su/daemon.c su/pts.c +LOCAL_INIT_RC := su/kauditd.rc + +# generate 9-char string which contains only [0-9a-zA-Z] +CPH_SU_SOCKET_NAME := $(shell (head -c 32 /dev/urandom | base64 | sed 's/[^a-zA-Z0-9]//g' | head -c 9)) + +# generate config.h with $(CPH_SU_SOCKET_NAME) to intermediates directory +GEN := $(local-generated-sources-dir)/config.h +$(GEN): PRIVATE_CUSTOM_TOOL = sed -e 's/%SOCKET_NAME_VALUE%/$(CPH_SU_SOCKET_NAME)/' $< >$@ +$(GEN): $(LOCAL_PATH)/su/config.h.in + $(transform-generated-source) + +LOCAL_GENERATED_SOURCES += $(GEN) + +# Create symlinks. +LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_OUT_VENDOR)/sbin; \ + mkdir -p $(TARGET_ROOT_OUT)/sbin; \ + ln -sf /system/bin/kauditd $(TARGET_OUT_VENDOR)/sbin/su; + +include $(BUILD_EXECUTABLE) diff --git a/aosp/external/superuser/su/config.h.in b/aosp/external/superuser/su/config.h.in new file mode 100644 index 000000000..c4a9e9f0d --- /dev/null +++ b/aosp/external/superuser/su/config.h.in @@ -0,0 +1,3 @@ +// This is automatically generated by script. + +#define SOCKET_NAME "%SOCKET_NAME_VALUE%" diff --git a/aosp/external/superuser/su/daemon.c b/aosp/external/superuser/su/daemon.c new file mode 100644 index 000000000..d00248cda --- /dev/null +++ b/aosp/external/superuser/su/daemon.c @@ -0,0 +1,662 @@ +/* +** Copyright 2010, Adam Shanks (@ChainsDD) +** Copyright 2008, Zinx Verituse (@zinxv) +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define _GNU_SOURCE /* for unshare() */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SUPERUSER_EMBEDDED +#include +#endif + +#include "su.h" +#include "utils.h" +#include "pts.h" +#include "config.h" + +int is_daemon = 0; +int daemon_from_uid = 0; +int daemon_from_pid = 0; + +// Constants for the atty bitfield +#define ATTY_IN 1U +#define ATTY_OUT 2U +#define ATTY_ERR 4U + +/* + * Receive a file descriptor from a Unix socket. + * Contributed by @mkasick + * + * Returns the file descriptor on success, or -1 if a file + * descriptor was not actually included in the message + * + * On error the function terminates by calling exit(-1) + */ +static int recv_fd(int sockfd) { + // Need to receive data from the message, otherwise don't care about it. + char iovbuf; + + struct iovec iov = { + .iov_base = &iovbuf, + .iov_len = 1, + }; + + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + + if (recvmsg(sockfd, &msg, MSG_WAITALL) != 1) { + goto error; + } + + // Was a control message actually sent? + switch (msg.msg_controllen) { + case 0: + // No, so the file descriptor was closed and won't be used. + return -1; + case sizeof(cmsgbuf): + // Yes, grab the file descriptor from it. + break; + default: + goto error; + } + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + + if (cmsg == NULL || + cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) { +error: + LOGE("unable to read fd"); + exit(-1); + } + + return *(int *)CMSG_DATA(cmsg); +} + +/* + * Send a file descriptor through a Unix socket. + * Contributed by @mkasick + * + * On error the function terminates by calling exit(-1) + * + * fd may be -1, in which case the dummy data is sent, + * but no control message with the FD is sent. + */ +static void send_fd(int sockfd, int fd) { + // Need to send some data in the message, this will do. + struct iovec iov = { + .iov_base = "", + .iov_len = 1, + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + if (fd != -1) { + // Is the file descriptor actually open? + if (fcntl(fd, F_GETFD) == -1) { + if (errno != EBADF) { + goto error; + } + // It's closed, don't send a control message or sendmsg will EBADF. + } else { + // It's open, send the file descriptor in a control message. + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + *(int *)CMSG_DATA(cmsg) = fd; + } + } + + /*lint -save -e563 */ + if (sendmsg(sockfd, &msg, 0) != 1) { +error: + PLOGE("unable to send fd"); + exit(-1); + } + /*lint -restore */ +} + +static int read_int(int fd) { + int val; + int len = read(fd, &val, sizeof(int)); + if (len != (ssize_t)sizeof(int)) { + LOGE("unable to read int: %d", len); + exit(-1); + } + return val; +} + +static void write_int(int fd, int val) { + int written = write(fd, &val, sizeof(int)); + if (written != (int)sizeof(int)) { + PLOGE("unable to write int"); + exit(-1); + } +} + +static char* read_string(int fd) { + int len = read_int(fd); + if (len > PATH_MAX || len < 0) { + LOGE("invalid string length %d", len); + exit(-1); + } + char* val = malloc(sizeof(char) * (len + 1)); + if (val == NULL) { + LOGE("unable to malloc string"); + exit(-1); + } + val[len] = '\0'; //lint !e662 + int amount = read(fd, val, len); + if (amount != len) { + LOGE("unable to read string"); + exit(-1); + } + return val; +} + +static void write_string(int fd, const char* val) { + int len = (int)strlen(val); + write_int(fd, len); + int written = write(fd, val, len); + if (written != len) { + PLOGE("unable to write string"); + exit(-1); + } +} + +#ifdef SUPERUSER_EMBEDDED +static void mount_emulated_storage(int user_id) { + const char *emulated_source = getenv("EMULATED_STORAGE_SOURCE"); + const char *emulated_target = getenv("EMULATED_STORAGE_TARGET"); + const char* legacy = getenv("EXTERNAL_STORAGE"); + + if (!emulated_source || !emulated_target) { + // No emulated storage is present + return; + } + + // Create a second private mount namespace for our process + if (unshare(CLONE_NEWNS) < 0) { + PLOGE("unshare"); + return; + } + + if (mount("rootfs", "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) { + PLOGE("mount rootfs as slave"); + return; + } + + // /mnt/shell/emulated -> /storage/emulated + if (mount(emulated_source, emulated_target, NULL, MS_BIND, NULL) < 0) { + PLOGE("mount emulated storage"); + } + + char target_user[PATH_MAX]; + snprintf(target_user, PATH_MAX, "%s/%d", emulated_target, user_id); + + // /mnt/shell/emulated/ -> /storage/emulated/legacy + if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) < 0) { + PLOGE("mount legacy path"); + } +} +#endif + +static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) { + if (-1 == dup2(outfd, STDOUT_FILENO)) { + PLOGE("dup2 child outfd"); + exit(-1); + } + + if (-1 == dup2(errfd, STDERR_FILENO)) { + PLOGE("dup2 child errfd"); + exit(-1); + } + + if (-1 == dup2(infd, STDIN_FILENO)) { + PLOGE("dup2 child infd"); + exit(-1); + } + + close(infd); + close(outfd); + close(errfd); + + return su_main(argc, argv, 0); +} + +static int daemon_accept(int fd) { + is_daemon = 1; + int pid = read_int(fd); + LOGD("remote pid: %d", pid); + char *pts_slave = read_string(fd); + LOGD("remote pts_slave: %s", pts_slave); + daemon_from_uid = read_int(fd); + LOGD("remote uid: %d", daemon_from_uid); + daemon_from_pid = read_int(fd); + LOGD("remote req pid: %d", daemon_from_pid); + + struct ucred credentials; + size_t ucred_length = sizeof(struct ucred); + /* fill in the user data structure */ + if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, (socklen_t*)&ucred_length)) { + LOGE("could obtain credentials from unix domain socket"); + exit(-1); + } + // if the credentials on the other side of the wire are NOT root, + // we can't trust anything being sent. + if (credentials.uid != 0) { + daemon_from_uid = (int)credentials.uid; + pid = credentials.pid; + daemon_from_pid = credentials.pid; + } + + int mount_storage = read_int(fd); + (void) mount_storage; + // The the FDs for each of the streams + int infd = recv_fd(fd); + int outfd = recv_fd(fd); + int errfd = recv_fd(fd); + + int argc = read_int(fd); + if (argc < 0 || argc > 512) { + LOGE("unable to allocate args: %d", argc); + exit(-1); + } + LOGD("remote args: %d", argc); + char** argv = (char**)malloc(sizeof(char*) * (argc + 1)); + if (argv == NULL) { + LOGE("No memory."); + free(pts_slave); + exit(-1); + } + argv[argc] = NULL; + int i; + for (i = 0; i < argc; i++) { + argv[i] = read_string(fd); + } + + // ack + write_int(fd, 1); + + // Fork the child process. The fork has to happen before calling + // setsid() and opening the pseudo-terminal so that the parent + // is not affected + int child = fork(); + if (child < 0) { + // fork failed, send a return code and bail out + PLOGE("unable to fork"); + write(fd, &child, sizeof(int)); + close(fd); + free(pts_slave); + free(argv); + return child; + } + + if (child != 0) { + // In parent, wait for the child to exit, and send the exit code + // across the wire. + int status, code; + + free(pts_slave); + free(argv); + + LOGD("waiting for child exit"); + if (waitpid(child, &status, 0) > 0) { + code = WEXITSTATUS(status); + } + else { + code = -1; + } + + // Pass the return code back to the client + LOGD("sending code"); + if (write(fd, &code, sizeof(int)) != (ssize_t)sizeof(int)) { + PLOGE("unable to write exit code"); + } + + close(fd); + LOGD("child exited"); + return code; + } + + // We are in the child now + // Close the unix socket file descriptor + close (fd); + + // Become session leader + if (setsid() == (pid_t) -1) { + PLOGE("setsid"); + } + + int ptsfd; + if (pts_slave && pts_slave[0]) { //lint !e662 + // Opening the TTY has to occur after the + // fork() and setsid() so that it becomes + // our controlling TTY and not the daemon's + char real_pts_slave[PATH_MAX] = {0}; + if (realpath(pts_slave, real_pts_slave) == NULL) { + LOGE("Can not get real path. exit."); + free(pts_slave); + free(argv); + exit(-1); + } + ptsfd = open(real_pts_slave, O_RDWR); + if (ptsfd == -1) { + PLOGE("open(pts_slave) daemon"); + free(pts_slave); + free(argv); + exit(-1); + } + + if (infd < 0) { + LOGD("daemon: stdin using PTY"); + infd = ptsfd; + } + if (outfd < 0) { + LOGD("daemon: stdout using PTY"); + outfd = ptsfd; + } + if (errfd < 0) { + LOGD("daemon: stderr using PTY"); + errfd = ptsfd; + } + } else { + // If a TTY was sent directly, make it the CTTY. + if (isatty(infd)) { + ioctl(infd, TIOCSCTTY, 1); + } + } + free(pts_slave); + +#ifdef SUPERUSER_EMBEDDED + if (mount_storage) { + mount_emulated_storage(multiuser_get_user_id(daemon_from_uid)); + } +#endif + + return run_daemon_child(infd, outfd, errfd, argc, argv); +} + +int run_daemon() { + if (getuid() != 0 || getgid() != 0) { + PLOGE("daemon requires root. uid/gid not root"); + return -1; + } + + int fd; + struct sockaddr_un sun; + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) { + PLOGE("socket"); + return -1; + } + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { + PLOGE("fcntl FD_CLOEXEC"); + goto err; + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_LOCAL; + + memset(sun.sun_path, 0, sizeof(sun.sun_path)); + memcpy(sun.sun_path, "\0" SOCKET_NAME, strlen(SOCKET_NAME) + 1); + + if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { + PLOGE("daemon bind"); + goto err; + } + + if (listen(fd, 10) < 0) { + PLOGE("daemon listen"); + goto err; + } + + int client; + while ((client = accept(fd, NULL, NULL)) > 0) { + if (fork_zero_fucks() == 0) { + close(fd); + return daemon_accept(client); + } + else { + close(client); + } + } + + LOGE("daemon exiting"); +err: + close(fd); + return -1; +} + +// List of signals which cause process termination +static int quit_signals[] = { SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; + +static void sighandler(int sig) { + (void)sig; + restore_stdin(); + + // Assume we'll only be called before death + // See note before sigaction() in set_stdin_raw() + // + // Now, close all standard I/O to cause the pumps + // to exit so we can continue and retrieve the exit + // code + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + // Put back all the default handlers + struct sigaction act; + int i; + + memset(&act, '\0', sizeof(act)); + act.sa_handler = SIG_DFL; + for (i = 0; quit_signals[i]; i++) { + if (sigaction(quit_signals[i], &act, NULL) < 0) { + PLOGE("Error removing signal handler"); + continue; + } + } +} + +/** + * Setup signal handlers trap signals which should result in program termination + * so that we can restore the terminal to its normal state and retrieve the + * return code. + */ +static void setup_sighandlers(void) { + struct sigaction act; + int i; + + // Install the termination handlers + // Note: we're assuming that none of these signal handlers are already trapped. + // If they are, we'll need to modify this code to save the previous handler and + // call it after we restore stdin to its previous state. + memset(&act, '\0', sizeof(act)); + act.sa_handler = &sighandler; + for (i = 0; quit_signals[i]; i++) { + if (sigaction(quit_signals[i], &act, NULL) < 0) { + PLOGE("Error installing signal handler"); + continue; + } + } +} + +int connect_daemon(int argc, char *argv[], int ppid) { + int uid = (int)getuid(); + int ptmx; + char pts_slave[PATH_MAX]; + + struct sockaddr_un sun; + + // Open a socket to the daemon + int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (socketfd < 0) { + PLOGE("socket"); + exit(-1); + } + if (fcntl(socketfd, F_SETFD, FD_CLOEXEC)) { + PLOGE("fcntl FD_CLOEXEC"); + exit(-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_LOCAL; + + memset(sun.sun_path, 0, sizeof(sun.sun_path)); + memcpy(sun.sun_path, "\0" SOCKET_NAME, strlen(SOCKET_NAME) + 1); + + if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) { + PLOGE("connect"); + exit(-1); + } + + LOGD("connecting client %d", getpid()); + + int mount_storage = getenv("MOUNT_EMULATED_STORAGE") != NULL; + + // Determine which one of our streams are attached to a TTY + unsigned int atty = 0; + + // Send TTYs directly (instead of proxying with a PTY) if + // the SUPERUSER_SEND_TTY environment variable is set. + if (getenv("SUPERUSER_SEND_TTY") == NULL) { + if (isatty(STDIN_FILENO)) atty |= ATTY_IN; + if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT; + if (isatty(STDERR_FILENO)) atty |= ATTY_ERR; + } + + if (atty) { + // We need a PTY. Get one. + ptmx = pts_open(pts_slave, sizeof(pts_slave)); + if (ptmx < 0) { + PLOGE("pts_open"); + exit(-1); + } + } else { + pts_slave[0] = '\0'; + } + + // Send some info to the daemon, starting with our PID + write_int(socketfd, getpid()); + // Send the slave path to the daemon + // (This is "" if we're not using PTYs) + write_string(socketfd, pts_slave); + // User ID + write_int(socketfd, uid); + // Parent PID + write_int(socketfd, ppid); + write_int(socketfd, mount_storage); + + // Send stdin + if (atty & ATTY_IN) { + // Using PTY + send_fd(socketfd, -1); + } else { + send_fd(socketfd, STDIN_FILENO); + } + + // Send stdout + if (atty & ATTY_OUT) { + // Forward SIGWINCH + watch_sigwinch_async(STDOUT_FILENO, ptmx); //lint !e644 + + // Using PTY + send_fd(socketfd, -1); + } else { + send_fd(socketfd, STDOUT_FILENO); + } + + // Send stderr + if (atty & ATTY_ERR) { + // Using PTY + send_fd(socketfd, -1); + } else { + send_fd(socketfd, STDERR_FILENO); + } + + // Number of command line arguments + write_int(socketfd, mount_storage ? argc - 1 : argc); + + // Command line arguments + int i; + for (i = 0; i < argc; i++) { + if (i == 1 && mount_storage) { + continue; + } + write_string(socketfd, argv[i]); + } + + // Wait for acknowledgement from daemon + read_int(socketfd); + + if (atty & ATTY_IN) { + setup_sighandlers(); + pump_stdin_async(ptmx); + } + if (atty & ATTY_OUT) { + pump_stdout_blocking(ptmx); + } + + // Get the exit code + int code = read_int(socketfd); + close(socketfd); + LOGD("client exited %d", code); + + return code; +} diff --git a/aosp/external/superuser/su/kauditd.rc b/aosp/external/superuser/su/kauditd.rc new file mode 100644 index 000000000..2f119d98f --- /dev/null +++ b/aosp/external/superuser/su/kauditd.rc @@ -0,0 +1,10 @@ +on post-fs + mount none /system/vendor/sbin /sbin bind + +on zygote-start + start kauditd + +service kauditd /system/bin/kauditd --daemon + user root + group root + seclabel u:r:shell:s0 diff --git a/aosp/external/superuser/su/pts.c b/aosp/external/superuser/su/pts.c new file mode 100644 index 000000000..d5f1e1f72 --- /dev/null +++ b/aosp/external/superuser/su/pts.c @@ -0,0 +1,336 @@ +/* + * Copyright 2013, Tan Chee Eng (@tan-ce) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* + * pts.c + * + * Manages the pseudo-terminal driver on Linux/Android and provides some + * helper functions to handle raw input mode and terminal window resizing + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pts.h" + +/** + * Helper functions + */ +// Ensures all the data is written out +static int write_blocking(int fd, const char *buf, size_t bufsz) { + ssize_t ret, written; + + written = 0; + do { + ret = write(fd, buf + written, bufsz - written); + if (ret == -1) return -1; + written += ret; + } while (written < (ssize_t)bufsz); + + return 0; +} + +/** + * Pump data from input FD to output FD. If close_output is + * true, then close the output FD when we're done. + */ +static void pump_ex(int input, int output, int close_output) { + char buf[4096]; + int len; + while ((len = read(input, buf, 4096)) > 0) { + if (write_blocking(output, buf, len) == -1) break; + } + close(input); + if (close_output) close(output); +} + +/** + * Pump data from input FD to output FD. Will close the + * output FD when done. + */ +static void pump(int input, int output) { + pump_ex(input, output, 1); +} + +static void* pump_thread(void* data) { + int* files = (int*)data; + int input = files[0]; + int output = files[1]; + pump(input, output); + free(data); + return NULL; +} + +static void pump_async(int input, int output) { + pthread_t writer; + int* files = (int*)malloc(sizeof(int) * 2); + if (files == NULL) { + exit(-1); + } + files[0] = input; + files[1] = output; + pthread_create(&writer, NULL, pump_thread, files); +} + + +/** + * pts_open + * + * Opens a pts device and returns the name of the slave tty device. + * + * Arguments + * slave_name the name of the slave device + * slave_name_size the size of the buffer passed via slave_name + * + * Return Values + * on failure either -2 or -1 (errno set) is returned. + * on success, the file descriptor of the master device is returned. + */ +int pts_open(char *slave_name, size_t slave_name_size) { + int fdm; + char *sn_tmp; + + // Open master ptmx device + fdm = open("/dev/ptmx", O_RDWR); + if (fdm == -1) return -1; + + // Get the slave name + sn_tmp = ptsname(fdm); + if (!sn_tmp) { + close(fdm); + return -2; + } + + strncpy(slave_name, sn_tmp, slave_name_size); + slave_name[slave_name_size - 1] = '\0'; + + // Grant, then unlock + if (grantpt(fdm) == -1) { + close(fdm); + return -1; + } + if (unlockpt(fdm) == -1) { + close(fdm); + return -1; + } + + return fdm; +} + +// Stores the previous termios of stdin +static struct termios old_stdin; +static int stdin_is_raw = 0; + +/** + * set_stdin_raw + * + * Changes stdin to raw unbuffered mode, disables echo, + * auto carriage return, etc. + * + * Return Value + * on failure -1, and errno is set + * on success 0 + */ +int set_stdin_raw(void) { + struct termios new_termios; + + // Save the current stdin termios + if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) { + return -1; + } + + // Start from the current settings + new_termios = old_stdin; + + // Make the terminal like an SSH or telnet client + new_termios.c_iflag |= IGNPAR; + new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); + new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); + new_termios.c_oflag &= ~OPOST; + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) { + return -1; + } + + stdin_is_raw = 1; + + return 0; +} + +/** + * restore_stdin + * + * Restore termios on stdin to the state it was before + * set_stdin_raw() was called. If set_stdin_raw() was + * never called, does nothing and doesn't return an error. + * + * This function is async-safe. + * + * Return Value + * on failure, -1 and errno is set + * on success, 0 + */ +int restore_stdin(void) { + if (!stdin_is_raw) return 0; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) { + return -1; + } + + stdin_is_raw = 0; + + return 0; +} + +// Flag indicating whether the sigwinch watcher should terminate. +volatile static int closing_time = 0; + +/** + * Thread process. Wait for a SIGWINCH to be received, then update + * the terminal size. + */ +static void *watch_sigwinch(void *data) { + sigset_t winch; + int sig; + int master = ((int *)data)[0]; + int slave = ((int *)data)[1]; + + sigemptyset(&winch); + sigaddset(&winch, SIGWINCH); + + do { + // Wait for a SIGWINCH + sigwait(&winch, &sig); + + if (closing_time) break; + + // Get the new terminal size + struct winsize w; + if (ioctl(master, TIOCGWINSZ, &w) == -1) { + continue; + } + + // Set the new terminal size + ioctl(slave, TIOCSWINSZ, &w); + + } while (1); + + free(data); + return NULL; +} + +/** + * watch_sigwinch_async + * + * After calling this function, if the application receives + * SIGWINCH, the terminal window size will be read from + * "input" and set on "output". + * + * NOTE: This function blocks SIGWINCH and spawns a thread. + * NOTE 2: This function must be called before any of the + * pump functions. + * + * Arguments + * master A file descriptor of the TTY window size to follow + * slave A file descriptor of the TTY window size which is + * to be set on SIGWINCH + * + * Return Value + * on failure, -1 and errno will be set. In this case, no + * thread has been spawned and SIGWINCH will not be + * blocked. + * on success, 0 + */ +int watch_sigwinch_async(int master, int slave) { + pthread_t watcher; + int *files = (int *) malloc(sizeof(int) * 2); + if (files == NULL) { + return -1; + } + + // Block SIGWINCH so sigwait can later receive it + sigset_t winch; + sigemptyset(&winch); + sigaddset(&winch, SIGWINCH); + if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) { + free(files); + return -1; + } + + // Initialize some variables, then start the thread + closing_time = 0; + files[0] = master; + files[1] = slave; + int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files); + if (ret != 0) { + free(files); + errno = ret; + return -1; + } + + // Set the initial terminal size + raise(SIGWINCH); + return 0; +} + +/** + * watch_sigwinch_cleanup + * + * Cause the SIGWINCH watcher thread to terminate + */ +void watch_sigwinch_cleanup(void) { + closing_time = 1; + raise(SIGWINCH); +} + +/** + * pump_stdin_async + * + * Forward data from STDIN to the given FD + * in a seperate thread + */ +void pump_stdin_async(int outfd) { + // Put stdin into raw mode + set_stdin_raw(); + + // Pump data from stdin to the PTY + pump_async(STDIN_FILENO, outfd); +} + +/** + * pump_stdout_blocking + * + * Forward data from the FD to STDOUT. + * Returns when the remote end of the FD closes. + * + * Before returning, restores stdin settings. + */ +void pump_stdout_blocking(int infd) { + // Pump data from stdout to PTY + pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */); + + // Cleanup + restore_stdin(); + watch_sigwinch_cleanup(); +} diff --git a/aosp/external/superuser/su/pts.h b/aosp/external/superuser/su/pts.h new file mode 100644 index 000000000..c32364362 --- /dev/null +++ b/aosp/external/superuser/su/pts.h @@ -0,0 +1,116 @@ +/* + * Copyright 2013, Tan Chee Eng (@tan-ce) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* + * pts.h + * + * Manages the pseudo-terminal driver on Linux/Android and provides some + * helper functions to handle raw input mode and terminal window resizing + */ + +#ifndef _PTS_H_ +#define _PTS_H_ + +/** + * pts_open + * + * Opens a pts device and returns the name of the slave tty device. + * + * Arguments + * slave_name the name of the slave device + * slave_name_size the size of the buffer passed via slave_name + * + * Return Values + * on failure either -2 or -1 (errno set) is returned. + * on success, the file descriptor of the master device is returned. + */ +int pts_open(char *slave_name, size_t slave_name_size); + +/** + * set_stdin_raw + * + * Changes stdin to raw unbuffered mode, disables echo, + * auto carriage return, etc. + * + * Return Value + * on failure -1, and errno is set + * on success 0 + */ +int set_stdin_raw(void); + +/** + * restore_stdin + * + * Restore termios on stdin to the state it was before + * set_stdin_raw() was called. If set_stdin_raw() was + * never called, does nothing and doesn't return an error. + * + * This function is async-safe. + * + * Return Value + * on failure, -1 and errno is set + * on success, 0 + */ +int restore_stdin(void); + +/** + * watch_sigwinch_async + * + * After calling this function, if the application receives + * SIGWINCH, the terminal window size will be read from + * "input" and set on "output". + * + * NOTE: This function blocks SIGWINCH and spawns a thread. + * + * Arguments + * master A file descriptor of the TTY window size to follow + * slave A file descriptor of the TTY window size which is + * to be set on SIGWINCH + * + * Return Value + * on failure, -1 and errno will be set. In this case, no + * thread has been spawned and SIGWINCH will not be + * blocked. + * on success, 0 + */ +int watch_sigwinch_async(int master, int slave); + +/** + * watch_sigwinch_cleanup + * + * Cause the SIGWINCH watcher thread to terminate + */ +void watch_sigwinch_cleanup(void); + +/** + * pump_stdin_async + * + * Forward data from STDIN to the given FD + * in a seperate thread + */ +void pump_stdin_async(int outfd); + +/** + * pump_stdout_blocking + * + * Forward data from the FD to STDOUT. + * Returns when the remote end of the FD closes. + * + * Before returning, restores stdin settings. + */ +void pump_stdout_blocking(int infd); + +#endif diff --git a/aosp/external/superuser/su/su.c b/aosp/external/superuser/su/su.c new file mode 100644 index 000000000..964fd653e --- /dev/null +++ b/aosp/external/superuser/su/su.c @@ -0,0 +1,894 @@ +/* +** Copyright 2010, Adam Shanks (@ChainsDD) +** Copyright 2008, Zinx Verituse (@zinxv) +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "su.h" +#include "utils.h" + +extern int is_daemon; +extern int daemon_from_uid; +extern int daemon_from_pid; + +unsigned get_shell_uid() { + struct passwd* ppwd = getpwnam("shell"); + if (NULL == ppwd) { + return 2000; + } + + return ppwd->pw_uid; +} + +unsigned get_system_uid() { + struct passwd* ppwd = getpwnam("system"); + if (NULL == ppwd) { + return 1000; + } + + return ppwd->pw_uid; +} + +unsigned get_radio_uid() { + struct passwd* ppwd = getpwnam("radio"); + if (NULL == ppwd) { + return 1001; + } + + return ppwd->pw_uid; +} + +int fork_zero_fucks() { + int pid = fork(); + if (pid) { + int status; + waitpid(pid, &status, 0); + return pid; + } + else { + if ((pid = fork()) != 0) + exit(0); + return 0; + } +} + +void exec_log(char *priority, char* logline) { + int pid; + if ((pid = fork()) == 0) { + int null = open("/dev/null", O_WRONLY | O_CLOEXEC); + if (null < 0) { + exit(-1); + } + dup2(null, STDIN_FILENO); + dup2(null, STDOUT_FILENO); + dup2(null, STDERR_FILENO); + execl("/system/bin/log", "/system/bin/log", "-p", priority, "-t", LOG_TAG, logline, NULL); + _exit(0); + } + int status; + waitpid(pid, &status, 0); +} + +void exec_loge(const char* fmt, ...) { + va_list args; + + char logline[PATH_MAX]; + va_start(args, fmt); + vsnprintf(logline, PATH_MAX, fmt, args); + va_end(args); + exec_log("e", logline); +} + +void exec_logw(const char* fmt, ...) { + va_list args; + + char logline[PATH_MAX]; + va_start(args, fmt); + vsnprintf(logline, PATH_MAX, fmt, args); + va_end(args); + exec_log("w", logline); +} + +void exec_logd(const char* fmt, ...) { + va_list args; + + char logline[PATH_MAX]; + va_start(args, fmt); + vsnprintf(logline, PATH_MAX, fmt, args); + va_end(args); + exec_log("d", logline); +} + +static int from_init(struct su_initiator *from) { + char path[PATH_MAX], exe[PATH_MAX]; + char args[4096], *argv0, *argv_rest; + int fd; + ssize_t len; + int i; + int err; + + from->uid = getuid(); + from->pid = getppid(); + + if (is_daemon) { + from->uid = (unsigned)daemon_from_uid; + from->pid = daemon_from_pid; + } + + /* Get the command line */ + snprintf(path, sizeof(path), "/proc/%d/cmdline", from->pid); + fd = open(path, O_RDONLY); + if (fd < 0) { + PLOGE("Opening command line"); + return -1; + } + len = read(fd, args, sizeof(args)); + err = errno; + close(fd); + if (len < 0 || (size_t)len == sizeof(args)) { + PLOGEV("Reading command line", err); + return -1; + } + + argv0 = args; + argv_rest = NULL; + for (i = 0; i < len; i++) { + if (args[i] == '\0') { + if (!argv_rest) { + argv_rest = &args[i+1]; + } else { + args[i] = ' '; + } + } + } + args[len] = '\0'; + + if (argv_rest) { + strncpy(from->args, argv_rest, sizeof(from->args)); + from->args[sizeof(from->args)-1] = '\0'; + } else { + from->args[0] = '\0'; + } + + /* If this isn't app_process, use the real path instead of argv[0] */ + snprintf(path, sizeof(path), "/proc/%d/exe", from->pid); + len = readlink(path, exe, sizeof(exe)); + if (len < 0 || len >= PATH_MAX) { + PLOGE("Getting exe path"); + return -1; + } + exe[len] = '\0'; + if (strcmp(exe, "/system/bin/app_process")) { + argv0 = exe; + } + + strncpy(from->bin, argv0, sizeof(from->bin)); + from->bin[sizeof(from->bin)-1] = '\0'; + + struct passwd *pw; + pw = getpwuid(from->uid); + if (pw && pw->pw_name) { + strncpy(from->name, pw->pw_name, sizeof(from->name)); + } + + return 0; +} + +static int get_multiuser_mode() { + char *data; + char sdk_ver[PROPERTY_VALUE_MAX]; + + data = read_file("/system/build.prop"); + get_property(data, sdk_ver, PROPERTY_VALUE_MAX, "ro.build.version.sdk", "0"); + free(data); + + int sdk = atoi(sdk_ver); + if (sdk < 17) + return MULTIUSER_MODE_NONE; + + int ret = MULTIUSER_MODE_OWNER_ONLY; + char mode[12]; + FILE *fp; + if ((fp = fopen(REQUESTOR_MULTIUSER_MODE, "r"))) { + fgets(mode, sizeof(mode), fp); + size_t last = strlen(mode) - 1; + if (mode[last] == '\n') + mode[last] = '\0'; + if (strcmp(mode, MULTIUSER_VALUE_USER) == 0) { + ret = MULTIUSER_MODE_USER; + } else if (strcmp(mode, MULTIUSER_VALUE_OWNER_MANAGED) == 0) { + ret = MULTIUSER_MODE_OWNER_MANAGED; + } + else { + ret = MULTIUSER_MODE_OWNER_ONLY; + } + fclose(fp); + } + return ret; +} + +static void read_options(struct su_context *ctx) { + ctx->user.multiuser_mode = get_multiuser_mode(); +} + +static void user_init(struct su_context *ctx) { + if (ctx->from.uid > 99999) { + ctx->user.android_user_id = ctx->from.uid / 100000; + if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) { + snprintf(ctx->user.database_path, PATH_MAX, "%s/%u/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR_DATABASE_PATH); + snprintf(ctx->user.base_path, PATH_MAX, "%s/%u/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR); + } + } +} + +static void populate_environment(const struct su_context *ctx) { + struct passwd *pw; + + if (ctx->to.keepenv) + return; + + pw = getpwuid(ctx->to.uid); + if (pw) { + setenv("HOME", pw->pw_dir, 1); + if (ctx->to.shell) + setenv("SHELL", ctx->to.shell, 1); + else + setenv("SHELL", DEFAULT_SHELL, 1); + if (ctx->to.login || ctx->to.uid) { + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + } + } +} + +void set_identity(unsigned int uid) { + /* + * Set effective uid back to root, otherwise setres[ug]id will fail + * if uid isn't root. + */ + if (seteuid(0)) { + PLOGE("seteuid (root)"); + exit(EXIT_FAILURE); + } + if (setresgid(uid, uid, uid)) { + PLOGE("setresgid (%u)", uid); + exit(EXIT_FAILURE); + } + if (setresuid(uid, uid, uid)) { + PLOGE("setresuid (%u)", uid); + exit(EXIT_FAILURE); + } +} + +static void socket_cleanup(struct su_context *ctx) { + if (ctx && ctx->sock_path[0]) { + unlink(ctx->sock_path); + ctx->sock_path[0] = 0; + } +} + +/* + * For use in signal handlers/atexit-function + * NOTE: su_ctx points to main's local variable. + * It's OK due to the program uses exit(3), not return from main() + */ +static struct su_context *su_ctx = NULL; + +static void cleanup(void) { + socket_cleanup(su_ctx); +} + +static void cleanup_signal(int sig) { + socket_cleanup(su_ctx); + _exit(128 + sig); +} + +static int socket_create_temp(char *path, size_t len) { + int fd; + struct sockaddr_un sun; + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) { + PLOGE("socket"); + return -1; + } + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { + PLOGE("fcntl FD_CLOEXEC"); + goto err; + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_LOCAL; + snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid()); + memset(sun.sun_path, 0, sizeof(sun.sun_path)); + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path); + + /* + * Delete the socket to protect from situations when + * something bad occured previously and the kernel reused pid from that process. + * Small probability, isn't it. + */ + unlink(sun.sun_path); + + if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { + PLOGE("bind"); + goto err; + } + + if (listen(fd, 1) < 0) { + PLOGE("listen"); + goto err; + } + + return fd; +err: + close(fd); + return -1; +} + +static int socket_accept(int serv_fd) { + struct timeval tv; + fd_set fds; + int fd, rc; + + /* Wait 20 seconds for a connection, then give up. */ + tv.tv_sec = 20; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(serv_fd, &fds); + do { + rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); + } while (rc < 0 && errno == EINTR); + if (rc < 1) { + PLOGE("select"); + return -1; + } + + fd = accept(serv_fd, NULL, NULL); + if (fd < 0) { + PLOGE("accept"); + return -1; + } + + return fd; +} + +static int socket_send_request(int fd, const struct su_context *ctx) { +#define write_data(fd, data, data_len) \ +do { \ + size_t __len = htonl(data_len); \ + __len = (size_t)write((fd), &__len, sizeof(__len)); \ + if (__len != sizeof(__len)) { \ + PLOGE("write(" #data ")"); \ + return -1; \ + } \ + __len = (size_t)write((fd), data, data_len); \ + if (__len != data_len) { \ + PLOGE("write(" #data ")"); \ + return -1; \ + } \ +} while (0) + +#define write_string_data(fd, name, data) \ +do { \ + write_data(fd, name, strlen(name)); \ + write_data(fd, data, strlen(data)); \ +} while (0) + +// stringify everything. +#define write_token(fd, name, data) \ +do { \ + char buf[16]; \ + snprintf(buf, sizeof(buf), "%d", data); \ + write_string_data(fd, name, buf); \ +} while (0) + + /*lint -save -e666 */ + write_token(fd, "version", PROTO_VERSION); + write_token(fd, "binary.version", VERSION_CODE); + write_token(fd, "pid", ctx->from.pid); + write_string_data(fd, "from.name", ctx->from.name); + write_string_data(fd, "to.name", ctx->to.name); + write_token(fd, "from.uid", (int)ctx->from.uid); + write_token(fd, "to.uid", (int)ctx->to.uid); + write_string_data(fd, "from.bin", ctx->from.bin); + // TODO: Fix issue where not using -c does not result a in a command + write_string_data(fd, "command", get_command(&ctx->to)); + write_token(fd, "eof", PROTO_VERSION); + /*lint -restore */ + return 0; +} + +static int socket_receive_result(int fd, char *result, ssize_t result_len) { + ssize_t len; + + LOGD("waiting for user"); + len = read(fd, result, result_len-1); + if (len < 0) { + PLOGE("read(result)"); + return -1; + } + result[len] = '\0'; + + return 0; +} + +static void usage(int status) { + FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; + + fprintf(stream, + "Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n" + "Options:\n" + " --daemon start the su daemon agent\n" + " -c, --command COMMAND pass COMMAND to the invoked shell\n" + " -h, --help display this help message and exit\n" + " -, -l, --login pretend the shell to be a login shell\n" + " -m, -p,\n" + " --preserve-environment do not change environment variables\n" + " -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n" + " -u display the multiuser mode and exit\n" + " -v, --version display version number and exit\n" + " -V display version code and exit,\n"); + exit(status); +} + +static __attribute__ ((noreturn)) void deny(struct su_context *ctx) { + char *cmd = get_command(&ctx->to); + + LOGW("request rejected (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd); + fprintf(stderr, "%s\n", strerror(EACCES)); //lint !e560 + exit(EXIT_FAILURE); +} + +static __attribute__ ((noreturn)) void allow(struct su_context *ctx) { + char *arg0; + int argc, err; + + umask(ctx->umask); + + char *binary; + argc = ctx->to.optind; + if (ctx->to.command) { + binary = ctx->to.shell; + ctx->to.argv[--argc] = ctx->to.command; + ctx->to.argv[--argc] = "-c"; + } + else if (ctx->to.shell) { + binary = ctx->to.shell; + } + else { + if (ctx->to.argv[argc]) { + binary = ctx->to.argv[argc++]; + } + else { + binary = DEFAULT_SHELL; + } + } + + arg0 = strrchr (binary, '/'); + arg0 = (arg0) ? arg0 + 1 : binary; + if (ctx->to.login) { + size_t s = strlen(arg0) + 2; + char *p = malloc(s); + + if (!p) { + exit(EXIT_FAILURE); + } + + *p = '-'; + strcpy(p + 1, arg0); + arg0 = p; + } + + populate_environment(ctx); + set_identity(ctx->to.uid); + +#define PARG(arg) \ + (argc + (arg) < ctx->to.argc) ? " " : "", \ + (argc + (arg) < ctx->to.argc) ? ctx->to.argv[argc + (arg)] : "" + + ctx->to.argv[--argc] = arg0; + execvp(binary, ctx->to.argv + argc); + err = errno; + PLOGE("exec"); + fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(err)); //lint !e560 + exit(EXIT_FAILURE); +} + +/* + * CyanogenMod-specific behavior + * + * we can't simply use the property service, since we aren't launched from init + * and can't trust the location of the property workspace. + * Find the properties ourselves. + */ +int access_disabled(const struct su_initiator *from) { +#ifndef SUPERUSER_EMBEDDED + (void)from; + return 0; +#else + char *data; + char build_type[PROPERTY_VALUE_MAX]; + char debuggable[PROPERTY_VALUE_MAX], enabled[PROPERTY_VALUE_MAX]; + size_t len; + + data = read_file("/system/build.prop"); + if (check_property(data, "ro.cm.version")) { + get_property(data, build_type, PROPERTY_VALUE_MAX, "ro.build.type", ""); + free(data); + + data = read_file("/default.prop"); + get_property(data, debuggable, PROPERTY_VALUE_MAX, "ro.debuggable", "0"); + free(data); + /* only allow su on debuggable builds */ + if (strcmp("1", debuggable) != 0) { + LOGE("Root access is disabled on non-debug builds"); + return 1; + } + + data = read_file("/data/property/persist.sys.root_access"); + if (data != NULL) { + len = strlen(data); + if (len >= PROPERTY_VALUE_MAX) + memcpy(enabled, "1", 2); + else + memcpy(enabled, data, len + 1); + free(data); + } else + memcpy(enabled, "1", 2); + + /* enforce persist.sys.root_access on non-eng builds for apps */ + if (strcmp("eng", build_type) != 0 && + from->uid != AID_SHELL && from->uid != AID_ROOT && + (atoi(enabled) & CM_ROOT_ACCESS_APPS_ONLY) != CM_ROOT_ACCESS_APPS_ONLY ) { + LOGE("Apps root access is disabled by system setting - " + "enable it under settings -> developer options"); + return 1; + } + + /* disallow su in a shell if appropriate */ + if (from->uid == AID_SHELL && + (atoi(enabled) & CM_ROOT_ACCESS_ADB_ONLY) != CM_ROOT_ACCESS_ADB_ONLY ) { + LOGE("Shell root access is disabled by a system setting - " + "enable it under settings -> developer options"); + return 1; + } + + } + return 0; +#endif +} + +static int get_api_version() { + char sdk_ver[PROPERTY_VALUE_MAX]; + char *data = read_file("/system/build.prop"); + get_property(data, sdk_ver, PROPERTY_VALUE_MAX, "ro.build.version.sdk", "0"); + int ver = atoi(sdk_ver); + free(data); + return ver; +} + +int main(int argc, char *argv[]) { + return su_main(argc, argv, 1); +} + +int su_main(int argc, char *argv[], int need_client) { + // start up in daemon mode if prompted + if (argc == 2 && strcmp(argv[1], "--daemon") == 0) { + return run_daemon(); + } + + int ppid = getppid(); + + // Sanitize all secure environment variables (from linker_environ.c in AOSP linker). + /* The same list than GLibc at this point */ + static const char* const unsec_vars[] = { + "GCONV_PATH", + "GETCONF_DIR", + "HOSTALIASES", + "LD_AUDIT", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_DYNAMIC_WEAK", + "LD_LIBRARY_PATH", + "LD_ORIGIN_PATH", + "LD_PRELOAD", + "LD_PROFILE", + "LD_SHOW_AUXV", + "LD_USE_LOAD_BIAS", + "LOCALDOMAIN", + "LOCPATH", + "MALLOC_TRACE", + "MALLOC_CHECK_", + "NIS_PATH", + "NLSPATH", + "RESOLV_HOST_CONF", + "RES_OPTIONS", + "TMPDIR", + "TZDIR", + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + // not listed in linker, used due to system() call + "IFS", + }; + const char* const* cp = unsec_vars; + const char* const* endp = cp + sizeof(unsec_vars)/sizeof(unsec_vars[0]); + while (cp < endp) { + unsetenv(*cp); + cp++; + } + + struct su_context ctx = { + .from = { + .pid = -1, + .uid = 0, + .bin = "", + .args = "", + .name = "", + }, + .to = { + .uid = AID_ROOT, + .login = 0, + .keepenv = 0, + .shell = NULL, + .command = NULL, + .argv = argv, + .argc = argc, + .optind = 0, + .name = "", + }, + .user = { + .android_user_id = 0, + .multiuser_mode = MULTIUSER_MODE_OWNER_ONLY, + .database_path = REQUESTOR_DATA_PATH REQUESTOR_DATABASE_PATH, + .base_path = REQUESTOR_DATA_PATH REQUESTOR + }, + }; + struct stat st; + int c, socket_serv_fd, fd; + char buf[64], *result; + struct option long_opts[] = { + { "command", required_argument, NULL, 'c' }, + { "help", no_argument, NULL, 'h' }, + { "login", no_argument, NULL, 'l' }, + { "preserve-environment", no_argument, NULL, 'p' }, + { "shell", required_argument, NULL, 's' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 }, + }; + + while ((c = getopt_long(argc, argv, "+c:hlmps:Vvu", long_opts, NULL)) != -1) { + switch(c) { + case 'c': + ctx.to.shell = DEFAULT_SHELL; + ctx.to.command = optarg; + break; + case 'h': + usage(EXIT_SUCCESS); + break; + case 'l': + ctx.to.login = 1; + break; + case 'm': + case 'p': + ctx.to.keepenv = 1; + break; + case 's': + ctx.to.shell = optarg; + break; + case 'V': + printf("%d\n", VERSION_CODE); + exit(EXIT_SUCCESS); + break; + case 'v': + printf("%s\n", VERSION); + exit(EXIT_SUCCESS); + break; + case 'u': + switch (get_multiuser_mode()) { + case MULTIUSER_MODE_USER: + printf("%s\n", MULTIUSER_VALUE_USER); + break; + case MULTIUSER_MODE_OWNER_MANAGED: + printf("%s\n", MULTIUSER_VALUE_OWNER_MANAGED); + break; + case MULTIUSER_MODE_OWNER_ONLY: + printf("%s\n", MULTIUSER_VALUE_OWNER_ONLY); + break; + case MULTIUSER_MODE_NONE: + printf("%s\n", MULTIUSER_VALUE_NONE); + break; + } + exit(EXIT_SUCCESS); + break; + default: + /* Bionic getopt_long doesn't terminate its error output by newline */ + fprintf(stderr, "\n"); + usage(2); + } + } + + if (need_client) { + // attempt to use the daemon client if not root, + // or this is api 18 and adb shell (/data is not readable even as root) + // or just always use it on API 19+ (ART) + if ((geteuid() != AID_ROOT && getuid() != AID_ROOT) || + (get_api_version() >= 18 && getuid() == AID_SHELL) || + get_api_version() >= 19) { + // attempt to connect to daemon... + LOGD("starting daemon client %d %d", getuid(), geteuid()); + return connect_daemon(argc, argv, ppid); + } + } + + if (optind < argc && !strcmp(argv[optind], "-")) { + ctx.to.login = 1; + optind++; + } + /* username or uid */ + if (optind < argc && strcmp(argv[optind], "--")) { + struct passwd *pw; + pw = getpwnam(argv[optind]); + if (!pw) { + char *endptr; + + /* It seems we shouldn't do this at all */ + errno = 0; + ctx.to.uid = strtoul(argv[optind], &endptr, 10); + if (errno || *endptr) { + LOGE("Unknown id: %s\n", argv[optind]); + fprintf(stderr, "Unknown id: %s\n", argv[optind]); + exit(EXIT_FAILURE); + } + } else { + ctx.to.uid = pw->pw_uid; + if (pw->pw_name) + strncpy(ctx.to.name, pw->pw_name, sizeof(ctx.to.name)); + } + optind++; + } + if (optind < argc && !strcmp(argv[optind], "--")) { + optind++; + } + ctx.to.optind = optind; + + su_ctx = &ctx; + if (from_init(&ctx.from) < 0) { + deny(&ctx); + } + + read_options(&ctx); + user_init(&ctx); + + // the latter two are necessary for stock ROMs like note 2 which do dumb things with su, or crash otherwise + if (ctx.from.uid == AID_ROOT) { + LOGD("Allowing root/system/radio."); + allow(&ctx); + } + + // odd perms on superuser data dir + if (st.st_gid != st.st_uid) { + LOGE("Bad uid/gid %d/%d for Superuser Requestor application", + (int)st.st_uid, (int)st.st_gid); + deny(&ctx); + } + + // always allow if this is the superuser uid + // superuser needs to be able to reenable itself when disabled... + allow(&ctx); + + // check if superuser is disabled completely + if (access_disabled(&ctx.from)) { + LOGD("access_disabled"); + deny(&ctx); + } + + // autogrant shell at this point + if (ctx.from.uid == AID_SHELL) { + LOGD("Allowing shell."); + allow(&ctx); + } + + // deny if this is a non owner request and owner mode only + if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0) { + deny(&ctx); + } + + ctx.umask = umask(027); + + mkdir(REQUESTOR_CACHE_PATH, 0770); + if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) { + PLOGE("chown (%s, %ld, %ld)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid); + deny(&ctx); + } + + if (setgroups(0, NULL)) { + PLOGE("setgroups"); + deny(&ctx); + } + if (setegid(st.st_gid)) { + PLOGE("setegid (%lu)", st.st_gid); + deny(&ctx); + } + if (seteuid(st.st_uid)) { + PLOGE("seteuid (%lu)", st.st_uid); + deny(&ctx); + } + + socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path)); + LOGD(ctx.sock_path); + if (socket_serv_fd < 0) { + deny(&ctx); + } + + signal(SIGHUP, cleanup_signal); + signal(SIGPIPE, cleanup_signal); + signal(SIGTERM, cleanup_signal); + signal(SIGQUIT, cleanup_signal); + signal(SIGINT, cleanup_signal); + signal(SIGABRT, cleanup_signal); + + atexit(cleanup); + + fd = socket_accept(socket_serv_fd); + if (fd < 0) { + deny(&ctx); + } + if (socket_send_request(fd, &ctx)) { + deny(&ctx); + } + if (socket_receive_result(fd, buf, sizeof(buf))) { + deny(&ctx); + } + + close(fd); + close(socket_serv_fd); + socket_cleanup(&ctx); + + result = buf; + +#define SOCKET_RESPONSE "socket:" + if (strncmp(result, SOCKET_RESPONSE, sizeof(SOCKET_RESPONSE) - 1)) + LOGW("SECURITY RISK: Requestor still receives credentials in intent"); + else + result += sizeof(SOCKET_RESPONSE) - 1; + + if (!strcmp(result, "DENY")) { + deny(&ctx); + } else if (!strcmp(result, "ALLOW")) { + allow(&ctx); + } else { + LOGE("unknown response from Superuser Requestor: %s", result); + deny(&ctx); + } +} diff --git a/aosp/external/superuser/su/su.h b/aosp/external/superuser/su/su.h new file mode 100644 index 000000000..13b004f5d --- /dev/null +++ b/aosp/external/superuser/su/su.h @@ -0,0 +1,198 @@ +/* +** Copyright 2010, Adam Shanks (@ChainsDD) +** Copyright 2008, Zinx Verituse (@zinxv) +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef SU_h +#define SU_h 1 + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "su" + +#ifndef AID_SHELL +#define AID_SHELL (get_shell_uid()) +#endif + +#ifndef AID_ROOT +#define AID_ROOT 0 +#endif + +#ifndef AID_SYSTEM +#define AID_SYSTEM (get_system_uid()) +#endif + +#ifndef AID_RADIO +#define AID_RADIO (get_radio_uid()) +#endif + +// CyanogenMod-specific behavior +#define CM_ROOT_ACCESS_DISABLED 0 +#define CM_ROOT_ACCESS_APPS_ONLY 1 +#define CM_ROOT_ACCESS_ADB_ONLY 2 +#define CM_ROOT_ACCESS_APPS_AND_ADB 3 + +// DO NOT CHANGE LINE BELOW, java package name will always be the same +#define JAVA_PACKAGE_NAME "com.koushikdutta.superuser" + +// If --rename-manifest-package is used in AAPT, this +// must be changed to correspond to the new APK package name +// See the two Android.mk files for more details. +#ifndef REQUESTOR +#define REQUESTOR JAVA_PACKAGE_NAME +#endif +// This is used if wrapping the fragment classes and activities +// with classes in another package. CM requirement. +#ifndef REQUESTOR_PREFIX +#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME +#endif +#define REQUESTOR_DATA_PATH "/data/data/" +#define REQUESTOR_FILES_PATH REQUESTOR_DATA_PATH REQUESTOR "/files" +#define REQUESTOR_USER_PATH "/data/user/" +#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR + +// there's no guarantee that the db or files are actually created named as such by +// SQLiteOpenHelper, etc. Though that is the behavior as of current. +// it is up to the Android application to symlink as appropriate. +#define REQUESTOR_DATABASE_PATH REQUESTOR "/databases/su.sqlite" +#define REQUESTOR_MULTIUSER_MODE REQUESTOR_FILES_PATH "/multiuser_mode" + +#define DEFAULT_SHELL "/system/bin/sh" + +#define xstr(a) str(a) +#define str(a) #a + +#ifndef VERSION_CODE +#define VERSION_CODE 16 +#endif +#define VERSION xstr(VERSION_CODE) " " REQUESTOR + +#define PROTO_VERSION 1 + +struct su_initiator { + pid_t pid; + unsigned uid; + unsigned user; + char name[64]; + char bin[PATH_MAX]; + char args[4096]; +}; + +struct su_request { + unsigned uid; + char name[64]; + int login; + int keepenv; + char *shell; + char *command; + char **argv; + int argc; + int optind; +}; + +struct su_user_info { + // the user in android userspace (multiuser) + // that invoked this action. + unsigned android_user_id; + // how su behaves with multiuser. see enum below. + int multiuser_mode; + // path to superuser directory. this is populated according + // to the multiuser mode. + // this is used to check uid/gid for protecting socket. + // this is used instead of database, as it is more likely + // to exist. db will not exist if su has never launched. + char base_path[PATH_MAX]; + // path to su database. this is populated according + // to the multiuser mode. + char database_path[PATH_MAX]; +}; + +struct su_context { + struct su_initiator from; + struct su_request to; + struct su_user_info user; + mode_t umask; + char sock_path[PATH_MAX]; +}; + +// multiuser su behavior +typedef enum { + // only owner can su + MULTIUSER_MODE_OWNER_ONLY = 0, + // owner gets a su prompt + MULTIUSER_MODE_OWNER_MANAGED = 1, + // user gets a su prompt + MULTIUSER_MODE_USER = 2, + MULTIUSER_MODE_NONE = 3, +} multiuser_mode_t; + +#define MULTIUSER_VALUE_OWNER_ONLY "owner" +#define MULTIUSER_VALUE_OWNER_MANAGED "managed" +#define MULTIUSER_VALUE_USER "user" +#define MULTIUSER_VALUE_NONE "none" + +typedef enum { + INTERACTIVE = 0, + DENY = 1, + ALLOW = 2, +} policy_t; + +extern void set_identity(unsigned int uid); +extern int send_request(struct su_context *ctx); +extern int send_result(struct su_context *ctx, policy_t policy); + +static inline char *get_command(const struct su_request *to) +{ + if (to->command) + return to->command; + if (to->shell) + return to->shell; + char* ret = to->argv[to->optind]; + if (ret) + return ret; + return DEFAULT_SHELL; +} + +void exec_loge(const char* fmt, ...); +void exec_logw(const char* fmt, ...); +void exec_logd(const char* fmt, ...); + +int run_daemon(); +int connect_daemon(int argc, char *argv[], int ppid); +int su_main(int argc, char *argv[], int need_client); +// for when you give zero fucks about the state of the child process. +// this version of fork understands you don't care about the child. +// deadbeat dad fork. +int fork_zero_fucks(); + +// fallback to using /system/bin/log. +// can't use liblog.so because this is a static binary. +#ifndef LOGE +#define LOGE exec_loge +#endif +#ifndef LOGD +#define LOGD exec_logd +#endif +#ifndef LOGW +#define LOGW exec_logw +#endif + +#include +#include +#define PLOGE(fmt,args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) +#define PLOGEV(fmt,err,args...) LOGE(fmt " failed with %d: %s", ##args, err, strerror(err)) + +#endif diff --git a/aosp/external/superuser/su/utils.c b/aosp/external/superuser/su/utils.c new file mode 100644 index 000000000..efb97a003 --- /dev/null +++ b/aosp/external/superuser/su/utils.c @@ -0,0 +1,129 @@ +/* +** Copyright 2012, The CyanogenMod Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "su.h" + +/* reads a file, making sure it is terminated with \n \0 */ +char* read_file(const char *fn) +{ + struct stat st; + char *data = NULL; + + int fd = open(fn, O_RDONLY); + if (fd < 0) return data; + + if (fstat(fd, &st)) goto oops; + + data = malloc(st.st_size + 2); + if (!data) goto oops; + + if (read(fd, data, st.st_size) != st.st_size) goto oops; + close(fd); + data[st.st_size] = '\n'; + data[st.st_size + 1] = 0; + return data; + +oops: + close(fd); + if (data) free(data); + return NULL; +} + +int get_property(const char *data, char *found, unsigned found_len, const char *searchkey, const char *not_found) +{ + char *key = NULL; + char *value = NULL; + char *eol = NULL; + char *orig_sol = NULL; + char *sol = NULL; + char *tmp = NULL; + if (data == NULL) goto defval; + int matched = 0; + orig_sol = strdup(data); + if (orig_sol == NULL) { + LOGE("No memory!"); + exit(-1); + } + sol = orig_sol; + while((eol = strchr(sol, '\n'))) { + key = sol; + *eol++ = 0; + sol = eol; + + value = strchr(key, '='); + if(value == 0) continue; + *value++ = 0; + + while(isspace(*key)) key++; + if(*key == '#') continue; + tmp = value - 2; + while((tmp > key) && isspace(*tmp)) *tmp-- = 0; + + while(isspace(*value)) value++; + tmp = eol - 2; + while((tmp > value) && isspace(*tmp)) *tmp-- = 0; + + if (strncmp(searchkey, key, strlen(searchkey)) == 0) { + matched = 1; + break; + } + } + + size_t len; + if (matched) { + len = strlen(value); + if (len >= found_len) { + free(orig_sol); + return -1; + } + memcpy(found, value, len + 1); + } else goto defval; + + free(orig_sol); + return len; + +defval: + len = strlen(not_found); + memcpy(found, not_found, len + 1); + free(orig_sol); + return len; +} + +/* + * Fast version of get_property which purpose is to check + * whether the property with given prefix exists. + * + * Assume nobody is stupid enough to put a propery with prefix ro.cm.version + * in his build.prop on a non-CM ROM and comment it out. + */ +int check_property(const char *data, const char *prefix) +{ + if (!data) + return 0; + return strstr(data, prefix) != NULL; +} diff --git a/aosp/external/superuser/su/utils.h b/aosp/external/superuser/su/utils.h new file mode 100644 index 000000000..cdcac7d04 --- /dev/null +++ b/aosp/external/superuser/su/utils.h @@ -0,0 +1,30 @@ +/* +** Copyright 2012, The CyanogenMod Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#ifndef PROPERTY_VALUE_MAX +#define PROPERTY_VALUE_MAX 92 +#endif + +/* reads a file, making sure it is terminated with \n \0 */ +extern char* read_file(const char *fn); + +extern int get_property(const char *data, char *found, unsigned found_len, const char *searchkey, + const char *not_found); +extern int check_property(const char *data, const char *prefix); +#endif -- Gitee From e10b231e99b9fa4a644516cf4def8e6f75bc1790 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Tue, 12 Aug 2025 10:04:51 +0800 Subject: [PATCH 2/3] add origin files --- aosp/bionic/libc/include/paths.h | 65 + .../java/android/app/IActivityManager.aidl | 969 +++ .../android/app/SharedPreferencesHelper.java | 96 + .../java/com/android/internal/os/Zygote.java | 1500 +++++ aosp/frameworks/base/services/core/Android.bp | 315 + .../am/ActivityManagerShellCommand.java | 4510 +++++++++++++ .../am/HwActivityManagerShellCommand.java | 143 + .../com/android/server/am/ProcessList.java | 5824 +++++++++++++++++ .../com/android/server/am/RootManager.java | 78 + 9 files changed, 13500 insertions(+) create mode 100644 aosp/bionic/libc/include/paths.h create mode 100644 aosp/frameworks/base/core/java/android/app/IActivityManager.aidl create mode 100644 aosp/frameworks/base/core/java/android/app/SharedPreferencesHelper.java create mode 100644 aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java create mode 100644 aosp/frameworks/base/services/core/Android.bp create mode 100644 aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java create mode 100644 aosp/frameworks/base/services/core/java/com/android/server/am/HwActivityManagerShellCommand.java create mode 100644 aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java create mode 100644 aosp/frameworks/base/services/core/java/com/android/server/am/RootManager.java diff --git a/aosp/bionic/libc/include/paths.h b/aosp/bionic/libc/include/paths.h new file mode 100644 index 000000000..cfbc5b3f3 --- /dev/null +++ b/aosp/bionic/libc/include/paths.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)paths.h 8.1 (Berkeley) 6/2/93 + */ + +#pragma once + +/** + * @file paths.h + * @brief Default paths. + */ + +#include + +#ifndef _PATH_BSHELL +/** Path to the default system shell. Historically the 'B' was to specify the Bourne shell. */ +#define _PATH_BSHELL "/system/bin/sh" +#endif + +/** Path to the system console. */ +#define _PATH_CONSOLE "/dev/console" + +/** Default shell search path. */ +#define _PATH_DEFPATH "/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin" + +/** Path to the directory containing device files. */ +#define _PATH_DEV "/dev/" + +/** Path to `/dev/null`. */ +#define _PATH_DEVNULL "/dev/null" + +/** Path to the kernel log. */ +#define _PATH_KLOG "/proc/kmsg" + +/** Path to `/proc/mounts` for setmntent(). */ +#define _PATH_MOUNTED "/proc/mounts" + +/** Path to the calling process' tty. */ +#define _PATH_TTY "/dev/tty" diff --git a/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl b/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl new file mode 100644 index 000000000..7a95720c1 --- /dev/null +++ b/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl @@ -0,0 +1,969 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.ApplicationStartInfo; +import android.app.ApplicationErrorReport; +import android.app.ApplicationExitInfo; +import android.app.ContentProviderHolder; +import android.app.GrantedUriPermission; +import android.app.IApplicationStartInfoCompleteListener; +import android.app.IApplicationThread; +import android.app.IActivityController; +import android.app.IAppTask; +import android.app.IForegroundServiceObserver; +import android.app.IInstrumentationWatcher; +import android.app.IProcessObserver; +import android.app.IServiceConnection; +import android.app.IStopUserCallback; +import android.app.ITaskStackListener; +import android.app.IUiAutomationConnection; +import android.app.IUidFrozenStateChangedCallback; +import android.app.IUidObserver; +import android.app.IUserSwitchObserver; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.ProfilerInfo; +import android.app.WaitResult; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; +import android.content.ComponentName; +import android.content.IIntentReceiver; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.ConfigurationInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.ParceledListSlice; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.content.LocusId; +import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; +import android.graphics.Point; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Bundle; +import android.os.Debug; +import android.os.IBinder; +import android.os.IProgressListener; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.RemoteCallback; +import android.os.StrictMode; +import android.os.WorkSource; +import android.service.voice.IVoiceInteractionSession; +import android.view.RemoteAnimationDefinition; +import android.view.RemoteAnimationAdapter; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.os.IResultReceiver; +import com.android.internal.policy.IKeyguardDismissCallback; + +import java.util.List; + +/** + * System private API for talking with the activity manager service. This + * provides calls from the application back to the activity manager. + * + * {@hide} + */ +interface IActivityManager { + // WARNING: when these transactions are updated, check if they are any callers on the native + // side. If so, make sure they are using the correct transaction ids and arguments. + // If a transaction which will also be used on the native side is being inserted, add it to + // below block of transactions. + + // Since these transactions are also called from native code, these must be kept in sync with + // the ones in frameworks/native/libs/binder/include_activitymanager/binder/ActivityManager.h + // =============== Beginning of transactions used on native side as well ====================== + ParcelFileDescriptor openContentUri(in String uriString); + void registerUidObserver(in IUidObserver observer, int which, int cutpoint, + String callingPackage); + void unregisterUidObserver(in IUidObserver observer); + + /** + * Registers a UidObserver with a uid filter. + * + * @param observer The UidObserver implementation to register. + * @param which A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*. + * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this + * threshold in either direction, onUidStateChanged will be called. + * @param callingPackage The name of the calling package. + * @param uids A list of uids to watch. If all uids are to be watched, use + * registerUidObserver instead. + * @throws RemoteException + * @return Returns A binder token identifying the UidObserver registration. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") + IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint, + String callingPackage, in int[] uids); + + /** + * Adds a uid to the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to watch. + * @throws RemoteException + */ + void addUidToObserver(in IBinder observerToken, String callingPackage, int uid); + + /** + * Removes a uid from the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to stop watching. + * @throws RemoteException + */ + void removeUidFromObserver(in IBinder observerToken, String callingPackage, int uid); + + boolean isUidActive(int uid, String callingPackage); + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)") + int getUidProcessState(int uid, in String callingPackage); + @UnsupportedAppUsage + int checkPermission(in String permission, int pid, int uid); + + /** Logs start of an API call to associate with an FGS, used for FGS Type Metrics */ + oneway void logFgsApiBegin(int apiType, int appUid, int appPid); + + /** Logs stop of an API call to associate with an FGS, used for FGS Type Metrics */ + oneway void logFgsApiEnd(int apiType, int appUid, int appPid); + + /** Logs API state change to associate with an FGS, used for FGS Type Metrics */ + oneway void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid); + // =============== End of transactions used on native side as well ============================ + + // Special low-level communication with activity manager. + void handleApplicationCrash(in IBinder app, + in ApplicationErrorReport.ParcelableCrashInfo crashInfo); + /** @deprecated Use {@link #startActivityWithFeature} instead */ + @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#startActivity(android.content.Intent)} instead") + int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent, + in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, + int flags, in ProfilerInfo profilerInfo, in Bundle options); + int startActivityWithFeature(in IApplicationThread caller, in String callingPackage, + in String callingFeatureId, in Intent intent, in String resolvedType, + in IBinder resultTo, in String resultWho, int requestCode, int flags, + in ProfilerInfo profilerInfo, in Bundle options); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void unhandledBack(); + @UnsupportedAppUsage + boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask); + @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter)} instead") + Intent registerReceiver(in IApplicationThread caller, in String callerPackage, + in IIntentReceiver receiver, in IntentFilter filter, + in String requiredPermission, int userId, int flags); + Intent registerReceiverWithFeature(in IApplicationThread caller, in String callerPackage, + in String callingFeatureId, in String receiverId, in IIntentReceiver receiver, + in IntentFilter filter, in String requiredPermission, int userId, int flags); + @UnsupportedAppUsage + void unregisterReceiver(in IIntentReceiver receiver); + /** @deprecated Use {@link #broadcastIntentWithFeature} instead */ + @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#sendBroadcast(android.content.Intent)} instead") + int broadcastIntent(in IApplicationThread caller, in Intent intent, + in String resolvedType, in IIntentReceiver resultTo, int resultCode, + in String resultData, in Bundle map, in String[] requiredPermissions, + int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); + int broadcastIntentWithFeature(in IApplicationThread caller, in String callingFeatureId, + in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, + in String resultData, in Bundle map, in String[] requiredPermissions, in String[] excludePermissions, + in String[] excludePackages, int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); + void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId); + @UnsupportedAppUsage + oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map, + boolean abortBroadcast, int flags); + void attachApplication(in IApplicationThread app, long startSeq); + void finishAttachApplication(long startSeq); + List getTasks(int maxNum); + @UnsupportedAppUsage + void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task, + int flags, in Bundle options); + @UnsupportedAppUsage + int getTaskForActivity(in IBinder token, in boolean onlyRoot); + ContentProviderHolder getContentProvider(in IApplicationThread caller, in String callingPackage, + in String name, int userId, boolean stable); + @UnsupportedAppUsage + void publishContentProviders(in IApplicationThread caller, + in List providers); + boolean refContentProvider(in IBinder connection, int stableDelta, int unstableDelta); + PendingIntent getRunningServiceControlPanel(in ComponentName service); + ComponentName startService(in IApplicationThread caller, in Intent service, + in String resolvedType, boolean requireForeground, in String callingPackage, + in String callingFeatureId, int userId); + @UnsupportedAppUsage + int stopService(in IApplicationThread caller, in Intent service, + in String resolvedType, int userId); + // Currently keeping old bindService because it is on the greylist + @UnsupportedAppUsage + int bindService(in IApplicationThread caller, in IBinder token, in Intent service, + in String resolvedType, in IServiceConnection connection, long flags, + in String callingPackage, int userId); + int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service, + in String resolvedType, in IServiceConnection connection, long flags, + in String instanceName, in String callingPackage, int userId); + void updateServiceGroup(in IServiceConnection connection, int group, int importance); + @UnsupportedAppUsage + boolean unbindService(in IServiceConnection connection); + void publishService(in IBinder token, in Intent intent, in IBinder service); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void setDebugApp(in String packageName, boolean waitForDebugger, boolean persistent); + void setAgentApp(in String packageName, @nullable String agent); + @UnsupportedAppUsage + void setAlwaysFinish(boolean enabled); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean startInstrumentation(in ComponentName className, in String profileFile, + int flags, in Bundle arguments, in IInstrumentationWatcher watcher, + in IUiAutomationConnection connection, int userId, + in String abiOverride); + void addInstrumentationResults(in IApplicationThread target, in Bundle results); + void finishInstrumentation(in IApplicationThread target, int resultCode, + in Bundle results); + /** + * @return A copy of global {@link Configuration}, contains general settings for the entire + * system. Corresponds to the configuration of the default display. + * @throws RemoteException + */ + @UnsupportedAppUsage + Configuration getConfiguration(); + /** + * Updates global configuration and applies changes to the entire system. + * @param values Update values for global configuration. If null is passed it will request the + * Window Manager to compute new config for the default display. + * @throws RemoteException + * @return Returns true if the configuration was updated. + */ + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean updateConfiguration(in Configuration values); + /** + * Updates mcc mnc configuration and applies changes to the entire system. + * + * @param mcc mcc configuration to update. + * @param mnc mnc configuration to update. + * @throws RemoteException; IllegalArgumentException if mcc or mnc is null. + * @return Returns {@code true} if the configuration was updated; + * {@code false} otherwise. + */ + boolean updateMccMncConfiguration(in String mcc, in String mnc); + boolean stopServiceToken(in ComponentName className, in IBinder token, int startId); + @UnsupportedAppUsage + void setProcessLimit(int max); + @UnsupportedAppUsage + int getProcessLimit(); + int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId, + in IBinder callerToken); + int checkContentUriPermissionFull(in Uri uri, int pid, int uid, int mode, int userId); + int[] checkUriPermissions(in List uris, int pid, int uid, int mode, int userId, + in IBinder callerToken); + void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri, + int mode, int userId); + void revokeUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri, + int mode, int userId); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void setActivityController(in IActivityController watcher, boolean imAMonkey); + void showWaitingForDebugger(in IApplicationThread who, boolean waiting); + /* + * This will deliver the specified signal to all the persistent processes. Currently only + * SIGUSR1 is delivered. All others are ignored. + */ + void signalPersistentProcesses(int signal); + + @UnsupportedAppUsage + ParceledListSlice getRecentTasks(int maxNum, int flags, int userId); + @UnsupportedAppUsage + oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res, + in Intent intent); + /** @deprecated Use {@link #getIntentSenderWithFeature} instead */ + @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link PendingIntent#getIntentSender()} instead") + IIntentSender getIntentSender(int type, in String packageName, in IBinder token, + in String resultWho, int requestCode, in Intent[] intents, in String[] resolvedTypes, + int flags, in Bundle options, int userId); + IIntentSender getIntentSenderWithFeature(int type, in String packageName, in String featureId, + in IBinder token, in String resultWho, int requestCode, in Intent[] intents, + in String[] resolvedTypes, int flags, in Bundle options, int userId); + void cancelIntentSender(in IIntentSender sender); + ActivityManager.PendingIntentInfo getInfoForIntentSender(in IIntentSender sender); + /** + This method used to be called registerIntentSenderCancelListener(), was void, and + would call `receiver` if the PI has already been canceled. + Now it returns false if the PI is cancelled, without calling `receiver`. + The method was renamed to catch calls to the original method. + */ + boolean registerIntentSenderCancelListenerEx(in IIntentSender sender, + in IResultReceiver receiver); + void unregisterIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver); + void enterSafeMode(); + void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid, + in String sourcePkg, in String tag); + oneway void removeContentProvider(in IBinder connection, boolean stable); + @UnsupportedAppUsage + void setRequestedOrientation(in IBinder token, int requestedOrientation); + void unbindFinished(in IBinder token, in Intent service, boolean doRebind); + @UnsupportedAppUsage + void setProcessImportant(in IBinder token, int pid, boolean isForeground, String reason); + void setServiceForeground(in ComponentName className, in IBinder token, + int id, in Notification notification, int flags, int foregroundServiceType); + int getForegroundServiceType(in ComponentName className, in IBinder token); + @UnsupportedAppUsage + boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot); + @UnsupportedAppUsage + void getMemoryInfo(out ActivityManager.MemoryInfo outInfo); + List getProcessesInErrorState(); + boolean clearApplicationUserData(in String packageName, boolean keepState, + in IPackageDataObserver observer, int userId); + void stopAppForUser(in String packageName, int userId); + /** Returns {@code false} if the callback could not be registered, {@true} otherwise. */ + boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback); + @UnsupportedAppUsage + void forceStopPackage(in String packageName, int userId); + void forceStopPackageEvenWhenStopping(in String packageName, int userId); + boolean killPids(in int[] pids, in String reason, boolean secure); + @UnsupportedAppUsage + List getServices(int maxNum, int flags); + // Retrieve running application processes in the system + @UnsupportedAppUsage + List getRunningAppProcesses(); + IBinder peekService(in Intent service, in String resolvedType, in String callingPackage); + // Turn on/off profiling in a particular process. + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean profileControl(in String process, int userId, boolean start, + in ProfilerInfo profilerInfo, int profileType); + @UnsupportedAppUsage + boolean shutdown(int timeout); + @UnsupportedAppUsage + void stopAppSwitches(); + @UnsupportedAppUsage + void resumeAppSwitches(); + boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId, + int backupDestination); + void backupAgentCreated(in String packageName, in IBinder agent, int userId); + void unbindBackupAgent(in ApplicationInfo appInfo); + int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, + boolean requireFull, in String name, in String callerPackage); + void addPackageDependency(in String packageName); + void killApplication(in String pkg, int appId, int userId, in String reason, + int exitInfoReason); + @UnsupportedAppUsage + void closeSystemDialogs(in String reason); + @UnsupportedAppUsage + Debug.MemoryInfo[] getProcessMemoryInfo(in int[] pids); + void killApplicationProcess(in String processName, int uid); + // Special low-level communication with activity manager. + boolean handleApplicationWtf(in IBinder app, in String tag, boolean system, + in ApplicationErrorReport.ParcelableCrashInfo crashInfo, int immediateCallerPid); + @UnsupportedAppUsage + void killBackgroundProcesses(in String packageName, int userId); + boolean isUserAMonkey(); + // Retrieve info of applications installed on external media that are currently + // running. + List getRunningExternalApplications(); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void finishHeavyWeightApp(); + // A StrictMode violation to be handled. + @UnsupportedAppUsage + void handleApplicationStrictModeViolation(in IBinder app, int penaltyMask, + in StrictMode.ViolationInfo crashInfo); + void registerStrictModeCallback(in IBinder binder); + boolean isTopActivityImmersive(); + void crashApplicationWithType(int uid, int initialPid, in String packageName, int userId, + in String message, boolean force, int exceptionTypeId); + void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName, + int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras); + oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback); + // Cause the specified process to dump the specified heap. + boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo, + boolean runGc, in String path, in ParcelFileDescriptor fd, + in RemoteCallback finishCallback); + @UnsupportedAppUsage + boolean isUserRunning(int userid, int flags); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void setPackageScreenCompatMode(in String packageName, int mode); + @UnsupportedAppUsage + boolean switchUser(int userid); + String getSwitchingFromUserMessage(); + String getSwitchingToUserMessage(); + @UnsupportedAppUsage + void setStopUserOnSwitch(int value); + boolean removeTask(int taskId); + @UnsupportedAppUsage + void registerProcessObserver(in IProcessObserver observer); + @UnsupportedAppUsage + void unregisterProcessObserver(in IProcessObserver observer); + boolean isIntentSenderTargetedToPackage(in IIntentSender sender); + @UnsupportedAppUsage + void updatePersistentConfiguration(in Configuration values); + void updatePersistentConfigurationWithAttribution(in Configuration values, + String callingPackageName, String callingAttributionTag); + @UnsupportedAppUsage + long[] getProcessPss(in int[] pids); + void showBootMessage(in CharSequence msg, boolean always); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void killAllBackgroundProcesses(); + ContentProviderHolder getContentProviderExternal(in String name, int userId, + in IBinder token, String tag); + /** @deprecated - Use {@link #removeContentProviderExternalAsUser} which takes a user ID. */ + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void removeContentProviderExternal(in String name, in IBinder token); + void removeContentProviderExternalAsUser(in String name, in IBinder token, int userId); + // Get memory information about the calling process. + void getMyMemoryState(out ActivityManager.RunningAppProcessInfo outInfo); + boolean killProcessesBelowForeground(in String reason); + @UnsupportedAppUsage + UserInfo getCurrentUser(); + int getCurrentUserId(); + // This is not public because you need to be very careful in how you + // manage your activity to make sure it is always the uid you expect. + @UnsupportedAppUsage + int getLaunchedFromUid(in IBinder activityToken); + @UnsupportedAppUsage + void unstableProviderDied(in IBinder connection); + @UnsupportedAppUsage + boolean isIntentSenderAnActivity(in IIntentSender sender); + /** @deprecated Use {@link startActivityAsUserWithFeature} instead */ + @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@code android.content.Context#createContextAsUser(android.os.UserHandle, int)} and {@link android.content.Context#startActivity(android.content.Intent)} instead") + int startActivityAsUser(in IApplicationThread caller, in String callingPackage, + in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, + int requestCode, int flags, in ProfilerInfo profilerInfo, + in Bundle options, int userId); + int startActivityAsUserWithFeature(in IApplicationThread caller, in String callingPackage, + in String callingFeatureId, in Intent intent, in String resolvedType, + in IBinder resultTo, in String resultWho, int requestCode, int flags, + in ProfilerInfo profilerInfo, in Bundle options, int userId); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + int stopUser(int userid, boolean force, in IStopUserCallback callback); + /** + * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)} + * for details. + */ + int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback); + + @UnsupportedAppUsage + void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name); + void unregisterUserSwitchObserver(in IUserSwitchObserver observer); + int[] getRunningUserIds(); + + // Request a heap dump for the system server. + void requestSystemServerHeapDump(); + + void requestBugReport(int bugreportType); + void requestBugReportWithDescription(in @nullable String shareTitle, + in @nullable String shareDescription, int bugreportType); + + /** + * Takes a telephony bug report and notifies the user with the title and description + * that are passed to this API as parameters + * + * @param shareTitle should be a valid legible string less than 50 chars long + * @param shareDescription should be less than 150 chars long + * + * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the + * paremeters cannot be encoding to an UTF-8 charset. + */ + void requestTelephonyBugReport(in String shareTitle, in String shareDescription); + + /** + * This method is only used by Wifi. + * + * Takes a minimal bugreport of Wifi-related state. + * + * @param shareTitle should be a valid legible string less than 50 chars long + * @param shareDescription should be less than 150 chars long + * + * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the + * parameters cannot be encoding to an UTF-8 charset. + */ + void requestWifiBugReport(in String shareTitle, in String shareDescription); + void requestInteractiveBugReportWithDescription(in String shareTitle, + in String shareDescription); + + void requestInteractiveBugReport(); + void requestFullBugReport(); + void requestRemoteBugReport(long nonce); + boolean launchBugReportHandlerApp(); + List getBugreportWhitelistedPackages(); + + @UnsupportedAppUsage + Intent getIntentForIntentSender(in IIntentSender sender); + // This is not public because you need to be very careful in how you + // manage your activity to make sure it is always the uid you expect. + @UnsupportedAppUsage + String getLaunchedFromPackage(in IBinder activityToken); + void killUid(int appId, int userId, in String reason); + void setUserIsMonkey(boolean monkey); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void hang(in IBinder who, boolean allowRestart); + + List getAllRootTaskInfos(); + void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop); + void setFocusedRootTask(int taskId); + ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo(); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void restart(); + void performIdleMaintenance(); + void appNotRespondingViaProvider(in IBinder connection); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + Rect getTaskBounds(int taskId); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean setProcessMemoryTrimLevel(in String process, int userId, int level); + + + // Start of L transactions + String getTagForIntentSender(in IIntentSender sender, in String prefix); + + /** + * Starts a user in the background (i.e., while another user is running in the foreground). + * + * Notice that a background user is "invisible" and cannot launch activities. Starting on + * Android U, all users started with this method are invisible, even profiles (prior to Android + * U, profiles started with this method would be visible if its parent was the current user) - + * if you want to start a profile visible, you should call {@code startProfile()} instead. + */ + @UnsupportedAppUsage + boolean startUserInBackground(int userid); + + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean isInLockTaskMode(); + @UnsupportedAppUsage + int startActivityFromRecents(int taskId, in Bundle options); + @UnsupportedAppUsage + void startSystemLockTaskMode(int taskId); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean isTopOfTask(in IBinder token); + void bootAnimationComplete(); + + /** + * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify when color + * palette is ready. + * + * @param userId The ID of the user where ThemeOverlayController is ready. + * + * @throws RemoteException + */ + void setThemeOverlayReady(int userId); + + @UnsupportedAppUsage + void registerTaskStackListener(in ITaskStackListener listener); + void unregisterTaskStackListener(in ITaskStackListener listener); + void notifyCleartextNetwork(int uid, in byte[] firstPacket); + @UnsupportedAppUsage + void setTaskResizeable(int taskId, int resizeableMode); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void resizeTask(int taskId, in Rect bounds, int resizeMode); + @UnsupportedAppUsage + int getLockTaskModeState(); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void setDumpHeapDebugLimit(in String processName, int uid, long maxMemSize, + in String reportPackage); + void dumpHeapFinished(in String path); + void updateLockTaskPackages(int userId, in String[] packages); + void noteAlarmStart(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); + void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); + @UnsupportedAppUsage + int getPackageProcessState(in String packageName, in String callingPackage); + + // Start of N transactions + // Start Binder transaction tracking for all applications. + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean startBinderTracking(); + // Stop Binder transaction tracking for all applications and dump trace data to the given file + // descriptor. + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd); + + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void suppressResizeConfigChanges(boolean suppress); + + /** + * @deprecated Use {@link #unlockUser2(int, IProgressListener)} instead, since the token and + * secret arguments no longer do anything. This method still exists only because it is marked + * with {@code @UnsupportedAppUsage}, so it might not be safe to remove it or change its + * signature. + */ + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + boolean unlockUser(int userid, in byte[] token, in byte[] secret, + in IProgressListener listener); + + /** + * Tries to unlock the given user. + *

+ * This will succeed only if the user's CE storage key is already unlocked or if the user + * doesn't have a lockscreen credential set. + * + * @param userId The ID of the user to unlock. + * @param listener An optional progress listener. + * + * @return true if the user was successfully unlocked, otherwise false. + */ + boolean unlockUser2(int userId, in IProgressListener listener); + + void killPackageDependents(in String packageName, int userId); + void makePackageIdle(String packageName, int userId); + void setDeterministicUidIdle(boolean deterministic); + int getMemoryTrimLevel(); + boolean isVrModePackageEnabled(in ComponentName packageName); + void notifyLockedProfile(int userId); + void startConfirmDeviceCredentialIntent(in Intent intent, in Bundle options); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void sendIdleJobTrigger(); + int sendIntentSender(in IApplicationThread caller, in IIntentSender target, + in IBinder whitelistToken, int code, + in Intent intent, in String resolvedType, in IIntentReceiver finishedReceiver, + in String requiredPermission, in Bundle options); + boolean isBackgroundRestricted(in String packageName); + + // Start of N MR1 transactions + void setRenderThread(int tid); + /** + * Lets activity manager know whether the calling process is currently showing "top-level" UI + * that is not an activity, i.e. windows on the screen the user is currently interacting with. + * + *

This flag can only be set for persistent processes. + * + * @param hasTopUi Whether the calling process has "top-level" UI. + */ + void setHasTopUi(boolean hasTopUi); + + // Start of O transactions + /** Cancels the window transitions for the given task. */ + @UnsupportedAppUsage + void cancelTaskWindowTransition(int taskId); + void scheduleApplicationInfoChanged(in List packageNames, int userId); + void setPersistentVrThread(int tid); + + void waitForNetworkStateUpdate(long procStateSeq); + /** + * Add a bare uid to the background restrictions whitelist. Only the system uid may call this. + */ + void backgroundAllowlistUid(int uid); + + // Start of P transactions + /** + * Similar to {@link #startUserInBackground(int userId), but with a listener to report + * user unlock progress. + */ + boolean startUserInBackgroundWithListener(int userid, IProgressListener unlockProgressListener); + + /** + * Method for the shell UID to start deletating its permission identity to an + * active instrumenation. The shell can delegate permissions only to one active + * instrumentation at a time. An active instrumentation is one running and + * started from the shell. + */ + void startDelegateShellPermissionIdentity(int uid, in String[] permissions); + + /** + * Method for the shell UID to stop deletating its permission identity to an + * active instrumenation. An active instrumentation is one running and + * started from the shell. + */ + void stopDelegateShellPermissionIdentity(); + + /** + * Method for the shell UID to get currently adopted permissions for an active instrumentation. + * An active instrumentation is one running and started from the shell. + */ + List getDelegatedShellPermissions(); + + /** Returns a file descriptor that'll be closed when the system server process dies. */ + ParcelFileDescriptor getLifeMonitor(); + + /** + * Start user, if it us not already running, and bring it to foreground. + * unlockProgressListener can be null if monitoring progress is not necessary. + */ + boolean startUserInForegroundWithListener(int userid, IProgressListener unlockProgressListener); + + /** + * Method for the app to tell system that it's wedged and would like to trigger an ANR. + */ + void appNotResponding(String reason); + + /** + * Return a list of {@link ApplicationStartInfo} records. + * + *

Note: System stores historical information in a ring buffer, older + * records would be overwritten by newer records.

+ * + * @param packageName Optional, an empty value means match all packages belonging to the + * caller's UID. If this package belongs to another UID, you must hold + * {@link android.Manifest.permission#DUMP} in order to retrieve it. + * @param maxNum Optional, the maximum number of results should be returned; A value of 0 + * means to ignore this parameter and return all matching records + * @param userId The userId in the multi-user environment. + * + * @return a list of {@link ApplicationStartInfo} records with the matching criteria, sorted in + * the order from most recent to least recent. + */ + ParceledListSlice getHistoricalProcessStartReasons(String packageName, + int maxNum, int userId); + + + /** + * Sets a callback for {@link ApplicationStartInfo} upon completion of collecting startup data. + * + *

Note: completion of startup is no guaranteed and as such this callback may not occur.

+ * + * @param listener A listener to for the callback upon completion of startup data collection. + * @param userId The userId in the multi-user environment. + */ + void addApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, + int userId); + + + /** + * Removes callback for {@link ApplicationStartInfo} upon completion of collecting startup data. + * + * @param userId The userId in the multi-user environment. + */ + void removeApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, + int userId); + + + /** + * Adds a timestamp of the moment called to the calling apps most recent + * {@link ApplicationStartInfo}. + * + * + * @param key Unique key for timestamp. + * @param timestampNs Clock monotonic time in nanoseconds of event to be + * recorded. + * @param userId The userId in the multi-user environment. + */ + void addStartInfoTimestamp(int key, long timestampNs, int userId); + + /** + * Return a list of {@link ApplicationExitInfo} records. + * + *

Note: System stores these historical information in a ring buffer, older + * records would be overwritten by newer records.

+ * + *

Note: In the case that this application bound to an external service with + * flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}, the process of that external + * service will be included in this package's exit info.

+ * + * @param packageName Optional, an empty value means match all packages belonging to the + * caller's UID. If this package belongs to another UID, you must hold + * {@link android.Manifest.permission#DUMP} in order to retrieve it. + * @param pid Optional, it could be a process ID that used to belong to this package but + * died later; A value of 0 means to ignore this parameter and return all + * matching records. + * @param maxNum Optional, the maximum number of results should be returned; A value of 0 + * means to ignore this parameter and return all matching records + * @param userId The userId in the multi-user environment. + * + * @return a list of {@link ApplicationExitInfo} records with the matching criteria, sorted in + * the order from most recent to least recent. + */ + ParceledListSlice getHistoricalProcessExitReasons(String packageName, + int pid, int maxNum, int userId); + + /* + * Kill the given PIDs, but the killing will be delayed until the device is idle + * and the given process is imperceptible. + */ + void killProcessesWhenImperceptible(in int[] pids, String reason); + + /** + * Set locus context for a given activity. + * @param activity + * @param locusId a unique, stable id that identifies this activity instance from others. + * @param appToken ActivityRecord's appToken. + */ + void setActivityLocusContext(in ComponentName activity, in LocusId locusId, + in IBinder appToken); + + /** + * Set custom state data for this process. It will be included in the record of + * {@link ApplicationExitInfo} on the death of the current calling process; the new process + * of the app can retrieve this state data by calling + * {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by + * {@link #getHistoricalProcessExitReasons}. + * + *

This would be useful for the calling app to save its stateful data: if it's + * killed later for any reason, the new process of the app can know what the + * previous process of the app was doing. For instance, you could use this to encode + * the current level in a game, or a set of features/experiments that were enabled. Later you + * could analyze under what circumstances the app tends to crash or use too much memory. + * However, it's not suggested to rely on this to restore the applications previous UI state + * or so, it's only meant for analyzing application healthy status.

+ * + *

System might decide to throttle the calls to this API; so call this API in a reasonable + * manner, excessive calls to this API could result a {@link java.lang.RuntimeException}. + *

+ * + * @param state The customized state data + */ + void setProcessStateSummary(in byte[] state); + + /** + * Return whether the app freezer is supported (true) or not (false) by this system. + */ + boolean isAppFreezerSupported(); + + /** + * Return whether the app freezer is enabled (true) or not (false) by this system. + */ + boolean isAppFreezerEnabled(); + + /** + * Kills uid with the reason of permission change. + */ + void killUidForPermissionChange(int appId, int userId, String reason); + + /** + * Resets the state of the {@link com.android.server.am.AppErrors} instance. + * This is intended for testing within the CTS only and is protected by + * android.permission.RESET_APP_ERRORS. + */ + void resetAppErrors(); + + /** + * Control the app freezer state. Returns true in case of success, false if the operation + * didn't succeed (for example, when the app freezer isn't supported). + * Handling the freezer state via this method is reentrant, that is it can be + * disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to + * enable match, the freezer is re-enabled at last enable only. + * @param enable set it to true to enable the app freezer, false to disable it. + */ + boolean enableAppFreezer(in boolean enable); + + /** + * Suppress or reenable the rate limit on foreground service notification deferral. + * This is for use within CTS and is protected by android.permission.WRITE_DEVICE_CONFIG. + * + * @param enable false to suppress rate-limit policy; true to reenable it. + */ + boolean enableFgsNotificationRateLimit(in boolean enable); + + /** + * Holds the AM lock for the specified amount of milliseconds. + * This is intended for use by the tests that need to imitate lock contention. + * The token should be obtained by + * {@link android.content.pm.PackageManager#getHoldLockToken()}. + */ + void holdLock(in IBinder token, in int durationMs); + + /** + * Starts a profile. + * @param userId the user id of the profile. + * @return true if the profile has been successfully started or if the profile is already + * running, false if profile failed to start. + * @throws IllegalArgumentException if the user is not a profile. + */ + boolean startProfile(int userId); + + /** + * Stops a profile. + * @param userId the user id of the profile. + * @return true if the profile has been successfully stopped or is already stopped. Otherwise + * the exceptions listed below are thrown. + * @throws IllegalArgumentException if the user is not a profile. + */ + boolean stopProfile(int userId); + + /** Called by PendingIntent.queryIntentComponents() */ + ParceledListSlice queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags); + + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)") + int getUidProcessCapabilities(int uid, in String callingPackage); + + /** Blocks until all broadcast queues become idle. */ + void waitForBroadcastIdle(); + void waitForBroadcastBarrier(); + + /** Delays delivering broadcasts to the specified package. */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)") + void forceDelayBroadcastDelivery(in String targetPackage, long delayedDurationMs); + + /** Checks if the modern broadcast queue is enabled. */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)") + boolean isModernBroadcastQueueEnabled(); + + /** Checks if the process represented by the given pid is frozen. */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)") + boolean isProcessFrozen(int pid); + + /** + * @return The reason code of whether or not the given UID should be exempted from background + * restrictions here. + * + *

+ * Note: Call it with caution as it'll try to acquire locks in other services. + *

+ */ + int getBackgroundRestrictionExemptionReason(int uid); + + // Start (?) of T transactions + /** + * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener)}, + * but setting the user as the visible user of that display (i.e., allowing the user and its + * running profiles to launch activities on that display). + * + *

Typically used only by automotive builds when the vehicle has multiple displays. + */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)") + boolean startUserInBackgroundVisibleOnDisplay(int userid, int displayId, IProgressListener unlockProgressListener); + + /** + * Similar to {@link #startProfile(int userId)}, but with a listener to report user unlock + * progress. + */ + boolean startProfileWithListener(int userid, IProgressListener unlockProgressListener); + + int restartUserInBackground(int userId, int userStartMode); + + /** + * Gets the ids of displays that can be used on {@link #startUserInBackgroundVisibleOnDisplay(int userId, int displayId)}. + * + *

Typically used only by automotive builds when the vehicle has multiple displays. + */ + @nullable int[] getDisplayIdsForStartingVisibleBackgroundUsers(); + + /** Returns if the service is a short-service is still "alive" and past the timeout. */ + boolean shouldServiceTimeOut(in ComponentName className, in IBinder token); + /** Returns if the service has a time-limit restricted type and is past the time limit. */ + boolean hasServiceTimeLimitExceeded(in ComponentName className, in IBinder token); + + void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") + void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") + int[] getUidFrozenState(in int[] uids); + + int checkPermissionForDevice(in String permission, int pid, int uid, int deviceId); + + /** + * Notify AMS about binder transactions to frozen apps. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err); + int getBindingUidProcessState(int uid, in String callingPackage); +} diff --git a/aosp/frameworks/base/core/java/android/app/SharedPreferencesHelper.java b/aosp/frameworks/base/core/java/android/app/SharedPreferencesHelper.java new file mode 100644 index 000000000..feedab35e --- /dev/null +++ b/aosp/frameworks/base/core/java/android/app/SharedPreferencesHelper.java @@ -0,0 +1,96 @@ +/* ****************************************************************************** + * Copyright Notice: + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. ALL Rights Reserved. + * + * Warning: This computer software sourcecode is protected by copyright law + * and international treaties. Unauthorized reproduction or distribution + * of this sourcecode, or any portion of it, may result in severe civil and + * criminal penalties, and will be prosecuted to the maximum extent + * possible under the law. + * ******************************************************************************* + */ + +package android.app; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +/** + * {@hide} + */ +public class SharedPreferencesHelper { + + private SharedPreferencesImpl mPreferences; + private File mFile; + + public SharedPreferencesHelper(File file) { + mFile = file; + mPreferences = new SharedPreferencesImpl(mFile, Context.MODE_PRIVATE); + } + + public SharedPreferences getSharedPreferences() { + return mPreferences; + } + + public void changeMode(int mode) { + if (mFile.exists()) { + ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mode, 0); + } + } + + public String getString(String key, String defValue) { + return mPreferences.getString(key, defValue); + } + + public boolean putString(String key, String value) { + SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(key, value); + return editor.commit(); + } + + public boolean putString(String[] keys, String value) { + SharedPreferences.Editor editor = mPreferences.edit(); + for (String key : keys) { + editor.putString(key, value); + } + return editor.commit(); + } + + public int getInt(String key, int defValue) { + return mPreferences.getInt(key, defValue); + } + + public boolean putInt(String key, int value) { + SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(key, value); + return editor.commit(); + } + + public boolean putInt(String[] keys, int value) { + SharedPreferences.Editor editor = mPreferences.edit(); + for (String key : keys) { + editor.putInt(key, value); + } + return editor.commit(); + } + + public boolean remove(String key) { + SharedPreferences.Editor editor = mPreferences.edit(); + editor.remove(key); + return editor.commit(); + } + + public Set getStringSet(String key, Set set) { + return mPreferences.getStringSet(key, set); + } + + public boolean putStringSet(String key, Set set) { + SharedPreferences.Editor editor = mPreferences.edit(); + editor.putStringSet(key, new HashSet<>(set)); + return editor.commit(); + } +} diff --git a/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java b/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java new file mode 100644 index 000000000..cab84bb01 --- /dev/null +++ b/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java @@ -0,0 +1,1500 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static android.system.OsConstants.O_CLOEXEC; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.ProcessInfo; +import android.net.Credentials; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.Build; +import android.os.FactoryTest; +import android.os.IVold; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.Trace; +import android.provider.DeviceConfig; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import com.android.internal.compat.IPlatformCompat; +import com.android.internal.net.NetworkUtilsInternal; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; +import dalvik.system.ZygoteHooks; + +import libcore.io.IoUtils; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; + +/** @hide */ +public final class Zygote { + /* + * Bit values for "runtimeFlags" argument. The definitions are duplicated + * in the native code. + */ + + /** enable debugging over JDWP */ + public static final int DEBUG_ENABLE_JDWP = 1; + /** enable JNI checks */ + public static final int DEBUG_ENABLE_CHECKJNI = 1 << 1; + /** enable Java programming language "assert" statements */ + public static final int DEBUG_ENABLE_ASSERT = 1 << 2; + /** disable the AOT compiler and JIT */ + public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3; + /** Enable logging of third-party JNI activity. */ + public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; + /** Force generation of native debugging information. */ + public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5; + /** Always use JIT-ed code. */ + public static final int DEBUG_ALWAYS_JIT = 1 << 6; + /** Make the code native debuggable by turning off some optimizations. */ + public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7; + /** Make the code Java debuggable by turning off some optimizations. */ + public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8; + + /** Turn off the verifier. */ + public static final int DISABLE_VERIFIER = 1 << 9; + /** Only use oat files located in /system. Otherwise use dex/jar/apk . */ + public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10; + /** Force generation of native debugging information for backtraces. */ + public static final int DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11; + /** + * Hidden API access restrictions. This is a mask for bits representing the API enforcement + * policy, defined by {@code @ApplicationInfo.HiddenApiEnforcementPolicy}. + */ + public static final int API_ENFORCEMENT_POLICY_MASK = (1 << 12) | (1 << 13); + /** + * Bit shift for use with {@link #API_ENFORCEMENT_POLICY_MASK}. + * + * (flags & API_ENFORCEMENT_POLICY_MASK) >> API_ENFORCEMENT_POLICY_SHIFT gives + * {@link ApplicationInfo.HiddenApiEnforcementPolicy} values. + */ + public static final int API_ENFORCEMENT_POLICY_SHIFT = + Integer.numberOfTrailingZeros(API_ENFORCEMENT_POLICY_MASK); + /** + * Enable system server ART profiling. + */ + public static final int PROFILE_SYSTEM_SERVER = 1 << 14; + + /** + * Enable profiling from shell. + */ + public static final int PROFILE_FROM_SHELL = 1 << 15; + + /* + * Enable using the ART app image startup cache + */ + public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16; + + /** + * When set, application specified signal handlers are not chained (i.e, ignored) + * by the runtime. + * + * Used for debugging only. Usage: set debug.ignoreappsignalhandler to 1. + */ + public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17; + + /** + * Disable runtime access to {@link android.annotation.TestApi} annotated members. + * + *

This only takes effect if Hidden API access restrictions are enabled as well. + */ + public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18; + + public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20); + + public static final int MEMORY_TAG_LEVEL_NONE = 0; + + /** + * Enable pointer tagging in this process. + * Tags are checked during memory deallocation, but not on access. + * TBI stands for Top-Byte-Ignore, an ARM CPU feature. + * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging} + */ + public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19; + + /** + * Enable asynchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19; + + /** + * Enable synchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19; + + /** + * A two-bit field for GWP-ASan level of this process. See the possible values below. + */ + public static final int GWP_ASAN_LEVEL_MASK = (1 << 21) | (1 << 22); + + /** + * Disable GWP-ASan in this process. + * GWP-ASan is a low-overhead memory bug detector using guard pages on a small + * subset of heap allocations. + */ + public static final int GWP_ASAN_LEVEL_NEVER = 0 << 21; + + /** + * Enable GWP-ASan in this process with a small sampling rate. + * With approx. 1% chance GWP-ASan will be activated and apply its protection + * to a small subset of heap allocations. + * Otherwise (~99% chance) this process is unaffected. + */ + public static final int GWP_ASAN_LEVEL_LOTTERY = 1 << 21; + + /** + * Always enable GWP-ASan in this process. + * GWP-ASan is activated unconditionally (but still, only a small subset of + * allocations is protected). + */ + public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21; + + /** + * GWP-ASan's `gwpAsanMode` manifest flag was unspecified. Currently, this + * means GWP_ASAN_LEVEL_LOTTERY for system apps, and GWP_ASAN_LEVEL_NONE for + * non-system apps. + */ + public static final int GWP_ASAN_LEVEL_DEFAULT = 3 << 21; + + /** Enable automatic zero-initialization of native heap memory allocations. */ + public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23; + + /** + * Enable profiling from system services. This loads profiling related plugins in ART. + */ + public static final int PROFILEABLE = 1 << 24; + + /** + * Enable ptrace. This is enabled on eng, if the app is debuggable, or if + * the persist.debug.ptrace.enabled property is set. + */ + public static final int DEBUG_ENABLE_PTRACE = 1 << 25; + + /** No external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; + /** Default external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT; + /** + * Mount mode for package installers which should give them access to + * all obb dirs in addition to their package sandboxes + */ + public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER; + /** The lower file system should be bind mounted directly on external storage */ + public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH; + + /** Use the regular scoped storage filesystem, but Android/ should be writable. + * Used to support the applications hosting DownloadManager and the MTP server. + */ + public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE; + + /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */ + static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8; + + /** Make the new process have top application priority. */ + public static final String START_AS_TOP_APP_ARG = "--is-top-app"; + + /** List of packages with the same uid, and its app data info: volume uuid and inode. */ + public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map"; + + /** List of allowlisted packages and its app data info: volume uuid and inode. */ + public static final String ALLOWLISTED_DATA_INFO_MAP = "--allowlisted-data-info-map"; + + /** Bind mount app storage dirs to lower fs not via fuse */ + public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs"; + + /** Bind mount app storage dirs to lower fs not via fuse */ + public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs"; + + /** Bind the system properties to an alternate set, for appcompat reasons */ + public static final String BIND_MOUNT_SYSPROP_OVERRIDES = "--bind-mount-sysprop-overrides"; + + /** + * An extraArg passed when a zygote process is forking a child-zygote, specifying a name + * in the abstract socket namespace. This socket name is what the new child zygote + * should listen for connections on. + */ + public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket="; + + /** + * An extraArg passed when a zygote process is forking a child-zygote, specifying the + * requested ABI for the child Zygote. + */ + public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list="; + + /** + * An extraArg passed when a zygote process is forking a child-zygote, specifying the + * start of the UID range the children of the Zygote may setuid()/setgid() to. This + * will be enforced with a seccomp filter. + */ + public static final String CHILD_ZYGOTE_UID_RANGE_START = "--uid-range-start="; + + /** + * An extraArg passed when a zygote process is forking a child-zygote, specifying the + * end of the UID range the children of the Zygote may setuid()/setgid() to. This + * will be enforced with a seccomp filter. + */ + public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end="; + + private static final String TAG = "Zygote"; + + /** Prefix prepended to socket names created by init */ + private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; + + /** + * The duration to wait before re-checking Zygote related system properties. + * + * One minute in milliseconds. + */ + public static final long PROPERTY_CHECK_INTERVAL = 60000; + + /** + * @hide for internal use only + */ + public static final int SOCKET_BUFFER_SIZE = 256; + + /** + * @hide for internal use only + */ + private static final int PRIORITY_MAX = -20; + + /** a prototype instance for a future List.toArray() */ + static final int[][] INT_ARRAY_2D = new int[0][0]; + + /** + * @hide for internal use only. + */ + public static final String PRIMARY_SOCKET_NAME = "zygote"; + + /** + * @hide for internal use only. + */ + public static final String SECONDARY_SOCKET_NAME = "zygote_secondary"; + + /** + * @hide for internal use only + */ + public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary"; + + /** + * @hide for internal use only + */ + public static final String USAP_POOL_SECONDARY_SOCKET_NAME = "usap_pool_secondary"; + + private Zygote() {} + + private static boolean containsInetGid(int[] gids) { + for (int i = 0; i < gids.length; i++) { + if (gids[i] == android.os.Process.INET_GID) return true; + } + return false; + } + + /** + * Forks a new VM instance. The current VM must have been started + * with the -Xzygote flag. NOTE: new instance keeps all + * root capabilities. The new process is expected to call capset(). + * + * @param uid the UNIX uid that the new process should setuid() to after + * fork()ing and and before spawning any threads. + * @param gid the UNIX gid that the new process should setgid() to after + * fork()ing and and before spawning any threads. + * @param gids null-ok; a list of UNIX gids that the new process should + * setgroups() to after fork and before spawning any threads. + * @param runtimeFlags bit flags that enable ART features. + * @param rlimits null-ok an array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param seInfo null-ok a string specifying SELinux information for + * the new process. + * @param niceName null-ok a string specifying the process name. + * @param fdsToClose an array of ints, holding one or more POSIX + * file descriptor numbers that are to be closed by the child + * (and replaced by /dev/null) after forking. An integer value + * of -1 in any entry in the array means "ignore this one". + * @param fdsToIgnore null-ok an array of ints, either null or holding + * one or more POSIX file descriptor numbers that are to be ignored + * in the file descriptor table check. + * @param startChildZygote if true, the new child process will itself be a + * new zygote process. + * @param instructionSet null-ok the instruction set to use. + * @param appDataDir null-ok the data directory of the app. + * @param isTopApp true if the process is for top (high priority) application. + * @param pkgDataInfoList A list that stores related packages and its app data + * info: volume uuid and inode. + * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. + * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. + * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. + * @param bindMountSyspropOverrides True if the zygote needs to mount the override system + * properties + * + * @return 0 if this is the child, pid of the child + * if this is the parent, or -1 on error. + */ + static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, + boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs, + boolean bindMountSyspropOverrides) { + ZygoteHooks.preFork(); + + int pid = nativeForkAndSpecialize( + uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, + pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, + bindMountAppStorageDirs, bindMountSyspropOverrides); + if (pid == 0) { + // Note that this event ends at the end of handleChildProc, + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + // If no GIDs were specified, don't make any permissions changes based on groups. + if (gids != null && gids.length > 0) { + NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); + } + } + + // Set the Java Language thread priority to the default value for new apps. + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + + ZygoteHooks.postForkCommon(); + return pid; + } + + private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, + String appDataDir, boolean isTopApp, String[] pkgDataInfoList, + String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, + boolean bindMountAppStorageDirs, boolean bindMountSyspropOverrides); + + /** + * Specialize an unspecialized app process. The current VM must have been started + * with the -Xzygote flag. + * + * @param uid The UNIX uid that the new process should setuid() to before spawning any threads + * @param gid The UNIX gid that the new process should setgid() to before spawning any threads + * @param gids null-ok; A list of UNIX gids that the new process should + * setgroups() to before spawning any threads + * @param runtimeFlags Bit flags that enable ART features + * @param rlimits null-ok An array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param seInfo null-ok A string specifying SELinux information for + * the new process. + * @param niceName null-ok A string specifying the process name. + * @param startChildZygote If true, the new child process will itself be a + * new zygote process. + * @param instructionSet null-ok The instruction set to use. + * @param appDataDir null-ok The data directory of the app. + * @param isTopApp True if the process is for top (high priority) application. + * @param pkgDataInfoList A list that stores related packages and its app data + * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name, + * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid, + * app_b_ce_inode, ...]; + * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. + * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. + * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. + * @param bindMountSyspropOverrides True if the zygote needs to mount the override system + * properties + */ + private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, + String[] pkgDataInfoList, String[] allowlistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs, + boolean bindMountSyspropOverrides) { + nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, + niceName, startChildZygote, instructionSet, appDataDir, isTopApp, + pkgDataInfoList, allowlistedDataInfoList, + bindMountAppDataDirs, bindMountAppStorageDirs, bindMountSyspropOverrides); + + // Note that this event ends at the end of handleChildProc. + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + if (gids != null && gids.length > 0) { + NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); + } + + // Set the Java Language thread priority to the default value for new apps. + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + + /* + * This is called here (instead of after the fork but before the specialize) to maintain + * consistancy with the code paths for forkAndSpecialize. + * + * TODO (chriswailes): Look into moving this to immediately after the fork. + */ + ZygoteHooks.postForkCommon(); + } + + private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, + String[] pkgDataInfoList, String[] allowlistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs, + boolean bindMountSyspropOverrides); + + /** + * Called to do any initialization before starting an application. + */ + static native void nativePreApplicationInit(); + + /** + * Special method to start the system server process. In addition to the + * common actions performed in forkAndSpecialize, the pid of the child + * process is recorded such that the death of the child process will cause + * zygote to exit. + * + * @param uid the UNIX uid that the new process should setuid() to after + * fork()ing and and before spawning any threads. + * @param gid the UNIX gid that the new process should setgid() to after + * fork()ing and and before spawning any threads. + * @param gids null-ok; a list of UNIX gids that the new process should + * setgroups() to after fork and before spawning any threads. + * @param runtimeFlags bit flags that enable ART features. + * @param rlimits null-ok an array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param permittedCapabilities argument for setcap() + * @param effectiveCapabilities argument for setcap() + * + * @return 0 if this is the child, pid of the child + * if this is the parent, or -1 on error. + */ + static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { + ZygoteHooks.preFork(); + + int pid = nativeForkSystemServer( + uid, gid, gids, runtimeFlags, rlimits, + permittedCapabilities, effectiveCapabilities); + + // Set the Java Language thread priority to the default value for new apps. + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + + ZygoteHooks.postForkCommon(); + return pid; + } + + private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); + + /** + * Lets children of the zygote inherit open file descriptors to this path. + */ + protected static native void nativeAllowFileAcrossFork(String path); + + /** + * Lets children of the zygote inherit open file descriptors that belong to the + * ApplicationInfo that is passed in. + * + * @param appInfo ApplicationInfo of the application + */ + static void allowAppFilesAcrossFork(ApplicationInfo appInfo) { + for (String path : appInfo.getAllApkPaths()) { + Zygote.nativeAllowFileAcrossFork(path); + } + } + + /** + * Scans file descriptors in /proc/self/fd/, stores their metadata from readlink(2)/stat(2) when + * available. Saves this information in a global on native side, to be used by subsequent call + * to allowFilesOpenedByPreload(). Fatally fails if the FDs are of unsupported type and are not + * explicitly allowed. Ignores repeated invocations. + * + * Inspecting the FDs is more permissive than in forkAndSpecialize() because preload is invoked + * earlier and hence needs to allow a few open sockets. The checks in forkAndSpecialize() + * enforce that these sockets are closed when forking. + */ + static void markOpenedFilesBeforePreload() { + nativeMarkOpenedFilesBeforePreload(); + } + + private static native void nativeMarkOpenedFilesBeforePreload(); + + /** + * By scanning /proc/self/fd/ determines file descriptor numbers in this process opened since + * the first call to markOpenedFilesBeforePreload(). These FDs are treated as 'owned' by the + * custom preload of the App Zygote - the app is responsible for not sharing data with its other + * processes using these FDs, including by lseek(2). File descriptor types and file names are + * not checked. Changes in FDs recorded by markOpenedFilesBeforePreload() are not expected and + * kill the current process. + */ + static void allowFilesOpenedByPreload() { + nativeAllowFilesOpenedByPreload(); + } + + private static native void nativeAllowFilesOpenedByPreload(); + + /** + * Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range + * @param uidGidMin The smallest allowed uid/gid + * @param uidGidMax The largest allowed uid/gid + */ + native protected static void nativeInstallSeccompUidGidFilter(int uidGidMin, int uidGidMax); + + /** + * Initialize the native state of the Zygote. This inclues + * - Fetching socket FDs from the environment + * - Initializing security properties + * - Unmounting storage as appropriate + * - Loading necessary performance profile information + * + * @param isPrimary True if this is the zygote process, false if it is zygote_secondary + */ + static void initNativeState(boolean isPrimary) { + nativeInitNativeState(isPrimary); + } + + protected static native void nativeInitNativeState(boolean isPrimary); + + /** + * Returns the raw string value of a system property. + * + * Note that Device Config is not available without an application so SystemProperties is used + * instead. + * + * TODO (chriswailes): Cache the system property location in native code and then write a JNI + * function to fetch it. + */ + public static String getConfigurationProperty(String propertyName, String defaultValue) { + return SystemProperties.get( + String.join(".", + "persist.device_config", + DeviceConfig.NAMESPACE_RUNTIME_NATIVE, + propertyName), + defaultValue); + } + + static void emptyUsapPool() { + nativeEmptyUsapPool(); + } + + private static native void nativeEmptyUsapPool(); + + /** + * Returns the value of a system property converted to a boolean using specific logic. + * + * Note that Device Config is not available without an application so SystemProperties is used + * instead. + * + * @see SystemProperties#getBoolean + * + * TODO (chriswailes): Cache the system property location in native code and then write a JNI + * function to fetch it. + * TODO (chriswailes): Move into ZygoteConfig.java once the necessary CL lands (go/ag/6580627) + */ + public static boolean getConfigurationPropertyBoolean( + String propertyName, Boolean defaultValue) { + return SystemProperties.getBoolean( + String.join(".", + "persist.device_config", + DeviceConfig.NAMESPACE_RUNTIME_NATIVE, + propertyName), + defaultValue); + } + + /** + * @return Number of unspecialized app processes currently in the pool + */ + static int getUsapPoolCount() { + return nativeGetUsapPoolCount(); + } + + private static native int nativeGetUsapPoolCount(); + + /** + * @return The event FD used for communication between the signal handler and the ZygoteServer + * poll loop + */ + static FileDescriptor getUsapPoolEventFD() { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(nativeGetUsapPoolEventFD()); + + return fd; + } + + private static native int nativeGetUsapPoolEventFD(); + + /** + * Fork a new unspecialized app process from the zygote. Adds the Usap to the native + * Usap table. + * + * @param usapPoolSocket The server socket the USAP will call accept on + * @param sessionSocketRawFDs Anonymous session sockets that are currently open. + * These are closed in the child. + * @param isPriorityFork Raise the initial process priority level because this is on the + * critical path for application startup. + * @return In the child process, this returns a Runnable that waits for specialization + * info to start an app process. In the sygote/parent process this returns null. + */ + static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket, + int[] sessionSocketRawFDs, + boolean isPriorityFork) { + FileDescriptor readFD; + FileDescriptor writeFD; + + try { + FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC); + readFD = pipeFDs[0]; + writeFD = pipeFDs[1]; + } catch (ErrnoException errnoEx) { + throw new IllegalStateException("Unable to create USAP pipe.", errnoEx); + } + + int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(), + sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork); + if (pid == 0) { + IoUtils.closeQuietly(readFD); + return childMain(null, usapPoolSocket, writeFD); + } else if (pid == -1) { + // Fork failed. + return null; + } else { + // readFD will be closed by the native code. See removeUsapTableEntry(); + IoUtils.closeQuietly(writeFD); + nativeAddUsapTableEntry(pid, readFD.getInt$()); + return null; + } + } + + private static native int nativeForkApp(int readPipeFD, + int writePipeFD, + int[] sessionSocketRawFDs, + boolean argsKnown, + boolean isPriorityFork); + + /** + * Add an entry for a new Usap to the table maintained in native code. + */ + @CriticalNative + private static native void nativeAddUsapTableEntry(int pid, int readPipeFD); + + /** + * Fork a new app process from the zygote. argBuffer contains a fork command that + * request neither a child zygote, nor a wrapped process. Continue to accept connections + * on the specified socket, use those to refill argBuffer, and continue to process + * sufficiently simple fork requests. We presume that the only open file descriptors + * requiring special treatment are the session socket embedded in argBuffer, and + * zygoteSocket. + * @param argBuffer containing initial command and the connected socket from which to + * read more + * @param zygoteSocket socket from which to obtain new connections when current argBuffer + * one is disconnected + * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different + * peer will cause us to return rather than perform the requested fork. + * @param minUid Minimum Uid enforced for all but first fork request. The caller checks + * the Uid policy for the initial request. + * @param firstNiceName name of first created process. Used for error reporting only. + * @return A Runnable in each child process, null in the parent. + * If this returns in then argBuffer still contains a command needing to be executed. + */ + static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer, + @NonNull FileDescriptor zygoteSocket, + int expectedUid, + int minUid, + @Nullable String firstNiceName) { + boolean in_child = + argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName); + if (in_child) { + return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null); + } else { + return null; + } + } + + /** + * Specialize the current process into one described by argBuffer or the command read from + * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close + * it. Used both for a specializing a USAP process, and for process creation without USAPs. + * In both cases, we specialize the process after first returning to Java code. + * + * @param writePipe The write end of the reporting pipe used to communicate with the poll loop + * of the ZygoteServer. + * @return A runnable oject representing the new application. + */ + private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer, + @Nullable LocalServerSocket usapPoolSocket, + FileDescriptor writePipe) { + final int pid = Process.myPid(); + + DataOutputStream usapOutputStream = null; + ZygoteArguments args = null; + + LocalSocket sessionSocket = null; + if (argBuffer == null) { + // Read arguments from usapPoolSocket instead. + + Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32"); + + // Change the priority to max before calling accept so we can respond to new + // specialization requests as quickly as possible. This will be reverted to the + // default priority in the native specialization code. + boostUsapPriority(); + + while (true) { + ZygoteCommandBuffer tmpArgBuffer = null; + try { + sessionSocket = usapPoolSocket.accept(); + // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. + // This is safe from a race condition because the pool is only flushed after + // the SystemServer changes its internal state to stop using the USAP pool. + blockSigTerm(); + + usapOutputStream = + new DataOutputStream(sessionSocket.getOutputStream()); + Credentials peerCredentials = sessionSocket.getPeerCredentials(); + tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket); + args = ZygoteArguments.getInstance(tmpArgBuffer); + applyUidSecurityPolicy(args, peerCredentials); + // TODO (chriswailes): Should this only be run for debug builds? + validateUsapCommand(args); + break; + } catch (Exception ex) { + Log.e("USAP", ex.getMessage()); + } + // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary. + unblockSigTerm(); + IoUtils.closeQuietly(sessionSocket); + IoUtils.closeQuietly(tmpArgBuffer); + } + } else { + // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. + blockSigTerm(); + try { + args = ZygoteArguments.getInstance(argBuffer); + } catch (Exception ex) { + Log.e("AppStartup", ex.getMessage()); + throw new AssertionError("Failed to parse application start command", ex); + } + // peerCredentials were checked in parent. + } + if (args == null) { + throw new AssertionError("Empty command line"); + } + try { + // SIGTERM is blocked here. This prevents a USAP that is specializing from being + // killed during a pool flush. + + applyDebuggerSystemProperty(args); + + int[][] rlimits = null; + + if (args.mRLimits != null) { + rlimits = args.mRLimits.toArray(INT_ARRAY_2D); + } + + if (argBuffer == null) { + // This must happen before the SELinux policy for this process is + // changed when specializing. + try { + // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a + // Process.ProcessStartResult object. + usapOutputStream.writeInt(pid); + } catch (IOException ioEx) { + Log.e("USAP", "Failed to write response to session socket: " + + ioEx.getMessage()); + throw new RuntimeException(ioEx); + } finally { + try { + // Since the raw FD is created by init and then loaded from an environment + // variable (as opposed to being created by the LocalSocketImpl itself), + // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See + // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd). + // Thus closing the LocalSocket does not suffice. See b/130309968 for more + // discussion. + FileDescriptor fd = usapPoolSocket.getFileDescriptor(); + usapPoolSocket.close(); + Os.close(fd); + } catch (ErrnoException | IOException ex) { + Log.e("USAP", "Failed to close USAP pool socket"); + throw new RuntimeException(ex); + } + } + } + + if (writePipe != null) { + try { + ByteArrayOutputStream buffer = + new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES); + DataOutputStream outputStream = new DataOutputStream(buffer); + + // This is written as a long so that the USAP reporting pipe and USAP pool + // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two + // cases should both send/receive 8 bytes. + // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects + // a different format. + outputStream.writeLong(pid); + outputStream.flush(); + Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); + } catch (Exception ex) { + Log.e("USAP", + String.format("Failed to write PID (%d) to pipe (%d): %s", + pid, writePipe.getInt$(), ex.getMessage())); + throw new RuntimeException(ex); + } finally { + IoUtils.closeQuietly(writePipe); + } + } + + specializeAppProcess(args.mUid, args.mGid, args.mGids, + args.mRuntimeFlags, rlimits, args.mMountExternal, + args.mSeInfo, args.mNiceName, args.mStartChildZygote, + args.mInstructionSet, args.mAppDataDir, args.mIsTopApp, + args.mPkgDataInfoList, args.mAllowlistedDataInfoList, + args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs, + args.mBindMountSyspropOverrides); + + // While `specializeAppProcess` sets the thread name on the process's main thread, this + // is distinct from the app process name which appears in stack traces, as the latter is + // sourced from the argument buffer of the Process class. Set the app process name here. + Zygote.setAppProcessName(args, TAG); + + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + return ZygoteInit.zygoteInit(args.mTargetSdkVersion, + args.mDisabledCompatChanges, + args.mRemainingArgs, + null /* classLoader */); + } finally { + // Unblock SIGTERM to restore the process to default behavior. + unblockSigTerm(); + } + } + + private static void blockSigTerm() { + nativeBlockSigTerm(); + } + + private static native void nativeBlockSigTerm(); + + private static void unblockSigTerm() { + nativeUnblockSigTerm(); + } + + private static native void nativeUnblockSigTerm(); + + private static void boostUsapPriority() { + nativeBoostUsapPriority(); + } + + private static native void nativeBoostUsapPriority(); + + static void setAppProcessName(ZygoteArguments args, String loggingTag) { + if (args.mNiceName != null) { + Process.setArgV0(args.mNiceName); + } else if (args.mPackageName != null) { + Process.setArgV0(args.mPackageName); + } else { + Log.w(loggingTag, "Unable to set package name."); + } + } + + private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: "; + + /** + * Checks a set of zygote arguments to see if they can be handled by a USAP. Throws an + * exception if an invalid arugment is encountered. + * @param args The arguments to test + */ + private static void validateUsapCommand(ZygoteArguments args) { + if (args.mAbiListQuery) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--query-abi-list"); + } else if (args.mPidQuery) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--get-pid"); + } else if (args.mPreloadDefault) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-default"); + } else if (args.mPreloadPackage != null) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-package"); + } else if (args.mPreloadApp != null) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app"); + } else if (args.mStartChildZygote) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--start-child-zygote"); + } else if (args.mApiDenylistExemptions != null) { + throw new IllegalArgumentException( + USAP_ERROR_PREFIX + "--set-api-denylist-exemptions"); + } else if (args.mHiddenApiAccessLogSampleRate != -1) { + throw new IllegalArgumentException( + USAP_ERROR_PREFIX + "--hidden-api-log-sampling-rate="); + } else if (args.mHiddenApiAccessStatslogSampleRate != -1) { + throw new IllegalArgumentException( + USAP_ERROR_PREFIX + "--hidden-api-statslog-sampling-rate="); + } else if (args.mInvokeWith != null) { + throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--invoke-with"); + } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities) + + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities)); + } + } + + /** + * @return Raw file descriptors for the read-end of USAP reporting pipes. + */ + static int[] getUsapPipeFDs() { + return nativeGetUsapPipeFDs(); + } + + private static native int[] nativeGetUsapPipeFDs(); + + /** + * Remove the USAP table entry for the provided process ID. + * + * @param usapPID Process ID of the entry to remove + * @return True if the entry was removed; false if it doesn't exist + */ + static boolean removeUsapTableEntry(int usapPID) { + return nativeRemoveUsapTableEntry(usapPID); + } + + @CriticalNative + private static native boolean nativeRemoveUsapTableEntry(int usapPID); + + /** + * Return the minimum child uid that the given peer is allowed to create. + * uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal + * operation. It may also specify any gid and setgroups() list it chooses. + * In factory test mode, it may specify any UID. + */ + static int minChildUid(Credentials peer) { + if (peer.getUid() == Process.SYSTEM_UID + && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) { + /* In normal operation, SYSTEM_UID can only specify a restricted + * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. + */ + return Process.SYSTEM_UID; + } else { + return 0; + } + } + + /* + * Adjust uid and gid arguments, ensuring that the security policy is satisfied. + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException Indicates a security issue when applying the UID based + * security policies + */ + static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + + if (args.mUidSpecified && (args.mUid < minChildUid(peer))) { + throw new ZygoteSecurityException( + "System UID may not launch process with UID < " + + Process.SYSTEM_UID); + } + + // If not otherwise specified, uid and gid are inherited from peer + if (!args.mUidSpecified) { + args.mUid = peer.getUid(); + args.mUidSpecified = true; + } + if (!args.mGidSpecified) { + args.mGid = peer.getGid(); + args.mGidSpecified = true; + } + } + + /** + * This will enable jdwp by default for all apps. It is OK to cache this property + * because we expect to reboot the system whenever this property changes + */ + private static final boolean ENABLE_JDWP = SystemProperties.get( + "persist.debug.dalvik.vm.jdwp.enabled").equals("1"); + + /** + * This will enable ptrace by default for all apps. It is OK to cache this property + * because we expect to reboot the system whenever this property changes + */ + private static final boolean ENABLE_PTRACE = SystemProperties.get( + "persist.debug.ptrace.enabled").equals("1"); + + /** + * Applies debugger system properties to the zygote arguments. + * + * For eng builds all apps are debuggable with JDWP and ptrace. + * + * On userdebug builds if persist.debug.dalvik.vm.jdwp.enabled + * is 1 all apps are debuggable with JDWP and ptrace. Otherwise, the + * debugger state is specified via the "--enable-jdwp" flag in the + * spawn request. + * + * On userdebug builds if persist.debug.ptrace.enabled is 1 all + * apps are debuggable with ptrace. + * + * @param args non-null; zygote spawner args + */ + static void applyDebuggerSystemProperty(ZygoteArguments args) { + if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_JDWP)) { + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + // Also enable ptrace when JDWP is enabled for consistency with + // before persist.debug.ptrace.enabled existed. + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; + } + if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_PTRACE)) { + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; + } + } + + /** + * Applies zygote security policy. + * Based on the credentials of the process issuing a zygote command: + *

    + *
  1. uid 0 (root) may specify --invoke-with to launch Zygote with a + * wrapper command. + *
  2. Any other uid may not specify any invoke-with argument. + * + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException Thrown when `--invoke-with` is specified for a non-debuggable + * application. + */ + static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + int peerUid = peer.getUid(); + + if (args.mInvokeWith != null && peerUid != 0 + && (args.mRuntimeFlags + & (Zygote.DEBUG_ENABLE_JDWP | Zygote.DEBUG_ENABLE_PTRACE)) == 0) { + throw new ZygoteSecurityException("Peer is permitted to specify an " + + "explicit invoke-with wrapper command only for debuggable " + + "applications."); + } + } + + /** + * Gets the wrap property if set. + * + * @param appName the application name to check + * @return value of wrap property or null if property not set or + * null if app_name is null or null if app_name is empty + */ + public static String getWrapProperty(String appName) { + if (appName == null || appName.isEmpty()) { + return null; + } + + String propertyValue = SystemProperties.get("wrap." + appName); + if (propertyValue != null && !propertyValue.isEmpty()) { + return propertyValue; + } + return null; + } + + /** + * Applies invoke-with system properties to the zygote arguments. + * + * @param args non-null; zygote args + */ + static void applyInvokeWithSystemProperty(ZygoteArguments args) { + if (args.mInvokeWith == null) { + args.mInvokeWith = getWrapProperty(args.mNiceName); + } + } + + /** + * Creates a managed LocalServerSocket object using a file descriptor + * created by an init.rc script. The init scripts that specify the + * sockets name can be found in system/core/rootdir. The socket is bound + * to the file system in the /dev/sockets/ directory, and the file + * descriptor is shared via the ANDROID_SOCKET_ environment + * variable. + */ + static LocalServerSocket createManagedSocketFromInitSocket(String socketName) { + int fileDesc; + final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; + + try { + String env = System.getenv(fullSocketName); + fileDesc = Integer.parseInt(env); + } catch (RuntimeException ex) { + throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex); + } + + try { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(fileDesc); + return new LocalServerSocket(fd); + } catch (IOException ex) { + throw new RuntimeException( + "Error building socket from file descriptor: " + fileDesc, ex); + } + } + + // This function is called from native code in com_android_internal_os_Zygote.cpp + @SuppressWarnings("unused") + private static void callPostForkSystemServerHooks(int runtimeFlags) { + // SystemServer specific post fork hooks run before child post fork hooks. + ZygoteHooks.postForkSystemServer(runtimeFlags); + } + + // This function is called from native code in com_android_internal_os_Zygote.cpp + @SuppressWarnings("unused") + private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, + boolean isZygote, String instructionSet) { + ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); + } + + /** + * Executes "/system/bin/sh -c <command>" using the exec() system call. + * This method throws a runtime exception if exec() failed, otherwise, this + * method never returns. + * + * @param command The shell command to execute. + */ + static void execShell(String command) { + String[] args = { "/system/bin/sh", "-c", command }; + try { + Os.execv(args[0], args); + } catch (ErrnoException e) { + throw new RuntimeException(e); + } + } + + /** + * Appends quotes shell arguments to the specified string builder. + * The arguments are quoted using single-quotes, escaped if necessary, + * prefixed with a space, and appended to the command. + * + * @param command A string builder for the shell command being constructed. + * @param args An array of argument strings to be quoted and appended to the command. + * @see #execShell(String) + */ + static void appendQuotedShellArgs(StringBuilder command, String[] args) { + for (String arg : args) { + command.append(" '").append(arg.replace("'", "'\\''")).append("'"); + } + } + + /** + * Parse the given unsolicited zygote message as type SIGCHLD, + * extract the payload information into the given output buffer. + * + * @param in The unsolicited zygote message to be parsed + * @param length The number of bytes in the message + * @param out The output buffer where the payload information will be placed + * @return Number of elements being place into output buffer, or -1 if + * either the message is malformed or not the type as expected here. + * + * @hide + */ + @FastNative + public static native int nativeParseSigChld(byte[] in, int length, int[] out); + + /** + * Returns whether the hardware supports memory tagging (ARM MTE). + */ + public static native boolean nativeSupportsMemoryTagging(); + + /** + * Returns whether the kernel supports tagged pointers. Present in the + * Android Common Kernel from 4.14 and up. By default, you should prefer + * fully-feature Memory Tagging, rather than the static Tagged Pointers. + */ + public static native boolean nativeSupportsTaggedPointers(); + + /** + * Returns the current native tagging level, as one of the + * MEMORY_TAG_LEVEL_* constants. Returns zero if no tagging is present, or + * we failed to determine the level. + */ + public static native int nativeCurrentTaggingLevel(); + + /** + * Native heap allocations will now have a non-zero tag in the most significant byte. + * + * @see Tagged + * Pointers + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. + + /** + * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag + * in the most significant byte. + * + * @see Tagged + * Pointers + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) + private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677; + + /** + * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an + * effect on hardware supporting the ARM Memory Tagging Extension (MTE). + */ + @ChangeId @Disabled + private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id. + + /** + * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an + * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both + * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is + * enabled in SYNC mode. + */ + @ChangeId @Disabled + private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id. + + /** Enable automatic zero-initialization of native heap memory allocations. */ + @ChangeId @Disabled + private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id. + + /** + * Enable sampled memory bug detection in the app. + * + * @see GWP-ASan. + */ + @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id. + + private static int memtagModeToZygoteMemtagLevel(int memtagMode) { + switch (memtagMode) { + case ApplicationInfo.MEMTAG_ASYNC: + return MEMORY_TAG_LEVEL_ASYNC; + case ApplicationInfo.MEMTAG_SYNC: + return MEMORY_TAG_LEVEL_SYNC; + default: + return MEMORY_TAG_LEVEL_NONE; + } + } + + private static boolean isCompatChangeEnabled( + long change, + @NonNull ApplicationInfo info, + @Nullable IPlatformCompat platformCompat, + int enabledAfter) { + try { + if (platformCompat != null) return platformCompat.isChangeEnabled(change, info); + } catch (RemoteException ignore) { + } + return enabledAfter > 0 && info.targetSdkVersion > enabledAfter; + } + + // Returns the requested memory tagging level. + private static int getRequestedMemtagLevel( + @NonNull ApplicationInfo info, + @Nullable ProcessInfo processInfo, + @Nullable IPlatformCompat platformCompat) { + String appOverride = SystemProperties.get("persist.arm64.memtag.app." + info.packageName); + if ("sync".equals(appOverride)) { + return MEMORY_TAG_LEVEL_SYNC; + } else if ("async".equals(appOverride)) { + return MEMORY_TAG_LEVEL_ASYNC; + } else if ("off".equals(appOverride)) { + return MEMORY_TAG_LEVEL_NONE; + } + + // Look at the process attribute first. + if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) { + return memtagModeToZygoteMemtagLevel(processInfo.memtagMode); + } + + // Then at the application attribute. + if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) { + return memtagModeToZygoteMemtagLevel(info.getMemtagMode()); + } + + if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) { + return MEMORY_TAG_LEVEL_SYNC; + } + + if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) { + return MEMORY_TAG_LEVEL_ASYNC; + } + + // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute. + if (!info.allowsNativeHeapPointerTagging()) { + return MEMORY_TAG_LEVEL_NONE; + } + + String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default"); + if ("sync".equals(defaultLevel)) { + return MEMORY_TAG_LEVEL_SYNC; + } else if ("async".equals(defaultLevel)) { + return MEMORY_TAG_LEVEL_ASYNC; + } + + // Check to see that the compat feature for TBI is enabled. + if (isCompatChangeEnabled( + NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) { + return MEMORY_TAG_LEVEL_TBI; + } + + return MEMORY_TAG_LEVEL_NONE; + } + + private static int decideTaggingLevel( + @NonNull ApplicationInfo info, + @Nullable ProcessInfo processInfo, + @Nullable IPlatformCompat platformCompat) { + // Get the desired tagging level (app manifest + compat features). + int level = getRequestedMemtagLevel(info, processInfo, platformCompat); + + // Take into account the hardware capabilities. + if (nativeSupportsMemoryTagging()) { + // MTE devices can not do TBI, because the Zygote process already has live MTE + // allocations. Downgrade TBI to NONE. + if (level == MEMORY_TAG_LEVEL_TBI) { + level = MEMORY_TAG_LEVEL_NONE; + } + } else if (nativeSupportsTaggedPointers()) { + // TBI-but-not-MTE devices downgrade MTE modes to TBI. + // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with + // the "fake" pointer tagging (TBI). + if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) { + level = MEMORY_TAG_LEVEL_TBI; + } + } else { + // Otherwise disable all tagging. + level = MEMORY_TAG_LEVEL_NONE; + } + + // If we requested "sync" mode for the whole platform, upgrade mode for apps that enable + // MTE. + // This makes debugging a lot easier. + if (level == MEMORY_TAG_LEVEL_ASYNC + && (Build.IS_USERDEBUG || Build.IS_ENG) + && "sync".equals(SystemProperties.get("persist.arm64.memtag.default"))) { + level = MEMORY_TAG_LEVEL_SYNC; + } + + return level; + } + + private static int decideGwpAsanLevel( + @NonNull ApplicationInfo info, + @Nullable ProcessInfo processInfo, + @Nullable IPlatformCompat platformCompat) { + // Look at the process attribute first. + if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) { + return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS + ? GWP_ASAN_LEVEL_ALWAYS + : GWP_ASAN_LEVEL_NEVER; + } + // Then at the application attribute. + if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) { + return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS + ? GWP_ASAN_LEVEL_ALWAYS + : GWP_ASAN_LEVEL_NEVER; + } + if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) { + return GWP_ASAN_LEVEL_ALWAYS; + } + if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return GWP_ASAN_LEVEL_LOTTERY; + } + return GWP_ASAN_LEVEL_DEFAULT; + } + + private static boolean enableNativeHeapZeroInit( + @NonNull ApplicationInfo info, + @Nullable ProcessInfo processInfo, + @Nullable IPlatformCompat platformCompat) { + // Look at the process attribute first. + if (processInfo != null + && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) { + return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED; + } + // Then at the application attribute. + if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) { + return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED; + } + // Compat feature last. + if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) { + return true; + } + return false; + } + + /** + * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit) + * for a given app. + */ + public static int getMemorySafetyRuntimeFlags( + @NonNull ApplicationInfo info, + @Nullable ProcessInfo processInfo, + @Nullable String instructionSet, + @Nullable IPlatformCompat platformCompat) { + int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat); + // If instructionSet is non-null, this indicates that the system_server is spawning a + // process with an ISA that may be different from its own. System (kernel and hardware) + // compatibility for these features is checked in the decideTaggingLevel in the + // system_server process (not the child process). As both MTE and TBI are only supported + // in aarch64, we can simply ensure that the new process is also aarch64. This prevents + // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should + // enable some tagging variant. Theoretically, a 32-bit system server could exist that + // spawns 64-bit processes, in which case the new process won't get any tagging. This is + // fine as we haven't seen this configuration in practice, and we can reasonable assume + // that if tagging is desired, the system server will be 64-bit. + if (instructionSet == null || instructionSet.equals("arm64")) { + runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat); + } + if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) { + runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED; + } + return runtimeFlags; + } + + /** + * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit) + * for a secondary zygote (AppZygote or WebViewZygote). + */ + public static int getMemorySafetyRuntimeFlagsForSecondaryZygote( + @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) { + final IPlatformCompat platformCompat = + IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + int runtimeFlags = + getMemorySafetyRuntimeFlags( + info, processInfo, null /*instructionSet*/, platformCompat); + + // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature. + if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI + && isCompatChangeEnabled( + NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE, + info, + platformCompat, + Build.VERSION_CODES.S)) { + // Reset memory tag level to NONE. + runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK; + runtimeFlags |= MEMORY_TAG_LEVEL_NONE; + } + return runtimeFlags; + } +} diff --git a/aosp/frameworks/base/services/core/Android.bp b/aosp/frameworks/base/services/core/Android.bp new file mode 100644 index 000000000..5fe4be72c --- /dev/null +++ b/aosp/frameworks/base/services/core/Android.bp @@ -0,0 +1,315 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "services.core-sources-am-wm", + srcs: [ + "java/com/android/server/am/**/*.java", + "java/com/android/server/wm/**/*.java", + ], + path: "java", + visibility: ["//frameworks/base/services"], +} + +filegroup { + name: "services.core-sources", + srcs: ["java/**/*.java"], + exclude_srcs: [ + ":services.core-sources-am-wm", + ], + path: "java", + visibility: [ + "//frameworks/base/services", + "//frameworks/base/core/java/com/android/internal/protolog", + ], +} + +java_library_static { + name: "services-config-update", + srcs: [ + "java/**/ConfigUpdateInstallReceiver.java", + "java/**/*.logtags", + ], +} + +genrule { + name: "services.core.protologsrc", + srcs: [ + ":protolog-impl", + ":protolog-groups", + ":services.core-sources-am-wm", + ], + tools: ["protologtool"], + cmd: "$(location protologtool) transform-protolog-calls " + + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + + "--loggroups-jar $(location :protolog-groups) " + + "--viewer-config-file-path /etc/core.protolog.pb " + + "--legacy-viewer-config-file-path /system/etc/protolog.conf.json.gz " + + "--legacy-output-file-path /data/misc/wmtrace/wm_log.winscope " + + "--output-srcjar $(out) " + + "$(locations :services.core-sources-am-wm)", + out: ["services.core.protolog.srcjar"], +} + +genrule { + name: "generate-protolog.json", + srcs: [ + ":protolog-groups", + ":services.core-sources-am-wm", + ], + tools: ["protologtool"], + cmd: "$(location protologtool) generate-viewer-config " + + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + + "--loggroups-jar $(location :protolog-groups) " + + "--viewer-config-type json " + + "--viewer-config $(out) " + + "$(locations :services.core-sources-am-wm)", + out: ["services.core.protolog.json"], +} + +genrule { + name: "gen-core.protolog.pb", + srcs: [ + ":protolog-groups", + ":services.core-sources-am-wm", + ], + tools: ["protologtool"], + cmd: "$(location protologtool) generate-viewer-config " + + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + + "--loggroups-jar $(location :protolog-groups) " + + "--viewer-config-type proto " + + "--viewer-config $(out) " + + "$(locations :services.core-sources-am-wm)", + out: ["core.protolog.pb"], +} + +genrule { + name: "checked-protolog.json", + srcs: [ + ":generate-protolog.json", + ":services.core.protolog.json", + ], + cmd: "cp $(location :generate-protolog.json) $(out) && " + + "{ ! (diff $(out) $(location :services.core.protolog.json) | grep -q '^<') || " + + "{ echo -e '\\n\\n################################################################\\n#\\n" + + "# ERROR: ProtoLog viewer config is stale. To update it, run:\\n#\\n" + + "# cp $${ANDROID_BUILD_TOP}/$(location :generate-protolog.json) " + + "$${ANDROID_BUILD_TOP}/$(location :services.core.protolog.json)\\n#\\n" + + "################################################################\\n\\n' >&2 && false; } }", + out: ["services.core.protolog.json"], +} + +genrule { + name: "checked-core.protolog.pb", + srcs: [ + ":gen-core.protolog.pb", + ":file-core.protolog.pb", + ], + cmd: "cp $(location :gen-core.protolog.pb) $(out) && " + + "{ ! (diff $(out) $(location :file-core.protolog.pb) | grep -q '^<') || " + + "{ echo -e '\\n\\n################################################################\\n#\\n" + + "# ERROR: ProtoLog viewer config is stale. To update it, run:\\n#\\n" + + "# cp $${ANDROID_BUILD_TOP}/$(location :gen-core.protolog.pb) " + + "$${ANDROID_BUILD_TOP}/$(location :file-core.protolog.pb)\\n#\\n" + + "################################################################\\n\\n' >&2 && false; } }", + out: ["core.protolog.pb"], +} + +genrule { + name: "statslog-art-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module art" + + " --javaPackage com.android.internal.art --javaClass ArtStatsLog --worksource", + out: ["com/android/internal/art/ArtStatsLog.java"], +} + +genrule { + name: "statslog-contexthub-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module contexthub" + + " --javaPackage com.android.server.location.contexthub --javaClass ContextHubStatsLog", + out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"], +} + +java_library_static { + name: "services.core.unboosted", + defaults: [ + "platform_service_defaults", + "android.hardware.power-java_shared", + ], + srcs: [ + ":android.hardware.tv.hdmi.connection-V1-java-source", + ":android.hardware.tv.hdmi.earc-V1-java-source", + ":statslog-art-java-gen", + ":statslog-contexthub-java-gen", + ":services.core-sources", + ":services.core.protologsrc", + ":dumpstate_aidl", + ":framework_native_aidl", + ":gsiservice_aidl", + ":installd_aidl", + ":storaged_aidl", + ":vold_aidl", + ":platform-compat-config", + ":platform-compat-overrides", + ":display-device-config", + ":display-layout-config", + ":device-state-config", + "java/com/android/server/EventLogTags.logtags", + "java/com/android/server/am/EventLogTags.logtags", + "java/com/android/server/wm/EventLogTags.logtags", + "java/com/android/server/policy/EventLogTags.logtags", + + // Java/AIDL sources to be moved out to CrashRecovery module + ":services-crashrecovery-sources", + ], + + libs: [ + "services.net", + "android.frameworks.location.altitude-V2-java", + "android.hardware.common-V2-java", + "android.hardware.light-V2.0-java", + "android.hardware.gnss-V2-java", + "android.hardware.vibrator-V2-java", + "app-compat-annotations", + "framework-tethering.stubs.module_lib", + "service-art.stubs.system_server", + "service-permission.stubs.system_server", + "service-rkp.stubs.system_server", + "service-sdksandbox.stubs.system_server", + "device_policy_aconfig_flags_lib", + ], + plugins: ["ImmutabilityAnnotationProcessor"], + + required: [ + "default_television.xml", + "gps_debug.conf", + "protolog.conf.json.gz", + "core.protolog.pb", + ], + + static_libs: [ + "android.frameworks.vibrator-V1-java", // AIDL + "android.hardware.authsecret-V1.0-java", + "android.hardware.authsecret-V1-java", + "android.hardware.boot-V1.0-java", // HIDL + "android.hardware.boot-V1.1-java", // HIDL + "android.hardware.boot-V1.2-java", // HIDL + "android.hardware.boot-V1-java", // AIDL + "android.hardware.broadcastradio-V2.0-java", // HIDL + "android.hardware.broadcastradio-V2-java", // AIDL + "android.hardware.health-V1.0-java", // HIDL + "android.hardware.health-V2.0-java", // HIDL + "android.hardware.health-V2.1-java", // HIDL + "android.hardware.health-V3-java", // AIDL + "android.hardware.health-translate-java", + "android.hardware.light-V1-java", + "android.hardware.security.authgraph-V1-java", + "android.hardware.security.rkp-V3-java", + "android.hardware.security.secretkeeper-V1-java", + "android.hardware.tv.cec-V1.1-java", + "android.hardware.tv.hdmi.cec-V1-java", + "android.hardware.tv.hdmi.connection-V1-java", + "android.hardware.tv.hdmi.earc-V1-java", + "android.hardware.weaver-V1.0-java", + "android.hardware.weaver-V2-java", + "android.hardware.biometrics.face-V1.0-java", + "android.hardware.biometrics.fingerprint-V2.3-java", + "android.hardware.oemlock-V1.0-java", + "android.hardware.oemlock-V1-java", + "android.hardware.configstore-V1.1-java", + "android.hardware.ir-V1-java", + "android.hardware.rebootescrow-V1-java", + "android.hardware.power.stats-V2-java", + "android.hidl.manager-V1.2-java", + "cbor-java", + "com.android.media.audio-aconfig-java", + "dropbox_flags_lib", + "icu4j_calendar_astronomer", + "android.security.aaid_aidl-java", + "netd-client", + "overlayable_policy_aidl-java", + "SurfaceFlingerProperties", + "com.android.sysprop.watchdog", + "securebox", + "apache-commons-math", + "backstage_power_flags_lib", + "notification_flags_lib", + "biometrics_flags_lib", + "am_flags_lib", + "com_android_server_accessibility_flags_lib", + "com_android_systemui_shared_flags_lib", + "com_android_wm_shell_flags_lib", + "com.android.server.utils_aconfig-java", + "service-jobscheduler-deviceidle.flags-aconfig-java", + "policy_flags_lib", + "net_flags_lib", + "stats_flags_lib", + "core_os_flags_lib", + ], + javac_shard_size: 50, + javacflags: [ + "-J--add-modules=jdk.compiler", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], + lint: { + baseline_filename: "lint-baseline.xml", + }, +} + +java_genrule { + name: "services.core.priorityboosted", + srcs: [":services.core.unboosted"], + tools: ["lockedregioncodeinjection"], + cmd: "$(location lockedregioncodeinjection) " + + " --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/am/ActivityManagerGlobalLock;,Lcom/android/server/wm/WindowManagerGlobalLock;,Lcom/android/server/pm/PackageManagerTracedLock;\" " + + " --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/am/ActivityManagerService.boostPriorityForProcLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection,com/android/server/pm/PackageManagerService.boostPriorityForPackageManagerTracedLockedSection\" " + + " --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/am/ActivityManagerService.resetPriorityAfterProcLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection,com/android/server/pm/PackageManagerService.resetPriorityAfterPackageManagerTracedLockedSection\" " + + " -o $(out) " + + " -i $(in)", + out: ["services.core.priorityboosted.jar"], +} + +java_library { + name: "services.core", + static_libs: ["services.core.priorityboosted"], +} + +java_library_host { + name: "core_cts_test_resources", + srcs: ["java/com/android/server/notification/SmallHash.java"], +} + +prebuilt_etc { + name: "gps_debug.conf", + src: "java/com/android/server/location/gnss/gps_debug.conf", +} + +genrule { + name: "services.core.json.gz", + srcs: [":checked-protolog.json"], + out: ["services.core.protolog.json.gz"], + cmd: "gzip -c < $(in) > $(out)", +} + +prebuilt_etc { + name: "protolog.conf.json.gz", + src: ":services.core.json.gz", +} + +prebuilt_etc { + name: "core.protolog.pb", + src: ":checked-core.protolog.pb", +} diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java new file mode 100644 index 000000000..45f657d71 --- /dev/null +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -0,0 +1,4510 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; +import static android.app.ActivityTaskManager.RESIZE_MODE_USER; +import static android.app.WaitResult.launchStateToString; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; +import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED; +import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.Process.INVALID_UID; +import static android.view.Display.INVALID_DISPLAY; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; + +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; +import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT; +import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING; + +import android.annotation.UserIdInt; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.app.ActivityTaskManager.RootTaskInfo; +import android.app.AppGlobals; +import android.app.BroadcastOptions; +import android.app.ForegroundServiceDelegationOptions; +import android.app.IActivityController; +import android.app.IActivityManager; +import android.app.IActivityTaskManager; +import android.app.IProcessObserver; +import android.app.IStopUserCallback; +import android.app.IUserSwitchObserver; +import android.app.KeyguardManager; +import android.app.ProcessStateEnum; +import android.app.ProfilerInfo; +import android.app.RemoteServiceException.CrashedByAdbException; +import android.app.UidObserver; +import android.app.UserSwitchObserver; +import android.app.WaitResult; +import android.app.usage.AppStandbyInfo; +import android.app.usage.ConfigurationStats; +import android.app.usage.IUsageStatsManager; +import android.app.usage.UsageStatsManager; +import android.compat.Compatibility; +import android.content.ComponentCallbacks2; +import android.content.ComponentName; +import android.content.Context; +import android.content.DeviceConfigurationProto; +import android.content.GlobalConfigurationProto; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.pm.ConfigurationInfo; +import android.content.pm.FeatureInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; +import android.content.pm.SharedLibraryInfo; +import android.content.pm.UserInfo; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.opengl.GLES10; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.IProgressListener; +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; +import android.os.RemoteCallback.OnResultListener; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ShellCommand; +import android.os.StrictMode; +import android.os.SystemClock; +import android.os.Trace; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.DebugUtils; +import android.util.DisplayMetrics; +import android.util.TeeWriter; +import android.util.proto.ProtoOutputStream; +import android.view.Display; +import android.window.SplashScreen; + +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.internal.util.MemInfoReader; +import com.android.server.LocalServices; +import com.android.server.am.LowMemDetector.MemFactor; +import com.android.server.am.nano.Capabilities; +import com.android.server.am.nano.Capability; +import com.android.server.compat.PlatformCompat; +import com.android.server.pm.UserManagerInternal; +import com.android.server.utils.Slogf; + +import dalvik.annotation.optimization.NeverCompile; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.URISyntaxException; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +final class ActivityManagerShellCommand extends ShellCommand { + + static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerShellCommand" : TAG_AM; + + + public static final String NO_CLASS_ERROR_CODE = "Error type 3"; + + private static final String SHELL_PACKAGE_NAME = "com.android.shell"; + + private static final int USER_OPERATION_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes + + private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss", Locale.ROOT); + + // IPC interface to activity manager -- don't need to do additional security checks. + final IActivityManager mInterface; + final IActivityTaskManager mTaskInterface; + + // Internal service impl -- must perform security checks before touching. + final ActivityManagerService mInternal; + + // Convenience for interacting with package manager. + final IPackageManager mPm; + + private int mStartFlags = 0; + private boolean mWaitOption = false; + private boolean mStopOption = false; + + private int mRepeat = 0; + private int mUserId; + private String mReceiverPermission; + + private String mProfileFile; + private int mSamplingInterval; + private boolean mAutoStop; + private boolean mStreaming; // Streaming the profiling output to a file. + private String mAgent; // Agent to attach on startup. + private boolean mAttachAgentDuringBind; // Whether agent should be attached late. + private int mClockType; // Whether we need thread cpu / wall clock / both. + private int mDisplayId; + private int mTaskDisplayAreaFeatureId; + private int mWindowingMode; + private int mActivityType; + private int mTaskId; + private boolean mIsTaskOverlay; + private boolean mIsLockTask; + private boolean mAsync; + private BroadcastOptions mBroadcastOptions; + private boolean mShowSplashScreen; + private boolean mDismissKeyguardIfInsecure; + + final boolean mDumping; + + private static final String[] CAPABILITIES = {"start.suspend"}; + + + ActivityManagerShellCommand(ActivityManagerService service, boolean dumping) { + mInterface = service; + mTaskInterface = service.mActivityTaskManager; + mInternal = service; + mPm = AppGlobals.getPackageManager(); + mDumping = dumping; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + try { + switch (cmd) { + case "help": + onHelp(); + return 0; + case "start": + case "start-activity": + return runStartActivity(pw); + case "startservice": + case "start-service": + return runStartService(pw, false); + case "startforegroundservice": + case "startfgservice": + case "start-foreground-service": + case "start-fg-service": + return runStartService(pw, true); + case "stopservice": + case "stop-service": + return runStopService(pw); + case "broadcast": + return runSendBroadcast(pw); + case "compact": + return runCompact(pw); + case "freeze": + return runFreeze(pw); + case "unfreeze": + return runUnfreeze(pw); + case "instrument": + getOutPrintWriter().println("Error: must be invoked through 'am instrument'."); + return -1; + case "trace-ipc": + return runTraceIpc(pw); + case "profile": + return runProfile(pw); + case "dumpheap": + return runDumpHeap(pw); + case "set-debug-app": + return runSetDebugApp(pw); + case "set-agent-app": + return runSetAgentApp(pw); + case "clear-debug-app": + return runClearDebugApp(pw); + case "set-watch-heap": + return runSetWatchHeap(pw); + case "clear-watch-heap": + return runClearWatchHeap(pw); + case "clear-start-info": + return runClearStartInfo(pw); + case "clear-exit-info": + return runClearExitInfo(pw); + case "bug-report": + return runBugReport(pw); + case "force-stop": + return runForceStop(pw); + case "stop-app": + return runStopApp(pw); + case "clear-recent-apps": + return runClearRecentApps(pw); + case "fgs-notification-rate-limit": + return runFgsNotificationRateLimit(pw); + case "crash": + return runCrash(pw); + case "kill": + return runKill(pw); + case "kill-all": + return runKillAll(pw); + case "make-uid-idle": + return runMakeIdle(pw); + case "set-deterministic-uid-idle": + return runSetDeterministicUidIdle(pw); + case "monitor": + return runMonitor(pw); + case "watch-uids": + return runWatchUids(pw); + case "hang": + return runHang(pw); + case "restart": + return runRestart(pw); + case "idle-maintenance": + return runIdleMaintenance(pw); + case "screen-compat": + return runScreenCompat(pw); + case "package-importance": + return runPackageImportance(pw); + case "to-uri": + return runToUri(pw, 0); + case "to-intent-uri": + return runToUri(pw, Intent.URI_INTENT_SCHEME); + case "to-app-uri": + return runToUri(pw, Intent.URI_ANDROID_APP_SCHEME); + case "switch-user": + return runSwitchUser(pw); + case "get-current-user": + return runGetCurrentUser(pw); + case "start-user": + return runStartUser(pw); + case "unlock-user": + return runUnlockUser(pw); + case "stop-user": + return runStopUser(pw); + case "is-user-stopped": + return runIsUserStopped(pw); + case "get-started-user-state": + return runGetStartedUserState(pw); + case "track-associations": + return runTrackAssociations(pw); + case "untrack-associations": + return runUntrackAssociations(pw); + case "get-uid-state": + return getUidState(pw); + case "get-config": + return runGetConfig(pw); + case "suppress-resize-config-changes": + return runSuppressResizeConfigChanges(pw); + case "set-inactive": + return runSetInactive(pw); + case "get-inactive": + return runGetInactive(pw); + case "set-standby-bucket": + return runSetStandbyBucket(pw); + case "get-standby-bucket": + return runGetStandbyBucket(pw); + case "send-trim-memory": + return runSendTrimMemory(pw); + case "display": + return runDisplay(pw); + case "stack": + return runStack(pw); + case "task": + return runTask(pw); + case "write": + return runWrite(pw); + case "attach-agent": + return runAttachAgent(pw); + case "supports-multiwindow": + return runSupportsMultiwindow(pw); + case "supports-split-screen-multi-window": + return runSupportsSplitScreenMultiwindow(pw); + case "update-appinfo": + return runUpdateApplicationInfo(pw); + case "no-home-screen": + return runNoHomeScreen(pw); + case "wait-for-broadcast-idle": + return runWaitForBroadcastIdle(pw); + case "wait-for-broadcast-barrier": + return runWaitForBroadcastBarrier(pw); + case "wait-for-application-barrier": + return runWaitForApplicationBarrier(pw); + case "wait-for-broadcast-dispatch": + return runWaitForBroadcastDispatch(pw); + case "set-ignore-delivery-group-policy": + return runSetIgnoreDeliveryGroupPolicy(pw); + case "clear-ignore-delivery-group-policy": + return runClearIgnoreDeliveryGroupPolicy(pw); + case "compat": + return runCompat(pw); + case "refresh-settings-cache": + return runRefreshSettingsCache(); + case "memory-factor": + return runMemoryFactor(pw); + case "service-restart-backoff": + return runServiceRestartBackoff(pw); + case "get-isolated-pids": + return runGetIsolatedProcesses(pw); + case "set-stop-user-on-switch": + return runSetStopUserOnSwitch(pw); + case "set-bg-abusive-uids": + return runSetBgAbusiveUids(pw); + case "list-bg-exemptions-config": + return runListBgExemptionsConfig(pw); + case "set-bg-restriction-level": + return runSetBgRestrictionLevel(pw); + case "get-bg-restriction-level": + return runGetBgRestrictionLevel(pw); + case "observe-foreground-process": + return runGetCurrentForegroundProcess(pw, mInternal); + case "reset-dropbox-rate-limiter": + return runResetDropboxRateLimiter(); + case "list-displays-for-starting-users": + return runListDisplaysForStartingUsers(pw); + case "set-foreground-service-delegate": + return runSetForegroundServiceDelegate(pw); + case "capabilities": + return runCapabilities(pw); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + int runCapabilities(PrintWriter pw) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + boolean outputAsProtobuf = false; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--protobuf")) { + outputAsProtobuf = true; + } else { + err.println("Error: Unknown option: " + opt); + return -1; + } + } + + if (outputAsProtobuf) { + Capabilities capabilities = new Capabilities(); + capabilities.values = new Capability[CAPABILITIES.length]; + for (int i = 0; i < CAPABILITIES.length; i++) { + Capability cap = new Capability(); + cap.name = CAPABILITIES[i]; + capabilities.values[i] = cap; + } + + try { + getRawOutputStream().write(Capabilities.toByteArray(capabilities)); + } catch (IOException e) { + pw.println("Error while serializing capabilities protobuffer"); + } + + } else { + // Unfortunately we don't have protobuf text format capabilities here. + // Fallback to line separated list instead for text parser. + pw.println("Format: 1"); + for (String capability : CAPABILITIES) { + pw.println(capability); + } + } + return 0; + } + + private Intent makeIntent(int defUser) throws URISyntaxException { + mStartFlags = 0; + mWaitOption = false; + mStopOption = false; + mRepeat = 0; + mProfileFile = null; + mSamplingInterval = 0; + mAutoStop = false; + mStreaming = false; + mUserId = defUser; + mDisplayId = INVALID_DISPLAY; + mTaskDisplayAreaFeatureId = FEATURE_UNDEFINED; + mWindowingMode = WINDOWING_MODE_UNDEFINED; + mActivityType = ACTIVITY_TYPE_UNDEFINED; + mTaskId = INVALID_TASK_ID; + mIsTaskOverlay = false; + mIsLockTask = false; + mAsync = false; + mBroadcastOptions = null; + + return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() { + @Override + public boolean handleOption(String opt, ShellCommand cmd) { + if (opt.equals("-D")) { + mStartFlags |= ActivityManager.START_FLAG_DEBUG; + } else if (opt.equals("--suspend")) { + mStartFlags |= ActivityManager.START_FLAG_DEBUG_SUSPEND; + } else if (opt.equals("-N")) { + mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING; + } else if (opt.equals("-W")) { + mWaitOption = true; + } else if (opt.equals("-P")) { + mProfileFile = getNextArgRequired(); + mAutoStop = true; + } else if (opt.equals("--start-profiler")) { + mProfileFile = getNextArgRequired(); + mAutoStop = false; + } else if (opt.equals("--sampling")) { + mSamplingInterval = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--clock-type")) { + String clock_type = getNextArgRequired(); + mClockType = ProfilerInfo.getClockTypeFromString(clock_type); + } else if (opt.equals("--streaming")) { + mStreaming = true; + } else if (opt.equals("--attach-agent")) { + if (mAgent != null) { + cmd.getErrPrintWriter().println( + "Multiple --attach-agent(-bind) not supported"); + return false; + } + mAgent = getNextArgRequired(); + mAttachAgentDuringBind = false; + } else if (opt.equals("--attach-agent-bind")) { + if (mAgent != null) { + cmd.getErrPrintWriter().println( + "Multiple --attach-agent(-bind) not supported"); + return false; + } + mAgent = getNextArgRequired(); + mAttachAgentDuringBind = true; + } else if (opt.equals("-R")) { + mRepeat = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("-S")) { + mStopOption = true; + } else if (opt.equals("--track-allocation")) { + mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION; + } else if (opt.equals("--user")) { + mUserId = UserHandle.parseUserArg(getNextArgRequired()); + } else if (opt.equals("--receiver-permission")) { + mReceiverPermission = getNextArgRequired(); + } else if (opt.equals("--display")) { + mDisplayId = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--task-display-area-feature-id")) { + mTaskDisplayAreaFeatureId = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--windowingMode")) { + mWindowingMode = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--activityType")) { + mActivityType = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--task")) { + mTaskId = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--task-overlay")) { + mIsTaskOverlay = true; + } else if (opt.equals("--lock-task")) { + mIsLockTask = true; + } else if (opt.equals("--allow-background-activity-starts")) { + if (mBroadcastOptions == null) { + mBroadcastOptions = BroadcastOptions.makeBasic(); + } + mBroadcastOptions.setBackgroundActivityStartsAllowed(true); + } else if (opt.equals("--async")) { + mAsync = true; + } else if (opt.equals("--splashscreen-show-icon")) { + mShowSplashScreen = true; + } else if (opt.equals("--dismiss-keyguard-if-insecure") + || opt.equals("--dismiss-keyguard")) { + mDismissKeyguardIfInsecure = true; + } else if (opt.equals("--allow-fgs-start-reason")) { + final int reasonCode = Integer.parseInt(getNextArgRequired()); + mBroadcastOptions = BroadcastOptions.makeBasic(); + mBroadcastOptions.setTemporaryAppAllowlist(10_000, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + reasonCode, + ""); + } else { + return false; + } + return true; + } + }); + } + + private class ProgressWaiter extends IProgressListener.Stub { + private final CountDownLatch mFinishedLatch = new CountDownLatch(1); + private final @UserIdInt int mUserId; + + private ProgressWaiter(@UserIdInt int userId) { + mUserId = userId; + } + + @Override + public void onStarted(int id, Bundle extras) {} + + @Override + public void onProgress(int id, int progress, Bundle extras) { + Slogf.d(TAG, "ProgressWaiter[user=%d]: onProgress(%d, %d)", mUserId, id, progress); + } + + @Override + public void onFinished(int id, Bundle extras) { + Slogf.d(TAG, "ProgressWaiter[user=%d]: onFinished(%d)", mUserId, id); + mFinishedLatch.countDown(); + } + + @Override + public String toString() { + return "ProgressWaiter[userId=" + mUserId + ", finished=" + + (mFinishedLatch.getCount() == 0) + "]"; + } + + public boolean waitForFinish(long timeoutMillis) { + try { + return mFinishedLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + System.err.println("Thread interrupted unexpectedly."); + return false; + } + } + } + + int runStartActivity(PrintWriter pw) throws RemoteException { + Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + + if (mUserId == UserHandle.USER_ALL) { + getErrPrintWriter().println("Error: Can't start service with user 'all'"); + return 1; + } + + final String mimeType = intent.resolveType(mInternal.mContext); + + do { + if (mStopOption) { + String packageName; + if (intent.getComponent() != null) { + packageName = intent.getComponent().getPackageName(); + } else { + // queryIntentActivities does not convert user id, so we convert it here first + int userIdForQuery = mInternal.mUserController.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), mUserId, false, + ALLOW_NON_FULL, "ActivityManagerShellCommand", null); + List activities = mPm.queryIntentActivities(intent, mimeType, + 0, userIdForQuery).getList(); + if (activities == null || activities.size() <= 0) { + getErrPrintWriter().println("Error: Intent does not match any activities: " + + intent); + return 1; + } else if (activities.size() > 1) { + getErrPrintWriter().println( + "Error: Intent matches multiple activities; can't stop: " + + intent); + return 1; + } + packageName = activities.get(0).activityInfo.packageName; + } + pw.println("Stopping: " + packageName); + pw.flush(); + mInterface.forceStopPackage(packageName, mUserId); + try { + Thread.sleep(250); + } catch (InterruptedException e) { + } + } + + ProfilerInfo profilerInfo = null; + + if (mProfileFile != null || mAgent != null) { + ParcelFileDescriptor fd = null; + if (mProfileFile != null) { + fd = openFileForSystem(mProfileFile, "w"); + if (fd == null) { + return 1; + } + } + profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, + mStreaming, mAgent, mAttachAgentDuringBind, mClockType); + } + + pw.println("Starting: " + intent); + pw.flush(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + WaitResult result = null; + int res; + final long startTime = SystemClock.uptimeMillis(); + ActivityOptions options = null; + if (mDisplayId != INVALID_DISPLAY) { + options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(mDisplayId); + } + if (mTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLaunchTaskDisplayAreaFeatureId(mTaskDisplayAreaFeatureId); + } + if (mWindowingMode != WINDOWING_MODE_UNDEFINED) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLaunchWindowingMode(mWindowingMode); + } + if (mActivityType != ACTIVITY_TYPE_UNDEFINED) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLaunchActivityType(mActivityType); + } + if (mTaskId != INVALID_TASK_ID) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLaunchTaskId(mTaskId); + + if (mIsTaskOverlay) { + options.setTaskOverlay(true, true /* canResume */); + } + } + if (mIsLockTask) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLockTaskEnabled(true); + } + if (mShowSplashScreen) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); + } + if (mDismissKeyguardIfInsecure) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setDismissKeyguardIfInsecure(); + } + if (mWaitOption) { + result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent, + mimeType, null, null, 0, mStartFlags, profilerInfo, + options != null ? options.toBundle() : null, mUserId); + res = result.result; + } else { + res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null, + intent, mimeType, null, null, 0, mStartFlags, profilerInfo, + options != null ? options.toBundle() : null, mUserId); + } + final long endTime = SystemClock.uptimeMillis(); + PrintWriter out = mWaitOption ? pw : getErrPrintWriter(); + boolean launched = false; + switch (res) { + case ActivityManager.START_SUCCESS: + launched = true; + break; + case ActivityManager.START_SWITCHES_CANCELED: + launched = true; + out.println( + "Warning: Activity not started because the " + + " current activity is being kept for the user."); + break; + case ActivityManager.START_DELIVERED_TO_TOP: + launched = true; + out.println( + "Warning: Activity not started, intent has " + + "been delivered to currently running " + + "top-most instance."); + break; + case ActivityManager.START_RETURN_INTENT_TO_CALLER: + launched = true; + out.println( + "Warning: Activity not started because intent " + + "should be handled by the caller"); + break; + case ActivityManager.START_TASK_TO_FRONT: + launched = true; + out.println( + "Warning: Activity not started, its current " + + "task has been brought to the front"); + break; + case ActivityManager.START_INTENT_NOT_RESOLVED: + out.println( + "Error: Activity not started, unable to " + + "resolve " + intent.toString()); + return 1; + case ActivityManager.START_CLASS_NOT_FOUND: + out.println(NO_CLASS_ERROR_CODE); + out.println("Error: Activity class " + + intent.getComponent().toShortString() + + " does not exist."); + return 1; + case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: + out.println( + "Error: Activity not started, you requested to " + + "both forward and receive its result"); + return 1; + case ActivityManager.START_PERMISSION_DENIED: + out.println( + "Error: Activity not started, you do not " + + "have permission to access it."); + return 1; + case ActivityManager.START_NOT_VOICE_COMPATIBLE: + out.println( + "Error: Activity not started, voice control not allowed for: " + + intent); + return 1; + case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY: + out.println( + "Error: Not allowed to start background user activity" + + " that shouldn't be displayed for all users."); + return 1; + default: + out.println( + "Error: Activity not started, unknown error code " + res); + return 1; + } + out.flush(); + if (mWaitOption && launched) { + if (result == null) { + result = new WaitResult(); + result.who = intent.getComponent(); + } + pw.println("Status: " + (result.timeout ? "timeout" : "ok")); + pw.println("LaunchState: " + launchStateToString(result.launchState)); + if (result.who != null) { + pw.println("Activity: " + result.who.flattenToShortString()); + } + if (result.totalTime >= 0) { + pw.println("TotalTime: " + result.totalTime); + } + pw.println("WaitTime: " + (endTime-startTime)); + pw.println("Complete"); + pw.flush(); + } + mRepeat--; + if (mRepeat > 0) { + mTaskInterface.unhandledBack(); + } + } while (mRepeat > 0); + return 0; + } + + int runStartService(PrintWriter pw, boolean asForeground) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (mUserId == UserHandle.USER_ALL) { + err.println("Error: Can't start activity with user 'all'"); + return -1; + } + pw.println("Starting service: " + intent); + pw.flush(); + ComponentName cn = mInterface.startService(null, intent, intent.getType(), + asForeground, SHELL_PACKAGE_NAME, null, mUserId); + if (cn == null) { + err.println("Error: Not found; no service started."); + return -1; + } else if (cn.getPackageName().equals("!")) { + err.println("Error: Requires permission " + cn.getClassName()); + return -1; + } else if (cn.getPackageName().equals("!!")) { + err.println("Error: " + cn.getClassName()); + return -1; + } else if (cn.getPackageName().equals("?")) { + err.println("Error: " + cn.getClassName()); + return -1; + } + return 0; + } + + int runStopService(PrintWriter pw) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (mUserId == UserHandle.USER_ALL) { + err.println("Error: Can't stop activity with user 'all'"); + return -1; + } + pw.println("Stopping service: " + intent); + pw.flush(); + int result = mInterface.stopService(null, intent, intent.getType(), mUserId); + if (result == 0) { + err.println("Service not stopped: was not running."); + return -1; + } else if (result == 1) { + err.println("Service stopped"); + return -1; + } else if (result == -1) { + err.println("Error stopping service"); + return -1; + } + return 0; + } + + final static class IntentReceiver extends IIntentReceiver.Stub { + private final PrintWriter mPw; + private boolean mFinished = false; + + IntentReceiver(PrintWriter pw) { + mPw = pw; + } + + @Override + public void performReceive(Intent intent, int resultCode, String data, Bundle extras, + boolean ordered, boolean sticky, int sendingUser) { + String line = "Broadcast completed: result=" + resultCode; + if (data != null) line = line + ", data=\"" + data + "\""; + if (extras != null) line = line + ", extras: " + extras; + mPw.println(line); + mPw.flush(); + synchronized (this) { + mFinished = true; + notifyAll(); + } + } + + public synchronized void waitForFinish() { + try { + while (!mFinished) wait(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + + int runSendBroadcast(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + intent.addFlags(Intent.FLAG_RECEIVER_FROM_SHELL); + IntentReceiver receiver = new IntentReceiver(pw); + String[] requiredPermissions = mReceiverPermission == null ? null + : new String[] {mReceiverPermission}; + pw.println("Broadcasting: " + intent); + pw.flush(); + Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle(); + final int result = mInterface.broadcastIntentWithFeature(null, null, intent, null, + receiver, 0, null, null, requiredPermissions, null, null, + android.app.AppOpsManager.OP_NONE, bundle, true, false, mUserId); + Slogf.i(TAG, "Enqueued broadcast %s: " + result, intent); + if (result == ActivityManager.BROADCAST_SUCCESS && !mAsync) { + receiver.waitForFinish(); + } + return 0; + } + + int runTraceIpc(PrintWriter pw) throws RemoteException { + String op = getNextArgRequired(); + if (op.equals("start")) { + return runTraceIpcStart(pw); + } else if (op.equals("stop")) { + return runTraceIpcStop(pw); + } else { + getErrPrintWriter().println("Error: unknown trace ipc command '" + op + "'"); + return -1; + } + } + + int runTraceIpcStart(PrintWriter pw) throws RemoteException { + pw.println("Starting IPC tracing."); + pw.flush(); + mInterface.startBinderTracking(); + return 0; + } + + int runTraceIpcStop(PrintWriter pw) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + String opt; + String filename = null; + while ((opt=getNextOption()) != null) { + if (opt.equals("--dump-file")) { + filename = getNextArgRequired(); + } else { + err.println("Error: Unknown option: " + opt); + return -1; + } + } + if (filename == null) { + err.println("Error: Specify filename to dump logs to."); + return -1; + } + + // Writes an error message to stderr on failure + ParcelFileDescriptor fd = openFileForSystem(filename, "w"); + if (fd == null) { + return -1; + } + + if (!mInterface.stopBinderTrackingAndDump(fd)) { + err.println("STOP TRACE FAILED."); + return -1; + } + + pw.println("Stopped IPC tracing. Dumping logs to: " + filename); + return 0; + } + + // NOTE: current profiles can only be started on default display (even on automotive builds with + // passenger displays), so there's no need to pass a display-id + private int runProfile(PrintWriter pw) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + String profileFile = null; + boolean start = false; + int userId = UserHandle.USER_CURRENT; + int profileType = 0; + mSamplingInterval = 0; + mStreaming = false; + mClockType = ProfilerInfo.CLOCK_TYPE_DEFAULT; + + String process = null; + + String cmd = getNextArgRequired(); + + if ("start".equals(cmd)) { + start = true; + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else if (opt.equals("--clock-type")) { + String clock_type = getNextArgRequired(); + mClockType = ProfilerInfo.getClockTypeFromString(clock_type); + } else if (opt.equals("--streaming")) { + mStreaming = true; + } else if (opt.equals("--sampling")) { + mSamplingInterval = Integer.parseInt(getNextArgRequired()); + } else { + err.println("Error: Unknown option: " + opt); + return -1; + } + } + process = getNextArgRequired(); + } else if ("stop".equals(cmd)) { + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + err.println("Error: Unknown option: " + opt); + return -1; + } + } + process = getNextArgRequired(); + } else { + // Compatibility with old syntax: process is specified first. + process = cmd; + cmd = getNextArgRequired(); + if ("start".equals(cmd)) { + start = true; + } else if (!"stop".equals(cmd)) { + throw new IllegalArgumentException("Profile command " + process + " not valid"); + } + } + + if (userId == UserHandle.USER_ALL) { + err.println("Error: Can't profile with user 'all'"); + return -1; + } + + ParcelFileDescriptor fd = null; + ProfilerInfo profilerInfo = null; + + if (start) { + profileFile = getNextArgRequired(); + fd = openFileForSystem(profileFile, "w"); + if (fd == null) { + return -1; + } + profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming, + null, false, mClockType); + } + + if (!mInterface.profileControl(process, userId, start, profilerInfo, profileType)) { + err.println("PROFILE FAILED on process " + process); + return -1; + } + return 0; + } + + @NeverCompile + int runCompact(PrintWriter pw) throws RemoteException { + ProcessRecord app; + String op = getNextArgRequired(); + boolean isFullCompact = op.equals("full"); + boolean isSomeCompact = op.equals("some"); + if (isFullCompact || isSomeCompact) { + app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Process record found pid: " + app.mPid); + if (isFullCompact) { + pw.println("Executing full compaction for " + app.mPid); + synchronized (mInternal.mProcLock) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app, + CachedAppOptimizer.CompactProfile.FULL, + CachedAppOptimizer.CompactSource.SHELL, true); + } + pw.println("Finished full compaction for " + app.mPid); + } else if (isSomeCompact) { + pw.println("Executing some compaction for " + app.mPid); + synchronized (mInternal.mProcLock) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app, + CachedAppOptimizer.CompactProfile.SOME, + CachedAppOptimizer.CompactSource.SHELL, true); + } + pw.println("Finished some compaction for " + app.mPid); + } + } else if (op.equals("system")) { + pw.println("Executing system compaction"); + synchronized (mInternal.mProcLock) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactAllSystem(); + } + pw.println("Finished system compaction"); + } else if (op.equals("native")) { + op = getNextArgRequired(); + isFullCompact = op.equals("full"); + isSomeCompact = op.equals("some"); + int pid; + String pidStr = getNextArgRequired(); + try { + pid = Integer.parseInt(pidStr); + } catch (Exception e) { + getErrPrintWriter().println("Error: failed to parse '" + pidStr + "' as a PID"); + return -1; + } + if (isFullCompact) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative( + CachedAppOptimizer.CompactProfile.FULL, pid); + } else if (isSomeCompact) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative( + CachedAppOptimizer.CompactProfile.SOME, pid); + } else { + getErrPrintWriter().println("Error: unknown compaction type '" + op + "'"); + return -1; + } + } else { + getErrPrintWriter().println("Error: unknown compact command '" + op + "'"); + return -1; + } + + return 0; + } + + @NeverCompile + int runFreeze(PrintWriter pw) throws RemoteException { + String freezerOpt = getNextOption(); + boolean isSticky = false; + if (freezerOpt != null) { + isSticky = freezerOpt.equals("--sticky"); + } + ProcessRecord app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky); + synchronized (mInternal) { + synchronized (mInternal.mProcLock) { + app.mOptRecord.setFreezeSticky(isSticky); + mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true); + } + } + return 0; + } + + @NeverCompile + int runUnfreeze(PrintWriter pw) throws RemoteException { + String freezerOpt = getNextOption(); + boolean isSticky = false; + if (freezerOpt != null) { + isSticky = freezerOpt.equals("--sticky"); + } + ProcessRecord app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Unfreezing pid: " + app.mPid); + synchronized (mInternal) { + synchronized (mInternal.mProcLock) { + synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) { + app.mOptRecord.setFreezeSticky(isSticky); + mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0, + true); + } + } + } + return 0; + } + + /** + * Parses from the shell the process name and user id if provided and provides the corresponding + * {@link ProcessRecord)} If no user is provided, it will fallback to current user. + * Example usage: {@code --user current} or {@code } + * @return process record of process, null if none found. + * @throws RemoteException + */ + @NeverCompile + ProcessRecord getProcessFromShell() throws RemoteException { + ProcessRecord app; + String processName = getNextArgRequired(); + synchronized (mInternal.mProcLock) { + // Default to current user + int userId = getUserIdFromShellOrFallback(); + final int uid = + mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); + app = mInternal.getProcessRecordLocked(processName, uid); + } + return app; + } + + /** + * @return User id from command line provided in the form of + * {@code --user } and if the argument is not found it will fallback + * to current user. + * @throws RemoteException + */ + @NeverCompile + int getUserIdFromShellOrFallback() throws RemoteException { + int userId = mInterface.getCurrentUserId(); + String userOpt = getNextOption(); + if (userOpt != null && "--user".equals(userOpt)) { + int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); + if (inputUserId != UserHandle.USER_CURRENT) { + userId = inputUserId; + } + } + return userId; + } + + int runDumpHeap(PrintWriter pw) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + boolean managed = true; + boolean mallocInfo = false; + int userId = UserHandle.USER_CURRENT; + boolean runGc = false; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + if (userId == UserHandle.USER_ALL) { + err.println("Error: Can't dump heap with user 'all'"); + return -1; + } + } else if (opt.equals("-n")) { + managed = false; + } else if (opt.equals("-g")) { + runGc = true; + } else if (opt.equals("-m")) { + managed = false; + mallocInfo = true; + } else { + err.println("Error: Unknown option: " + opt); + return -1; + } + } + String process = getNextArgRequired(); + String heapFile = getNextArg(); + if (heapFile == null) { + LocalDateTime localDateTime = LocalDateTime.now(Clock.systemDefaultZone()); + String logNameTimeString = LOG_NAME_TIME_FORMATTER.format(localDateTime); + heapFile = "/data/local/tmp/heapdump-" + logNameTimeString + ".prof"; + } + + // Writes an error message to stderr on failure + ParcelFileDescriptor fd = openFileForSystem(heapFile, "w"); + if (fd == null) { + return -1; + } + + pw.println("File: " + heapFile); + pw.flush(); + + final CountDownLatch latch = new CountDownLatch(1); + + final RemoteCallback finishCallback = new RemoteCallback(new OnResultListener() { + @Override + public void onResult(Bundle result) { + latch.countDown(); + } + }, null); + + if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, heapFile, fd, + finishCallback)) { + err.println("HEAP DUMP FAILED on process " + process); + return -1; + } + pw.println("Waiting for dump to finish..."); + pw.flush(); + try { + latch.await(); + } catch (InterruptedException e) { + err.println("Caught InterruptedException"); + } + + return 0; + } + + int runSetDebugApp(PrintWriter pw) throws RemoteException { + boolean wait = false; + boolean persistent = false; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("-w")) { + wait = true; + } else if (opt.equals("--persistent")) { + persistent = true; + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + String pkg = getNextArgRequired(); + mInterface.setDebugApp(pkg, wait, persistent); + return 0; + } + + int runSetAgentApp(PrintWriter pw) throws RemoteException { + String pkg = getNextArgRequired(); + String agent = getNextArg(); + mInterface.setAgentApp(pkg, agent); + return 0; + } + + int runClearDebugApp(PrintWriter pw) throws RemoteException { + mInterface.setDebugApp(null, false, true); + return 0; + } + + int runSetWatchHeap(PrintWriter pw) throws RemoteException { + String proc = getNextArgRequired(); + String limit = getNextArgRequired(); + mInterface.setDumpHeapDebugLimit(proc, 0, Long.parseLong(limit), null); + return 0; + } + + int runClearWatchHeap(PrintWriter pw) throws RemoteException { + String proc = getNextArgRequired(); + mInterface.setDumpHeapDebugLimit(proc, 0, -1, null); + return 0; + } + + int runClearStartInfo(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + "runClearStartInfo()"); + String opt; + int userId = UserHandle.USER_CURRENT; + String packageName = null; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + packageName = opt; + } + } + if (userId == UserHandle.USER_CURRENT) { + UserInfo user = mInterface.getCurrentUser(); + if (user == null) { + return -1; + } + userId = user.id; + } + mInternal.mProcessList.getAppStartInfoTracker() + .clearHistoryProcessStartInfo(packageName, userId); + return 0; + } + + int runClearExitInfo(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + "runClearExitInfo()"); + String opt; + int userId = UserHandle.USER_CURRENT; + String packageName = null; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + packageName = opt; + } + } + if (userId == UserHandle.USER_CURRENT) { + UserInfo user = mInterface.getCurrentUser(); + if (user == null) { + return -1; + } + userId = user.id; + } + mInternal.mProcessList.mAppExitInfoTracker.clearHistoryProcessExitInfo(packageName, userId); + return 0; + } + + int runBugReport(PrintWriter pw) throws RemoteException { + String opt; + boolean fullBugreport = true; + while ((opt=getNextOption()) != null) { + if (opt.equals("--progress")) { + fullBugreport = false; + mInterface.requestInteractiveBugReport(); + } else if (opt.equals("--telephony")) { + fullBugreport = false; + // no title and description specified + mInterface.requestTelephonyBugReport("" /* no title */, "" /* no descriptions */); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + if (fullBugreport) { + mInterface.requestFullBugReport(); + } + pw.println("Your lovely bug report is being created; please be patient."); + return 0; + } + + int runForceStop(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + mInterface.forceStopPackage(getNextArgRequired(), userId); + return 0; + } + + int runStopApp(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + mInterface.stopAppForUser(getNextArgRequired(), userId); + return 0; + } + + int runClearRecentApps(PrintWriter pw) throws RemoteException { + mTaskInterface.removeAllVisibleRecentTasks(); + return 0; + } + + int runFgsNotificationRateLimit(PrintWriter pw) throws RemoteException { + final String toggleValue = getNextArgRequired(); + final boolean enable; + switch (toggleValue) { + case "enable": + enable = true; + break; + case "disable": + enable = false; + break; + default: + throw new IllegalArgumentException( + "Argument must be either 'enable' or 'disable'"); + } + mInterface.enableFgsNotificationRateLimit(enable); + return 0; + } + + int runCrash(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + int pid = -1; + String packageName = null; + final String arg = getNextArgRequired(); + // The argument is either a pid or a package name + try { + pid = Integer.parseInt(arg); + } catch (NumberFormatException e) { + packageName = arg; + } + + int[] userIds = (userId == UserHandle.USER_ALL) ? mInternal.mUserController.getUserIds() + : new int[]{userId}; + for (int id : userIds) { + if (mInternal.mUserController.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, id)) { + getOutPrintWriter().println( + "Shell does not have permission to crash packages for user " + id); + continue; + } + mInterface.crashApplicationWithType(-1, pid, packageName, id, "shell-induced crash", + false, CrashedByAdbException.TYPE_ID); + } + return 0; + } + + int runKill(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + mInterface.killBackgroundProcesses(getNextArgRequired(), userId); + return 0; + } + + int runKillAll(PrintWriter pw) throws RemoteException { + mInterface.killAllBackgroundProcesses(); + return 0; + } + + int runMakeIdle(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + mInterface.makePackageIdle(getNextArgRequired(), userId); + return 0; + } + + int runSetDeterministicUidIdle(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + boolean deterministic = Boolean.parseBoolean(getNextArgRequired()); + mInterface.setDeterministicUidIdle(deterministic); + return 0; + } + + static final class MyActivityController extends IActivityController.Stub { + final IActivityManager mInterface; + final PrintWriter mPw; + final InputStream mInput; + final String mGdbPort; + final boolean mMonkey; + final boolean mSimpleMode; + final String mTarget; + final boolean mAlwaysContinue; + final boolean mAlwaysKill; + + static final int STATE_NORMAL = 0; + static final int STATE_CRASHED = 1; + static final int STATE_EARLY_ANR = 2; + static final int STATE_ANR = 3; + + int mState; + + static final int RESULT_DEFAULT = 0; + + static final int RESULT_CRASH_DIALOG = 0; + static final int RESULT_CRASH_KILL = 1; + + static final int RESULT_EARLY_ANR_CONTINUE = 0; + static final int RESULT_EARLY_ANR_KILL = 1; + + static final int RESULT_ANR_DIALOG = 0; + static final int RESULT_ANR_KILL = 1; + static final int RESULT_ANR_WAIT = 2; + + int mResult; + + Process mGdbProcess; + Thread mGdbThread; + boolean mGotGdbPrint; + + MyActivityController(IActivityManager iam, PrintWriter pw, InputStream input, + String gdbPort, boolean monkey, boolean simpleMode, String target, + boolean alwaysContinue, boolean alwaysKill) { + mInterface = iam; + mPw = pw; + mInput = input; + mGdbPort = gdbPort; + mMonkey = monkey; + mSimpleMode = simpleMode; + mTarget = target; + mAlwaysContinue = alwaysContinue; + mAlwaysKill = alwaysKill; + } + + private boolean shouldHandlePackageOrProcess(String packageOrProcess) { + if (mTarget == null) { + return true; // Always handle all packages / processes. + } + return mTarget.equals(packageOrProcess); + } + + @Override + public boolean activityResuming(String pkg) { + if (!shouldHandlePackageOrProcess(pkg)) { + return true; + } + synchronized (this) { + mPw.println("** Activity resuming: " + pkg); + mPw.flush(); + } + return true; + } + + @Override + public boolean activityStarting(Intent intent, String pkg) { + if (!shouldHandlePackageOrProcess(pkg)) { + return true; + } + synchronized (this) { + mPw.println("** Activity starting: " + pkg); + mPw.flush(); + } + return true; + } + + @Override + public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, + long timeMillis, String stackTrace) { + if (!shouldHandlePackageOrProcess(processName)) { + return true; // Don't kill + } + synchronized (this) { + if (mSimpleMode) { + mPw.println("** PROCESS CRASHED: " + processName); + } else { + mPw.println("** ERROR: PROCESS CRASHED"); + mPw.println("processName: " + processName); + mPw.println("processPid: " + pid); + mPw.println("shortMsg: " + shortMsg); + mPw.println("longMsg: " + longMsg); + mPw.println("timeMillis: " + timeMillis); + mPw.println("uptime: " + SystemClock.uptimeMillis()); + mPw.println("stack:"); + mPw.print(stackTrace); + mPw.println("#"); + } + mPw.flush(); + if (mAlwaysContinue) { + return true; + } + if (mAlwaysKill) { + return false; + } + int result = waitControllerLocked(pid, STATE_CRASHED); + return result == RESULT_CRASH_KILL ? false : true; + } + } + + @Override + public int appEarlyNotResponding(String processName, int pid, String annotation) { + if (!shouldHandlePackageOrProcess(processName)) { + return 0; // Continue + } + synchronized (this) { + if (mSimpleMode) { + mPw.println("** EARLY PROCESS NOT RESPONDING: " + processName); + } else { + mPw.println("** ERROR: EARLY PROCESS NOT RESPONDING"); + mPw.println("processName: " + processName); + mPw.println("processPid: " + pid); + mPw.println("annotation: " + annotation); + mPw.println("uptime: " + SystemClock.uptimeMillis()); + } + mPw.flush(); + if (mAlwaysContinue) { + return 0; + } + if (mAlwaysKill) { + return -1; + } + int result = waitControllerLocked(pid, STATE_EARLY_ANR); + if (result == RESULT_EARLY_ANR_KILL) return -1; + return 0; + } + } + + @Override + public int appNotResponding(String processName, int pid, String processStats) { + if (!shouldHandlePackageOrProcess(processName)) { + return 0; // Default == show dialog + } + synchronized (this) { + if (mSimpleMode) { + mPw.println("** PROCESS NOT RESPONDING: " + processName); + } else { + mPw.println("** ERROR: PROCESS NOT RESPONDING"); + mPw.println("processName: " + processName); + mPw.println("processPid: " + pid); + mPw.println("uptime: " + SystemClock.uptimeMillis()); + mPw.println("processStats:"); + mPw.print(processStats); + mPw.println("#"); + } + mPw.flush(); + if (mAlwaysContinue) { + return 0; + } + if (mAlwaysKill) { + return -1; + } + int result = waitControllerLocked(pid, STATE_ANR); + if (result == RESULT_ANR_KILL) return -1; + if (result == RESULT_ANR_WAIT) return 1; + return 0; + } + } + + @Override + public int systemNotResponding(String message) { + if (mTarget != null) { + return -1; // If any target is set, just return. + } + synchronized (this) { + mPw.println("** ERROR: PROCESS NOT RESPONDING"); + if (!mSimpleMode) { + mPw.println("message: " + message); + mPw.println("#"); + mPw.println("Allowing system to die."); + } + mPw.flush(); + return -1; + } + } + + void killGdbLocked() { + mGotGdbPrint = false; + if (mGdbProcess != null) { + mPw.println("Stopping gdbserver"); + mPw.flush(); + mGdbProcess.destroy(); + mGdbProcess = null; + } + if (mGdbThread != null) { + mGdbThread.interrupt(); + mGdbThread = null; + } + } + + int waitControllerLocked(int pid, int state) { + if (mGdbPort != null) { + killGdbLocked(); + + try { + mPw.println("Starting gdbserver on port " + mGdbPort); + mPw.println("Do the following:"); + mPw.println(" adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort); + mPw.println(" gdbclient app_process :" + mGdbPort); + mPw.flush(); + + mGdbProcess = Runtime.getRuntime().exec(new String[] { + "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid) + }); + final InputStreamReader converter = new InputStreamReader( + mGdbProcess.getInputStream()); + mGdbThread = new Thread() { + @Override + public void run() { + BufferedReader in = new BufferedReader(converter); + String line; + int count = 0; + while (true) { + synchronized (MyActivityController.this) { + if (mGdbThread == null) { + return; + } + if (count == 2) { + mGotGdbPrint = true; + MyActivityController.this.notifyAll(); + } + } + try { + line = in.readLine(); + if (line == null) { + return; + } + mPw.println("GDB: " + line); + mPw.flush(); + count++; + } catch (IOException e) { + return; + } + } + } + }; + mGdbThread.start(); + + // Stupid waiting for .5s. Doesn't matter if we end early. + try { + this.wait(500); + } catch (InterruptedException e) { + } + + } catch (IOException e) { + mPw.println("Failure starting gdbserver: " + e); + mPw.flush(); + killGdbLocked(); + } + } + mState = state; + mPw.println(""); + printMessageForState(); + mPw.flush(); + + while (mState != STATE_NORMAL) { + try { + wait(); + } catch (InterruptedException e) { + } + } + + killGdbLocked(); + + return mResult; + } + + void resumeController(int result) { + synchronized (this) { + mState = STATE_NORMAL; + mResult = result; + notifyAll(); + } + } + + void printMessageForState() { + if ((mAlwaysContinue || mAlwaysKill) && mSimpleMode) { + return; // In the simplest mode, we don't need to show anything. + } + switch (mState) { + case STATE_NORMAL: + mPw.println("Monitoring activity manager... available commands:"); + break; + case STATE_CRASHED: + mPw.println("Waiting after crash... available commands:"); + mPw.println("(c)ontinue: show crash dialog"); + mPw.println("(k)ill: immediately kill app"); + break; + case STATE_EARLY_ANR: + mPw.println("Waiting after early ANR... available commands:"); + mPw.println("(c)ontinue: standard ANR processing"); + mPw.println("(k)ill: immediately kill app"); + break; + case STATE_ANR: + mPw.println("Waiting after ANR... available commands:"); + mPw.println("(c)ontinue: show ANR dialog"); + mPw.println("(k)ill: immediately kill app"); + mPw.println("(w)ait: wait some more"); + break; + } + mPw.println("(q)uit: finish monitoring"); + } + + void run() throws RemoteException { + try { + printMessageForState(); + mPw.flush(); + + mInterface.setActivityController(this, mMonkey); + mState = STATE_NORMAL; + + InputStreamReader converter = new InputStreamReader(mInput); + BufferedReader in = new BufferedReader(converter); + String line; + + while ((line = in.readLine()) != null) { + boolean addNewline = true; + if (line.length() <= 0) { + addNewline = false; + } else if ("q".equals(line) || "quit".equals(line)) { + resumeController(RESULT_DEFAULT); + break; + } else if (mState == STATE_CRASHED) { + if ("c".equals(line) || "continue".equals(line)) { + resumeController(RESULT_CRASH_DIALOG); + } else if ("k".equals(line) || "kill".equals(line)) { + resumeController(RESULT_CRASH_KILL); + } else { + mPw.println("Invalid command: " + line); + } + } else if (mState == STATE_ANR) { + if ("c".equals(line) || "continue".equals(line)) { + resumeController(RESULT_ANR_DIALOG); + } else if ("k".equals(line) || "kill".equals(line)) { + resumeController(RESULT_ANR_KILL); + } else if ("w".equals(line) || "wait".equals(line)) { + resumeController(RESULT_ANR_WAIT); + } else { + mPw.println("Invalid command: " + line); + } + } else if (mState == STATE_EARLY_ANR) { + if ("c".equals(line) || "continue".equals(line)) { + resumeController(RESULT_EARLY_ANR_CONTINUE); + } else if ("k".equals(line) || "kill".equals(line)) { + resumeController(RESULT_EARLY_ANR_KILL); + } else { + mPw.println("Invalid command: " + line); + } + } else { + mPw.println("Invalid command: " + line); + } + + synchronized (this) { + if (addNewline) { + mPw.println(""); + } + printMessageForState(); + mPw.flush(); + } + } + + } catch (IOException e) { + e.printStackTrace(mPw); + mPw.flush(); + } finally { + mInterface.setActivityController(null, mMonkey); + } + } + } + + int runMonitor(PrintWriter pw) throws RemoteException { + String opt; + String gdbPort = null; + boolean monkey = false; + boolean simpleMode = false; + boolean alwaysContinue = false; + boolean alwaysKill = false; + String target = null; + + while ((opt=getNextOption()) != null) { + if (opt.equals("--gdb")) { + gdbPort = getNextArgRequired(); + } else if (opt.equals("-p")) { + target = getNextArgRequired(); + } else if (opt.equals("-m")) { + monkey = true; + } else if (opt.equals("-s")) { + simpleMode = true; + } else if (opt.equals("-c")) { + alwaysContinue = true; + } else if (opt.equals("-k")) { + alwaysKill = true; + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + if (alwaysContinue && alwaysKill) { + getErrPrintWriter().println("Error: -k and -c options can't be used together."); + return -1; + } + + MyActivityController controller = new MyActivityController(mInterface, pw, + getRawInputStream(), gdbPort, monkey, simpleMode, target, alwaysContinue, + alwaysKill); + controller.run(); + return 0; + } + + static final class MyUidObserver extends UidObserver + implements ActivityManagerService.OomAdjObserver { + final IActivityManager mInterface; + final ActivityManagerService mInternal; + final PrintWriter mPw; + final InputStream mInput; + final int mUid; + + final int mMask; + + static final int STATE_NORMAL = 0; + + int mState; + + MyUidObserver(ActivityManagerService service, PrintWriter pw, InputStream input, int uid, + int mask) { + mInterface = service; + mInternal = service; + mPw = pw; + mInput = input; + mUid = uid; + mMask = mask; + } + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { + synchronized (this) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + mPw.print(uid); + mPw.print(" procstate "); + mPw.print(ProcessList.makeProcStateString(procState)); + mPw.print(" seq "); + mPw.print(procStateSeq); + mPw.print(" capability "); + mPw.println(capability & mMask); + mPw.flush(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + } + + @Override + public void onUidGone(int uid, boolean disabled) { + synchronized (this) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + mPw.print(uid); + mPw.print(" gone"); + if (disabled) { + mPw.print(" disabled"); + } + mPw.println(); + mPw.flush(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + } + + @Override + public void onUidActive(int uid) { + synchronized (this) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + mPw.print(uid); + mPw.println(" active"); + mPw.flush(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + } + + @Override + public void onUidIdle(int uid, boolean disabled) { + synchronized (this) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + mPw.print(uid); + mPw.print(" idle"); + if (disabled) { + mPw.print(" disabled"); + } + mPw.println(); + mPw.flush(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + } + + @Override + public void onUidCachedChanged(int uid, boolean cached) { + synchronized (this) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + mPw.print(uid); + mPw.println(cached ? " cached" : " uncached"); + mPw.flush(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + } + + @Override + public void onOomAdjMessage(String msg) { + synchronized (this) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + mPw.print("# "); + mPw.println(msg); + mPw.flush(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + } + + void printMessageForState() { + switch (mState) { + case STATE_NORMAL: + mPw.println("Watching uid states... available commands:"); + break; + } + mPw.println("(q)uit: finish watching"); + } + + void run() throws RemoteException { + try { + printMessageForState(); + mPw.flush(); + + mInterface.registerUidObserver(this, ActivityManager.UID_OBSERVER_ACTIVE + | ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE + | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_CACHED, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + if (mUid >= 0) { + mInternal.setOomAdjObserver(mUid, this); + } + mState = STATE_NORMAL; + + InputStreamReader converter = new InputStreamReader(mInput); + BufferedReader in = new BufferedReader(converter); + String line; + + while ((line = in.readLine()) != null) { + boolean addNewline = true; + if (line.length() <= 0) { + addNewline = false; + } else if ("q".equals(line) || "quit".equals(line)) { + break; + } else { + mPw.println("Invalid command: " + line); + } + + synchronized (this) { + if (addNewline) { + mPw.println(""); + } + printMessageForState(); + mPw.flush(); + } + } + + } catch (IOException e) { + e.printStackTrace(mPw); + mPw.flush(); + } finally { + if (mUid >= 0) { + mInternal.clearOomAdjObserver(); + } + mInterface.unregisterUidObserver(this); + } + } + } + + int runWatchUids(PrintWriter pw) throws RemoteException { + String opt; + int uid = -1; + + // Because a lot of CTS won't ignore capabilities newly added, we report + // only the following capabilities -- the ones we had on Android T -- by default. + int mask = PROCESS_CAPABILITY_FOREGROUND_LOCATION + | PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE + | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + + while ((opt=getNextOption()) != null) { + if (opt.equals("--oom")) { + uid = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--mask")) { + mask = Integer.parseInt(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + + } + } + + MyUidObserver controller = new MyUidObserver(mInternal, pw, getRawInputStream(), uid, mask); + controller.run(); + return 0; + } + + int runHang(PrintWriter pw) throws RemoteException { + String opt; + boolean allowRestart = false; + while ((opt=getNextOption()) != null) { + if (opt.equals("--allow-restart")) { + allowRestart = true; + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + pw.println("Hanging the system..."); + pw.flush(); + try { + mInterface.hang(getShellCallback().getShellCallbackBinder(), allowRestart); + } catch (NullPointerException e) { + pw.println("Hanging failed, since caller " + Binder.getCallingPid() + + " did not provide a ShellCallback!"); + pw.flush(); + return 1; + } + return 0; + } + + int runRestart(PrintWriter pw) throws RemoteException { + String opt; + while ((opt=getNextOption()) != null) { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + + pw.println("Restart the system..."); + pw.flush(); + mInterface.restart(); + return 0; + } + + int runIdleMaintenance(PrintWriter pw) throws RemoteException { + String opt; + while ((opt=getNextOption()) != null) { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + + pw.println("Performing idle maintenance..."); + mInterface.sendIdleJobTrigger(); + mInternal.performIdleMaintenance(); + return 0; + } + + int runScreenCompat(PrintWriter pw) throws RemoteException { + String mode = getNextArgRequired(); + boolean enabled; + if ("on".equals(mode)) { + enabled = true; + } else if ("off".equals(mode)) { + enabled = false; + } else { + getErrPrintWriter().println("Error: enabled mode must be 'on' or 'off' at " + mode); + return -1; + } + + String packageName = getNextArgRequired(); + do { + try { + mInterface.setPackageScreenCompatMode(packageName, enabled + ? ActivityManager.COMPAT_MODE_ENABLED + : ActivityManager.COMPAT_MODE_DISABLED); + } catch (RemoteException e) { + } + packageName = getNextArg(); + } while (packageName != null); + return 0; + } + + int runPackageImportance(PrintWriter pw) throws RemoteException { + String packageName = getNextArgRequired(); + int procState = mInterface.getPackageProcessState(packageName, "com.android.shell"); + pw.println(ActivityManager.RunningAppProcessInfo.procStateToImportance(procState)); + return 0; + } + + int runToUri(PrintWriter pw, int flags) throws RemoteException { + Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + pw.println(intent.toUri(flags)); + return 0; + } + + private boolean switchUserAndWaitForComplete(int userId) throws RemoteException { + UserInfo currentUser = mInterface.getCurrentUser(); + if (currentUser != null && userId == currentUser.id) { + // Already switched to the correct user, exit early. + return true; + } + + // Register switch observer. + final CountDownLatch switchLatch = new CountDownLatch(1); + final IUserSwitchObserver userSwitchObserver = new UserSwitchObserver() { + @Override + public void onUserSwitchComplete(int newUserId) { + if (userId == newUserId) { + switchLatch.countDown(); + } + } + }; + try { + mInterface.registerUserSwitchObserver(userSwitchObserver, + ActivityManagerShellCommand.class.getName()); + + // Switch. + boolean switched = mInterface.switchUser(userId); + if (!switched) { + // Switching failed, don't wait for the user switch observer. + return false; + } + + // Wait. + try { + switched = switchLatch.await(USER_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + getErrPrintWriter().println("Error: Thread interrupted unexpectedly."); + } + + return switched; + } finally { + mInterface.unregisterUserSwitchObserver(userSwitchObserver); + } + } + + int runSwitchUser(PrintWriter pw) throws RemoteException { + boolean wait = false; + String opt; + while ((opt = getNextOption()) != null) { + if ("-w".equals(opt)) { + wait = true; + } else { + getErrPrintWriter().println("Error: unknown option: " + opt); + return -1; + } + } + + int userId = Integer.parseInt(getNextArgRequired()); + + UserManager userManager = mInternal.mContext.getSystemService(UserManager.class); + final int userSwitchable = userManager.getUserSwitchability(UserHandle.of(userId)); + if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) { + getErrPrintWriter().println("Error: UserSwitchabilityResult=" + userSwitchable); + return -1; + } + + boolean switched; + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runSwitchUser"); + try { + if (wait) { + switched = switchUserAndWaitForComplete(userId); + } else { + switched = mInterface.switchUser(userId); + } + if (switched) { + return 0; + } else { + pw.printf("Error: Failed to switch to user %d\n", userId); + return 1; + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + + int runGetCurrentUser(PrintWriter pw) throws RemoteException { + int userId = mInterface.getCurrentUserId(); + if (userId == UserHandle.USER_NULL) { + throw new IllegalStateException("Current user not set"); + } + pw.println(userId); + return 0; + } + + int runStartUser(PrintWriter pw) throws RemoteException { + boolean wait = false; + String opt; + int displayId = Display.INVALID_DISPLAY; + while ((opt = getNextOption()) != null) { + switch(opt) { + case "-w": + wait = true; + break; + case "--display": + displayId = getDisplayIdFromNextArg(); + break; + default: + getErrPrintWriter().println("Error: unknown option: " + opt); + return -1; + } + } + final int userId = Integer.parseInt(getNextArgRequired()); + final ProgressWaiter waiter = wait ? new ProgressWaiter(userId) : null; + + // For backwards compatibility, if the user is a profile, we need to define whether it + // should be started visible (when its parent is the current user) or not (when it isn't) + final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + final ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); + final int parentUserId = umi.getProfileParentId(userId); + final int currentUserId = ami.getCurrentUserId(); + final boolean isProfile = parentUserId != userId; + final boolean isVisibleProfile = isProfile && parentUserId == currentUserId; + Slogf.d(TAG, "runStartUser(): userId=%d, parentUserId=%d, currentUserId=%d, isProfile=%b, " + + "isVisibleProfile=%b, display=%d, waiter=%s", userId, parentUserId, currentUserId, + isProfile, isVisibleProfile, displayId, waiter); + + boolean success; + String displaySuffix = ""; + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runStartUser" + userId); + try { + if (isVisibleProfile) { + Slogf.d(TAG, "calling startProfileWithListener(%d, %s)", userId, waiter); + // startProfileWithListener() will start the profile visible (as long its parent is + // the current user), while startUserInBackgroundWithListener() will always start + // the user (or profile) invisible + success = mInterface.startProfileWithListener(userId, waiter); + } else if (displayId == Display.INVALID_DISPLAY) { + Slogf.d(TAG, "calling startUserInBackgroundWithListener(%d)", userId); + success = mInterface.startUserInBackgroundWithListener(userId, waiter); + } else { + if (!UserManager.isVisibleBackgroundUsersEnabled()) { + pw.println("Not supported"); + return -1; + } + Slogf.d(TAG, "calling startUserInBackgroundVisibleOnDisplay(%d, %d, %s)", userId, + displayId, waiter); + success = mInterface.startUserInBackgroundVisibleOnDisplay(userId, displayId, + waiter); + displaySuffix = " on display " + displayId; + } + if (wait && success) { + Slogf.d(TAG, "waiting %d ms", USER_OPERATION_TIMEOUT_MS); + success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + if (success) { + pw.println("Success: user started" + displaySuffix); + } else { + getErrPrintWriter().println("Error: could not start user" + displaySuffix); + } + return 0; + } + + int runUnlockUser(PrintWriter pw) throws RemoteException { + int userId = Integer.parseInt(getNextArgRequired()); + + /* + * Originally this command required two more parameters: the hardware + * authentication token and secret needed to unlock the user. However, + * unlockUser() no longer takes a token parameter at all, and there + * isn't really any way for callers of this shell command to get the + * secret if one is needed (i.e., when the user has an LSKF), given that + * the secret must be a cryptographic key derived from the user's + * synthetic password. I.e. the secret is *not* the LSKF here, but + * rather an intermediate value several steps down the chain. + * + * As such, the only supported use for this command is unlocking a user + * who doesn't have an LSKF, with empty or unspecified token and secret. + * + * To preserve previous behavior, an exclamation mark ("!") is also + * accepted for these values; it means the same as an empty string. + */ + String token = getNextArg(); + if (!TextUtils.isEmpty(token) && !"!".equals(token)) { + getErrPrintWriter().println("Error: token parameter not supported"); + return -1; + } + String secret = getNextArg(); + if (!TextUtils.isEmpty(secret) && !"!".equals(secret)) { + getErrPrintWriter().println("Error: secret parameter not supported"); + return -1; + } + + boolean success = mInterface.unlockUser2(userId, null); + if (success) { + pw.println("Success: user unlocked"); + } else { + // TODO(b/218389026): we can reach here even if the user's storage + // was successfully unlocked. + getErrPrintWriter().println("Error: could not unlock user"); + } + return 0; + } + + static final class StopUserCallback extends IStopUserCallback.Stub { + private final @UserIdInt int mUserId; + private boolean mFinished = false; + + private StopUserCallback(@UserIdInt int userId) { + mUserId = userId; + } + + public synchronized void waitForFinish() { + try { + while (!mFinished) wait(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + Slogf.d(TAG, "user %d finished stopping", mUserId); + } + + @Override + public synchronized void userStopped(int userId) { + Slogf.d(TAG, "StopUserCallback: userStopped(%d)", userId); + mFinished = true; + notifyAll(); + } + + @Override + public synchronized void userStopAborted(int userId) { + Slogf.d(TAG, "StopUserCallback: userStopAborted(%d)", userId); + mFinished = true; + notifyAll(); + } + + @Override + public String toString() { + return "ProgressWaiter[userId=" + mUserId + ", finished=" + mFinished + "]"; + } + } + + int runStopUser(PrintWriter pw) throws RemoteException { + boolean wait = false; + boolean force = false; + String opt; + while ((opt = getNextOption()) != null) { + if ("-w".equals(opt)) { + wait = true; + } else if ("-f".equals(opt)) { + force = true; + } else { + getErrPrintWriter().println("Error: unknown option: " + opt); + return -1; + } + } + int userId = Integer.parseInt(getNextArgRequired()); + StopUserCallback callback = wait ? new StopUserCallback(userId) : null; + + Slogf.d(TAG, "Calling stopUser(%d, %b, %s)", userId, force, callback); + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "shell_runStopUser-" + userId + "-[stopUser]"); + try { + int res = mInterface.stopUser(userId, force, callback); + if (res != ActivityManager.USER_OP_SUCCESS) { + String txt = ""; + switch (res) { + case ActivityManager.USER_OP_IS_CURRENT: + txt = " (Can't stop current user)"; + break; + case ActivityManager.USER_OP_UNKNOWN_USER: + txt = " (Unknown user " + userId + ")"; + break; + case ActivityManager.USER_OP_ERROR_IS_SYSTEM: + txt = " (System user cannot be stopped)"; + break; + case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: + txt = " (Can't stop user " + userId + + " - one of its related users can't be stopped)"; + break; + } + getErrPrintWriter().println("Switch failed: " + res + txt); + return -1; + } else if (callback != null) { + callback.waitForFinish(); + } + return 0; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + + int runIsUserStopped(PrintWriter pw) { + int userId = UserHandle.parseUserArg(getNextArgRequired()); + boolean stopped = mInternal.isUserStopped(userId); + pw.println(stopped); + return 0; + } + + int runGetStartedUserState(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.DUMP, + "runGetStartedUserState()"); + final int userId = Integer.parseInt(getNextArgRequired()); + try { + pw.println(mInternal.getStartedUserState(userId)); + } catch (NullPointerException e) { + pw.println("User is not started: " + userId); + } + return 0; + } + + int runTrackAssociations(PrintWriter pw) { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "runTrackAssociations()"); + synchronized (mInternal) { + if (!mInternal.mTrackingAssociations) { + mInternal.mTrackingAssociations = true; + pw.println("Association tracking started."); + } else { + pw.println("Association tracking already enabled."); + } + } + return 0; + } + + int runUntrackAssociations(PrintWriter pw) { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "runUntrackAssociations()"); + synchronized (mInternal) { + if (mInternal.mTrackingAssociations) { + mInternal.mTrackingAssociations = false; + mInternal.mAssociations.clear(); + pw.println("Association tracking stopped."); + } else { + pw.println("Association tracking not running."); + } + } + return 0; + } + + int getUidState(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.DUMP, + "getUidState()"); + int state = mInternal.getUidState(Integer.parseInt(getNextArgRequired())); + pw.print(state); + pw.print(" ("); + pw.printf(DebugUtils.valueToString(ActivityManager.class, "PROCESS_STATE_", state)); + pw.println(")"); + return 0; + } + + private List getRecentConfigurations(int days) { + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + final long now = System.currentTimeMillis(); + final long nDaysAgo = now - (days * 24 * 60 * 60 * 1000); + try { + @SuppressWarnings("unchecked") + ParceledListSlice configStatsSlice = usm.queryConfigurationStats( + UsageStatsManager.INTERVAL_BEST, nDaysAgo, now, "com.android.shell"); + if (configStatsSlice == null) { + return Collections.emptyList(); + } + + final ArrayMap recentConfigs = new ArrayMap<>(); + final List configStatsList = configStatsSlice.getList(); + final int configStatsListSize = configStatsList.size(); + for (int i = 0; i < configStatsListSize; i++) { + final ConfigurationStats stats = configStatsList.get(i); + final int indexOfKey = recentConfigs.indexOfKey(stats.getConfiguration()); + if (indexOfKey < 0) { + recentConfigs.put(stats.getConfiguration(), stats.getActivationCount()); + } else { + recentConfigs.setValueAt(indexOfKey, + recentConfigs.valueAt(indexOfKey) + stats.getActivationCount()); + } + } + + final Comparator comparator = new Comparator() { + @Override + public int compare(Configuration a, Configuration b) { + return recentConfigs.get(b).compareTo(recentConfigs.get(a)); + } + }; + + ArrayList configs = new ArrayList<>(recentConfigs.size()); + configs.addAll(recentConfigs.keySet()); + Collections.sort(configs, comparator); + return configs; + + } catch (RemoteException e) { + return Collections.emptyList(); + } + } + + /** + * Adds all supported GL extensions for a provided EGLConfig to a set by creating an EGLContext + * and EGLSurface and querying extensions. + * + * @param egl An EGL API object + * @param display An EGLDisplay to create a context and surface with + * @param config The EGLConfig to get the extensions for + * @param surfaceSize eglCreatePbufferSurface generic parameters + * @param contextAttribs eglCreateContext generic parameters + * @param glExtensions A Set to add GL extensions to + */ + private static void addExtensionsForConfig( + EGL10 egl, + EGLDisplay display, + EGLConfig config, + int[] surfaceSize, + int[] contextAttribs, + Set glExtensions) { + // Create a context. + EGLContext context = + egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttribs); + // No-op if we can't create a context. + if (context == EGL10.EGL_NO_CONTEXT) { + return; + } + + // Create a surface. + EGLSurface surface = egl.eglCreatePbufferSurface(display, config, surfaceSize); + if (surface == EGL10.EGL_NO_SURFACE) { + egl.eglDestroyContext(display, context); + return; + } + + // Update the current surface and context. + egl.eglMakeCurrent(display, surface, surface, context); + + // Get the list of extensions. + String extensionList = GLES10.glGetString(GLES10.GL_EXTENSIONS); + if (!TextUtils.isEmpty(extensionList)) { + // The list of extensions comes from the driver separated by spaces. + // Split them apart and add them into a Set for deduping purposes. + for (String extension : extensionList.split(" ")) { + glExtensions.add(extension); + } + } + + // Tear down the context and surface for this config. + egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + egl.eglDestroySurface(display, surface); + egl.eglDestroyContext(display, context); + } + + + Set getGlExtensionsFromDriver() { + Set glExtensions = new HashSet<>(); + + // Get the EGL implementation. + EGL10 egl = (EGL10) EGLContext.getEGL(); + if (egl == null) { + getErrPrintWriter().println("Warning: couldn't get EGL"); + return glExtensions; + } + + // Get the default display and initialize it. + EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + int[] version = new int[2]; + egl.eglInitialize(display, version); + + // Call getConfigs() in order to find out how many there are. + int[] numConfigs = new int[1]; + if (!egl.eglGetConfigs(display, null, 0, numConfigs)) { + getErrPrintWriter().println("Warning: couldn't get EGL config count"); + return glExtensions; + } + + // Allocate space for all configs and ask again. + EGLConfig[] configs = new EGLConfig[numConfigs[0]]; + if (!egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) { + getErrPrintWriter().println("Warning: couldn't get EGL configs"); + return glExtensions; + } + + // Allocate surface size parameters outside of the main loop to cut down + // on GC thrashing. 1x1 is enough since we are only using it to get at + // the list of extensions. + int[] surfaceSize = + new int[] { + EGL10.EGL_WIDTH, 1, + EGL10.EGL_HEIGHT, 1, + EGL10.EGL_NONE + }; + + // For when we need to create a GLES2.0 context. + final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + int[] gles2 = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; + + // For getting return values from eglGetConfigAttrib + int[] attrib = new int[1]; + + for (int i = 0; i < numConfigs[0]; i++) { + // Get caveat for this config in order to skip slow (i.e. software) configs. + egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, attrib); + if (attrib[0] == EGL10.EGL_SLOW_CONFIG) { + continue; + } + + // If the config does not support pbuffers we cannot do an eglMakeCurrent + // on it in addExtensionsForConfig(), so skip it here. Attempting to make + // it current with a pbuffer will result in an EGL_BAD_MATCH error + egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_SURFACE_TYPE, attrib); + if ((attrib[0] & EGL10.EGL_PBUFFER_BIT) == 0) { + continue; + } + + final int EGL_OPENGL_ES_BIT = 0x0001; + final int EGL_OPENGL_ES2_BIT = 0x0004; + egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_RENDERABLE_TYPE, attrib); + if ((attrib[0] & EGL_OPENGL_ES_BIT) != 0) { + addExtensionsForConfig(egl, display, configs[i], surfaceSize, null, glExtensions); + } + if ((attrib[0] & EGL_OPENGL_ES2_BIT) != 0) { + addExtensionsForConfig(egl, display, configs[i], surfaceSize, gles2, glExtensions); + } + } + + // Release all EGL resources. + egl.eglTerminate(display); + + return glExtensions; + } + + private void writeDeviceConfig(ProtoOutputStream protoOutputStream, long fieldId, + PrintWriter pw, Configuration config, DisplayMetrics displayMetrics) { + long token = -1; + if (protoOutputStream != null) { + token = protoOutputStream.start(fieldId); + protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_WIDTH_PX, + displayMetrics.widthPixels); + protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_HEIGHT_PX, + displayMetrics.heightPixels); + protoOutputStream.write(DeviceConfigurationProto.STABLE_DENSITY_DPI, + DisplayMetrics.DENSITY_DEVICE_STABLE); + } + if (pw != null) { + pw.print("stable-width-px: "); pw.println(displayMetrics.widthPixels); + pw.print("stable-height-px: "); pw.println(displayMetrics.heightPixels); + pw.print("stable-density-dpi: "); pw.println(DisplayMetrics.DENSITY_DEVICE_STABLE); + } + + MemInfoReader memreader = new MemInfoReader(); + memreader.readMemInfo(); + KeyguardManager kgm = mInternal.mContext.getSystemService(KeyguardManager.class); + if (protoOutputStream != null) { + protoOutputStream.write(DeviceConfigurationProto.TOTAL_RAM, memreader.getTotalSize()); + protoOutputStream.write(DeviceConfigurationProto.LOW_RAM, + ActivityManager.isLowRamDeviceStatic()); + protoOutputStream.write(DeviceConfigurationProto.MAX_CORES, + Runtime.getRuntime().availableProcessors()); + protoOutputStream.write(DeviceConfigurationProto.HAS_SECURE_SCREEN_LOCK, + kgm.isDeviceSecure()); + } + if (pw != null) { + pw.print("total-ram: "); pw.println(memreader.getTotalSize()); + pw.print("low-ram: "); pw.println(ActivityManager.isLowRamDeviceStatic()); + pw.print("max-cores: "); pw.println(Runtime.getRuntime().availableProcessors()); + pw.print("has-secure-screen-lock: "); pw.println(kgm.isDeviceSecure()); + } + + ConfigurationInfo configInfo = null; + try { + configInfo = mTaskInterface.getDeviceConfigurationInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) { + if (protoOutputStream != null) { + protoOutputStream.write(DeviceConfigurationProto.OPENGL_VERSION, + configInfo.reqGlEsVersion); + } + if (pw != null) { + pw.print("opengl-version: 0x"); + pw.println(Integer.toHexString(configInfo.reqGlEsVersion)); + } + } + + Set glExtensionsSet = getGlExtensionsFromDriver(); + String[] glExtensions = new String[glExtensionsSet.size()]; + glExtensions = glExtensionsSet.toArray(glExtensions); + Arrays.sort(glExtensions); + for (int i = 0; i < glExtensions.length; i++) { + if (protoOutputStream != null) { + protoOutputStream.write(DeviceConfigurationProto.OPENGL_EXTENSIONS, + glExtensions[i]); + } + if (pw != null) { + pw.print("opengl-extensions: "); pw.println(glExtensions[i]); + } + + } + + PackageManager pm = mInternal.mContext.getPackageManager(); + List slibs = pm.getSharedLibraries(0); + Collections.sort(slibs, Comparator.comparing(SharedLibraryInfo::getName)); + for (int i = 0; i < slibs.size(); i++) { + if (protoOutputStream != null) { + protoOutputStream.write(DeviceConfigurationProto.SHARED_LIBRARIES, + slibs.get(i).getName()); + } + if (pw != null) { + pw.print("shared-libraries: "); pw.println(slibs.get(i).getName()); + } + } + + FeatureInfo[] features = pm.getSystemAvailableFeatures(); + Arrays.sort(features, (o1, o2) -> { + if (o1.name == o2.name) return 0; + if (o1.name == null) return -1; + if (o2.name == null) return 1; + return o1.name.compareTo(o2.name); + }); + + for (int i = 0; i < features.length; i++) { + if (features[i].name != null) { + if (protoOutputStream != null) { + protoOutputStream.write(DeviceConfigurationProto.FEATURES, features[i].name); + } + if (pw != null) { + pw.print("features: "); pw.println(features[i].name); + } + } + } + + if (protoOutputStream != null) { + protoOutputStream.end(token); + } + } + + private int getDisplayIdFromNextArg() { + int displayId = Integer.parseInt(getNextArgRequired()); + if (displayId < 0) { + throw new IllegalArgumentException("--display must be a non-negative integer"); + } + return displayId; + } + + int runGetConfig(PrintWriter pw) throws RemoteException { + int days = -1; + int displayId = Display.DEFAULT_DISPLAY; + boolean asProto = false; + boolean inclDevice = false; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--days")) { + days = Integer.parseInt(getNextArgRequired()); + if (days <= 0) { + throw new IllegalArgumentException("--days must be a positive integer"); + } + } else if (opt.equals("--proto")) { + asProto = true; + } else if (opt.equals("--device")) { + inclDevice = true; + } else if (opt.equals("--display")) { + displayId = getDisplayIdFromNextArg(); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + Configuration config = mInterface.getConfiguration(); + if (config == null) { + getErrPrintWriter().println("Activity manager has no configuration"); + return -1; + } + + DisplayManager dm = mInternal.mContext.getSystemService(DisplayManager.class); + Display display = dm.getDisplay(displayId); + + if (display == null) { + getErrPrintWriter().println("Error: Display does not exist: " + displayId); + return -1; + } + + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + + if (asProto) { + final ProtoOutputStream proto = new ProtoOutputStream(getOutFileDescriptor()); + config.writeResConfigToProto(proto, GlobalConfigurationProto.RESOURCES, metrics); + if (inclDevice) { + writeDeviceConfig(proto, GlobalConfigurationProto.DEVICE, null, config, metrics); + } + proto.flush(); + } else { + pw.println("config: " + Configuration.resourceQualifierString(config, metrics)); + pw.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS)); + if (inclDevice) { + writeDeviceConfig(null, -1, pw, config, metrics); + } + + if (days >= 0) { + final List recentConfigs = getRecentConfigurations(days); + final int recentConfigSize = recentConfigs.size(); + if (recentConfigSize > 0) { + pw.println("recentConfigs:"); + for (int i = 0; i < recentConfigSize; i++) { + pw.println(" config: " + Configuration.resourceQualifierString( + recentConfigs.get(i))); + } + } + } + + } + return 0; + } + + int runSuppressResizeConfigChanges(PrintWriter pw) throws RemoteException { + boolean suppress = Boolean.valueOf(getNextArgRequired()); + mTaskInterface.suppressResizeConfigChanges(suppress); + return 0; + } + + int runSetInactive(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArgRequired(); + String value = getNextArgRequired(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + usm.setAppInactive(packageName, Boolean.parseBoolean(value), userId); + return 0; + } + + private int bucketNameToBucketValue(String name) { + String lower = name.toLowerCase(); + if (lower.startsWith("ac")) { + return UsageStatsManager.STANDBY_BUCKET_ACTIVE; + } else if (lower.startsWith("wo")) { + return UsageStatsManager.STANDBY_BUCKET_WORKING_SET; + } else if (lower.startsWith("fr")) { + return UsageStatsManager.STANDBY_BUCKET_FREQUENT; + } else if (lower.startsWith("ra")) { + return UsageStatsManager.STANDBY_BUCKET_RARE; + } else if (lower.startsWith("re")) { + return UsageStatsManager.STANDBY_BUCKET_RESTRICTED; + } else if (lower.startsWith("ne")) { + return UsageStatsManager.STANDBY_BUCKET_NEVER; + } else { + try { + int bucket = Integer.parseInt(lower); + return bucket; + } catch (NumberFormatException nfe) { + getErrPrintWriter().println("Error: Unknown bucket: " + name); + } + } + return -1; + } + + int runSetStandbyBucket(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArgRequired(); + String value = getNextArgRequired(); + int bucket = bucketNameToBucketValue(value); + if (bucket < 0) return -1; + boolean multiple = peekNextArg() != null; + + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + if (!multiple) { + usm.setAppStandbyBucket(packageName, bucketNameToBucketValue(value), userId); + } else { + ArrayList bucketInfoList = new ArrayList<>(); + bucketInfoList.add(new AppStandbyInfo(packageName, bucket)); + while ((packageName = getNextArg()) != null) { + value = getNextArgRequired(); + bucket = bucketNameToBucketValue(value); + if (bucket < 0) continue; + bucketInfoList.add(new AppStandbyInfo(packageName, bucket)); + } + ParceledListSlice slice = new ParceledListSlice<>(bucketInfoList); + usm.setAppStandbyBuckets(slice, userId); + } + return 0; + } + + int runGetStandbyBucket(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArg(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + if (packageName != null) { + int bucket = usm.getAppStandbyBucket(packageName, null, userId); + pw.println(bucket); + } else { + ParceledListSlice buckets = usm.getAppStandbyBuckets( + SHELL_PACKAGE_NAME, userId); + for (AppStandbyInfo bucketInfo : buckets.getList()) { + pw.print(bucketInfo.mPackageName); pw.print(": "); + pw.println(bucketInfo.mStandbyBucket); + } + } + return 0; + } + + int runGetInactive(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArgRequired(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + boolean isIdle = usm.isAppInactive(packageName, userId, SHELL_PACKAGE_NAME); + pw.println("Idle=" + isIdle); + return 0; + } + + int runSendTrimMemory(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + if (userId == UserHandle.USER_ALL) { + getErrPrintWriter().println("Error: Can't use user 'all'"); + return -1; + } + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + String proc = getNextArgRequired(); + String levelArg = getNextArgRequired(); + int level; + switch (levelArg) { + case "HIDDEN": + level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; + break; + case "RUNNING_MODERATE": + level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; + break; + case "BACKGROUND": + level = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; + break; + case "RUNNING_LOW": + level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; + break; + case "MODERATE": + level = ComponentCallbacks2.TRIM_MEMORY_MODERATE; + break; + case "RUNNING_CRITICAL": + level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; + break; + case "COMPLETE": + level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; + break; + default: + try { + level = Integer.parseInt(levelArg); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: Unknown level option: " + levelArg); + return -1; + } + } + if (!mInterface.setProcessMemoryTrimLevel(proc, userId, level)) { + getErrPrintWriter().println("Unknown error: failed to set trim level"); + return -1; + } + return 0; + } + + int runDisplay(PrintWriter pw) throws RemoteException { + String op = getNextArgRequired(); + switch (op) { + case "move-stack": + return runDisplayMoveStack(pw); + default: + getErrPrintWriter().println("Error: unknown command '" + op + "'"); + return -1; + } + } + + int runStack(PrintWriter pw) throws RemoteException { + String op = getNextArgRequired(); + switch (op) { + case "move-task": + return runStackMoveTask(pw); + case "list": + return runStackList(pw); + case "info": + return runRootTaskInfo(pw); + case "remove": + return runRootTaskRemove(pw); + default: + getErrPrintWriter().println("Error: unknown command '" + op + "'"); + return -1; + } + } + + + private Rect getBounds() { + String leftStr = getNextArgRequired(); + int left = Integer.parseInt(leftStr); + String topStr = getNextArgRequired(); + int top = Integer.parseInt(topStr); + String rightStr = getNextArgRequired(); + int right = Integer.parseInt(rightStr); + String bottomStr = getNextArgRequired(); + int bottom = Integer.parseInt(bottomStr); + if (left < 0) { + getErrPrintWriter().println("Error: bad left arg: " + leftStr); + return null; + } + if (top < 0) { + getErrPrintWriter().println("Error: bad top arg: " + topStr); + return null; + } + if (right <= 0) { + getErrPrintWriter().println("Error: bad right arg: " + rightStr); + return null; + } + if (bottom <= 0) { + getErrPrintWriter().println("Error: bad bottom arg: " + bottomStr); + return null; + } + return new Rect(left, top, right, bottom); + } + + int runDisplayMoveStack(PrintWriter pw) throws RemoteException { + String rootTaskIdStr = getNextArgRequired(); + int rootTaskId = Integer.parseInt(rootTaskIdStr); + String displayIdStr = getNextArgRequired(); + int displayId = Integer.parseInt(displayIdStr); + mTaskInterface.moveRootTaskToDisplay(rootTaskId, displayId); + return 0; + } + + int runStackMoveTask(PrintWriter pw) throws RemoteException { + String taskIdStr = getNextArgRequired(); + int taskId = Integer.parseInt(taskIdStr); + String rootTaskIdStr = getNextArgRequired(); + int rootTaskId = Integer.parseInt(rootTaskIdStr); + String toTopStr = getNextArgRequired(); + final boolean toTop; + if ("true".equals(toTopStr)) { + toTop = true; + } else if ("false".equals(toTopStr)) { + toTop = false; + } else { + getErrPrintWriter().println("Error: bad toTop arg: " + toTopStr); + return -1; + } + + mTaskInterface.moveTaskToRootTask(taskId, rootTaskId, toTop); + return 0; + } + + int runStackList(PrintWriter pw) throws RemoteException { + List tasks = mTaskInterface.getAllRootTaskInfos(); + for (RootTaskInfo info : tasks) { + pw.println(info); + } + return 0; + } + + int runRootTaskInfo(PrintWriter pw) throws RemoteException { + int windowingMode = Integer.parseInt(getNextArgRequired()); + int activityType = Integer.parseInt(getNextArgRequired()); + RootTaskInfo info = mTaskInterface.getRootTaskInfo(windowingMode, activityType); + pw.println(info); + return 0; + } + + int runRootTaskRemove(PrintWriter pw) throws RemoteException { + String taskIdStr = getNextArgRequired(); + int taskId = Integer.parseInt(taskIdStr); + mTaskInterface.removeTask(taskId); + return 0; + } + + int runTask(PrintWriter pw) throws RemoteException { + String op = getNextArgRequired(); + if (op.equals("lock")) { + return runTaskLock(pw); + } else if (op.equals("resizeable")) { + return runTaskResizeable(pw); + } else if (op.equals("resize")) { + return runTaskResize(pw); + } else if (op.equals("focus")) { + return runTaskFocus(pw); + } else { + getErrPrintWriter().println("Error: unknown command '" + op + "'"); + return -1; + } + } + + int runTaskLock(PrintWriter pw) throws RemoteException { + String taskIdStr = getNextArgRequired(); + if (taskIdStr.equals("stop")) { + mTaskInterface.stopSystemLockTaskMode(); + } else { + int taskId = Integer.parseInt(taskIdStr); + mTaskInterface.startSystemLockTaskMode(taskId); + } + pw.println("Activity manager is " + (mTaskInterface.isInLockTaskMode() ? "" : "not ") + + "in lockTaskMode"); + return 0; + } + + int runTaskResizeable(PrintWriter pw) throws RemoteException { + final String taskIdStr = getNextArgRequired(); + final int taskId = Integer.parseInt(taskIdStr); + final String resizeableStr = getNextArgRequired(); + final int resizeableMode = Integer.parseInt(resizeableStr); + mTaskInterface.setTaskResizeable(taskId, resizeableMode); + return 0; + } + + int runTaskResize(PrintWriter pw) throws RemoteException { + final String taskIdStr = getNextArgRequired(); + final int taskId = Integer.parseInt(taskIdStr); + final Rect bounds = getBounds(); + if (bounds == null) { + getErrPrintWriter().println("Error: invalid input bounds"); + return -1; + } + taskResize(taskId, bounds, 0, false); + return 0; + } + + void taskResize(int taskId, Rect bounds, int delay_ms, boolean pretendUserResize) + throws RemoteException { + final int resizeMode = pretendUserResize ? RESIZE_MODE_USER : RESIZE_MODE_SYSTEM; + mTaskInterface.resizeTask(taskId, bounds, resizeMode); + try { + Thread.sleep(delay_ms); + } catch (InterruptedException e) { + } + } + + int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize, + int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms) + throws RemoteException { + int maxMove; + if (movingForward) { + while (maxToTravel > 0 + && ((horizontal && taskRect.right < stackRect.right) + ||(!horizontal && taskRect.bottom < stackRect.bottom))) { + if (horizontal) { + maxMove = Math.min(stepSize, stackRect.right - taskRect.right); + maxToTravel -= maxMove; + taskRect.right += maxMove; + taskRect.left += maxMove; + } else { + maxMove = Math.min(stepSize, stackRect.bottom - taskRect.bottom); + maxToTravel -= maxMove; + taskRect.top += maxMove; + taskRect.bottom += maxMove; + } + taskResize(taskId, taskRect, delay_ms, false); + } + } else { + while (maxToTravel < 0 + && ((horizontal && taskRect.left > stackRect.left) + ||(!horizontal && taskRect.top > stackRect.top))) { + if (horizontal) { + maxMove = Math.min(stepSize, taskRect.left - stackRect.left); + maxToTravel -= maxMove; + taskRect.right -= maxMove; + taskRect.left -= maxMove; + } else { + maxMove = Math.min(stepSize, taskRect.top - stackRect.top); + maxToTravel -= maxMove; + taskRect.top -= maxMove; + taskRect.bottom -= maxMove; + } + taskResize(taskId, taskRect, delay_ms, false); + } + } + // Return the remaining distance we didn't travel because we reached the target location. + return maxToTravel; + } + + int getStepSize(int current, int target, int inStepSize, boolean greaterThanTarget) { + int stepSize = 0; + if (greaterThanTarget && target < current) { + current -= inStepSize; + stepSize = inStepSize; + if (target > current) { + stepSize -= (target - current); + } + } + if (!greaterThanTarget && target > current) { + current += inStepSize; + stepSize = inStepSize; + if (target < current) { + stepSize += (current - target); + } + } + return stepSize; + } + + int runTaskFocus(PrintWriter pw) throws RemoteException { + final int taskId = Integer.parseInt(getNextArgRequired()); + pw.println("Setting focus to task " + taskId); + mTaskInterface.setFocusedTask(taskId); + return 0; + } + + int runWrite(PrintWriter pw) { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "registerUidObserver()"); + mInternal.mAtmInternal.flushRecentTasks(); + pw.println("All tasks persisted."); + return 0; + } + + int runAttachAgent(PrintWriter pw) { + // TODO: revisit the permissions required for attaching agents + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "attach-agent"); + String process = getNextArgRequired(); + String agent = getNextArgRequired(); + String opt; + if ((opt = getNextArg()) != null) { + pw.println("Error: Unknown option: " + opt); + return -1; + } + mInternal.attachAgent(process, agent); + return 0; + } + + int runSupportsMultiwindow(PrintWriter pw) throws RemoteException { + final Resources res = getResources(pw); + if (res == null) { + return -1; + } + pw.println(ActivityTaskManager.supportsMultiWindow(mInternal.mContext)); + return 0; + } + + int runSupportsSplitScreenMultiwindow(PrintWriter pw) throws RemoteException { + final Resources res = getResources(pw); + if (res == null) { + return -1; + } + pw.println(ActivityTaskManager.supportsSplitScreenMultiWindow(mInternal.mContext)); + return 0; + } + + int runUpdateApplicationInfo(PrintWriter pw) throws RemoteException { + int userid = UserHandle.parseUserArg(getNextArgRequired()); + ArrayList packages = new ArrayList<>(); + packages.add(getNextArgRequired()); + String packageName; + while ((packageName = getNextArg()) != null) { + packages.add(packageName); + } + mInternal.scheduleApplicationInfoChanged(packages, userid); + pw.println("Packages updated with most recent ApplicationInfos."); + return 0; + } + + int runNoHomeScreen(PrintWriter pw) throws RemoteException { + final Resources res = getResources(pw); + if (res == null) { + return -1; + } + pw.println(res.getBoolean(com.android.internal.R.bool.config_noHomeScreen)); + return 0; + } + + int runWaitForBroadcastIdle(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + boolean flushBroadcastLoopers = false; + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--flush-broadcast-loopers")) { + flushBroadcastLoopers = true; + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + mInternal.waitForBroadcastIdle(pw, flushBroadcastLoopers); + return 0; + } + + int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + boolean flushBroadcastLoopers = false; + boolean flushApplicationThreads = false; + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--flush-broadcast-loopers")) { + flushBroadcastLoopers = true; + } else if (opt.equals("--flush-application-threads")) { + flushApplicationThreads = true; + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers, flushApplicationThreads); + return 0; + } + + int runWaitForApplicationBarrier(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + mInternal.waitForApplicationBarrier(pw); + return 0; + } + + int runWaitForBroadcastDispatch(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + final Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + mInternal.waitForBroadcastDispatch(pw, intent); + return 0; + } + + int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException { + final String broadcastAction = getNextArgRequired(); + mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction); + return 0; + } + + int runClearIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException { + final String broadcastAction = getNextArgRequired(); + mInternal.clearIgnoreDeliveryGroupPolicy(broadcastAction); + return 0; + } + + int runRefreshSettingsCache() throws RemoteException { + mInternal.refreshSettingsCache(); + return 0; + } + + private int runCompat(PrintWriter pw) throws RemoteException { + final PlatformCompat platformCompat = (PlatformCompat) + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + String toggleValue = getNextArgRequired(); + boolean killPackage = !"--no-kill".equals(getNextOption()); + boolean toggleAll = false; + int targetSdkVersion = -1; + long changeId = -1; + + if (toggleValue.endsWith("-all")) { + toggleValue = toggleValue.substring(0, toggleValue.lastIndexOf("-all")); + toggleAll = true; + if (!toggleValue.equals("reset")) { + try { + targetSdkVersion = Integer.parseInt(getNextArgRequired()); + } catch (NumberFormatException e) { + pw.println("Invalid targetSdkVersion!"); + return -1; + } + } + } else { + String changeIdString = getNextArgRequired(); + try { + changeId = Long.parseLong(changeIdString); + } catch (NumberFormatException e) { + changeId = platformCompat.lookupChangeId(changeIdString); + } + if (changeId == -1) { + pw.println("Unknown or invalid change: '" + changeIdString + "'."); + return -1; + } + } + String packageName = getNextArgRequired(); + if (!toggleAll && !platformCompat.isKnownChangeId(changeId)) { + pw.println("Warning! Change " + changeId + " is not known yet. Enabling/disabling it" + + " could have no effect."); + } + ArraySet enabled = new ArraySet<>(); + ArraySet disabled = new ArraySet<>(); + try { + switch (toggleValue) { + case "enable": + if (toggleAll) { + int numChanges = platformCompat.enableTargetSdkChanges(packageName, + targetSdkVersion); + if (numChanges == 0) { + pw.println("No changes were enabled."); + return -1; + } + pw.println("Enabled " + numChanges + " changes gated by targetSdkVersion " + + targetSdkVersion + " for " + packageName + "."); + } else { + enabled.add(changeId); + CompatibilityChangeConfig overrides = + new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(enabled, disabled)); + if (killPackage) { + platformCompat.setOverrides(overrides, packageName); + } else { + platformCompat.setOverridesForTest(overrides, packageName); + } + pw.println("Enabled change " + changeId + " for " + packageName + "."); + } + return 0; + case "disable": + if (toggleAll) { + int numChanges = platformCompat.disableTargetSdkChanges(packageName, + targetSdkVersion); + if (numChanges == 0) { + pw.println("No changes were disabled."); + return -1; + } + pw.println("Disabled " + numChanges + " changes gated by targetSdkVersion " + + targetSdkVersion + " for " + packageName + "."); + } else { + disabled.add(changeId); + CompatibilityChangeConfig overrides = + new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(enabled, disabled)); + if (killPackage) { + platformCompat.setOverrides(overrides, packageName); + } else { + platformCompat.setOverridesForTest(overrides, packageName); + } + pw.println("Disabled change " + changeId + " for " + packageName + "."); + } + return 0; + case "reset": + if (toggleAll) { + if (killPackage) { + platformCompat.clearOverrides(packageName); + } else { + platformCompat.clearOverridesForTest(packageName); + } + pw.println("Reset all changes for " + packageName + " to default value."); + return 0; + } + boolean existed; + if (killPackage) { + existed = platformCompat.clearOverride(changeId, packageName); + } else { + existed = platformCompat.clearOverrideForTest(changeId, packageName); + } + if (existed) { + pw.println("Reset change " + changeId + " for " + packageName + + " to default value."); + } else { + pw.println("No override exists for changeId " + changeId + "."); + } + return 0; + default: + pw.println("Invalid toggle value: '" + toggleValue + "'."); + } + } catch (SecurityException e) { + pw.println(e.getMessage()); + } + return -1; + } + + private int runGetCurrentForegroundProcess(PrintWriter pw, IActivityManager iam) + throws RemoteException { + + ProcessObserver observer = new ProcessObserver(pw, iam); + iam.registerProcessObserver(observer); + + final InputStream mInput = getRawInputStream(); + InputStreamReader converter = new InputStreamReader(mInput); + BufferedReader in = new BufferedReader(converter); + String line; + try { + while ((line = in.readLine()) != null) { + boolean addNewline = true; + if (line.length() <= 0) { + addNewline = false; + } else if ("q".equals(line) || "quit".equals(line)) { + break; + } else { + pw.println("Invalid command: " + line); + } + if (addNewline) { + pw.println(""); + } + pw.flush(); + } + } catch (IOException e) { + e.printStackTrace(); + pw.flush(); + } finally { + iam.unregisterProcessObserver(observer); + } + return 0; + } + + static final class ProcessObserver extends IProcessObserver.Stub { + + private PrintWriter mPw; + private IActivityManager mIam; + + ProcessObserver(PrintWriter mPw, IActivityManager mIam) { + this.mPw = mPw; + this.mIam = mIam; + } + + @Override + public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { + if (foregroundActivities) { + try { + int prcState = mIam.getUidProcessState(uid, "android"); + + if (prcState == ProcessStateEnum.TOP) { + mPw.println("New foreground process: " + pid); + } else { + mPw.println("No top app found"); + } + mPw.flush(); + } catch (RemoteException e) { + mPw.println("Error occurred in binder call"); + mPw.flush(); + } + } + } + + @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override + public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { + } + + @Override + public void onProcessDied(int pid, int uid) { + } + } + + private int runSetMemoryFactor(PrintWriter pw) throws RemoteException { + final String levelArg = getNextArgRequired(); + @MemFactor int level = ADJ_MEM_FACTOR_NOTHING; + switch (levelArg) { + case "NORMAL": + level = ADJ_MEM_FACTOR_NORMAL; + break; + case "MODERATE": + level = ADJ_MEM_FACTOR_MODERATE; + break; + case "LOW": + level = ADJ_MEM_FACTOR_LOW; + break; + case "CRITICAL": + level = ADJ_MEM_FACTOR_CRITICAL; + break; + default: + try { + level = Integer.parseInt(levelArg); + } catch (NumberFormatException e) { + } + if (level < ADJ_MEM_FACTOR_NORMAL || level > ADJ_MEM_FACTOR_CRITICAL) { + getErrPrintWriter().println("Error: Unknown level option: " + levelArg); + return -1; + } + } + mInternal.setMemFactorOverride(level); + return 0; + } + + private int runShowMemoryFactor(PrintWriter pw) throws RemoteException { + final @MemFactor int level = mInternal.getMemoryTrimLevel(); + switch (level) { + case ADJ_MEM_FACTOR_NOTHING: + pw.println(""); + break; + case ADJ_MEM_FACTOR_NORMAL: + pw.println("NORMAL"); + break; + case ADJ_MEM_FACTOR_MODERATE: + pw.println("MODERATE"); + break; + case ADJ_MEM_FACTOR_LOW: + pw.println("LOW"); + break; + case ADJ_MEM_FACTOR_CRITICAL: + pw.println("CRITICAL"); + break; + } + pw.flush(); + return 0; + } + + private int runResetMemoryFactor(PrintWriter pw) throws RemoteException { + mInternal.setMemFactorOverride(ADJ_MEM_FACTOR_NOTHING); + return 0; + } + + private int runMemoryFactor(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + "runMemoryFactor()"); + + final String op = getNextArgRequired(); + switch (op) { + case "set": + return runSetMemoryFactor(pw); + case "show": + return runShowMemoryFactor(pw); + case "reset": + return runResetMemoryFactor(pw); + default: + getErrPrintWriter().println("Error: unknown command '" + op + "'"); + return -1; + } + } + + private int runServiceRestartBackoff(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT, + "runServiceRestartBackoff()"); + + final String opt = getNextArgRequired(); + switch (opt) { + case "enable": + mInternal.setServiceRestartBackoffEnabled(getNextArgRequired(), true, "shell"); + return 0; + case "disable": + mInternal.setServiceRestartBackoffEnabled(getNextArgRequired(), false, "shell"); + return 0; + case "show": + pw.println(mInternal.isServiceRestartBackoffEnabled(getNextArgRequired()) + ? "enabled" : "disabled"); + return 0; + default: + getErrPrintWriter().println("Error: unknown command '" + opt + "'"); + return -1; + } + } + + private int runGetIsolatedProcesses(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.DUMP, + "getIsolatedProcesses()"); + final List result = mInternal.mInternal.getIsolatedProcesses( + Integer.parseInt(getNextArgRequired())); + pw.print("["); + if (result != null) { + for (int i = 0, size = result.size(); i < size; i++) { + if (i > 0) { + pw.print(", "); + } + pw.print(result.get(i)); + } + } + pw.println("]"); + return 0; + } + + private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "setStopUserOnSwitch()"); + String arg = getNextArg(); + if (arg == null) { + Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value"); + mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT); + pw.println("Reset to default value"); + return 0; + } + + boolean stop = Boolean.parseBoolean(arg); + int value = stop + ? ActivityManager.STOP_USER_ON_SWITCH_TRUE + : ActivityManager.STOP_USER_ON_SWITCH_FALSE; + + Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop); + mInternal.setStopUserOnSwitch(value); + pw.println("Set to " + stop); + + return 0; + } + + // TODO(b/203105544) STOPSHIP - For debugging only, to be removed before shipping. + private int runSetBgAbusiveUids(PrintWriter pw) throws RemoteException { + final String arg = getNextArg(); + final AppBatteryTracker batteryTracker = + mInternal.mAppRestrictionController.getAppStateTracker(AppBatteryTracker.class); + if (batteryTracker == null) { + getErrPrintWriter().println("Unable to get bg battery tracker"); + return -1; + } + if (arg == null) { + batteryTracker.clearDebugUidPercentage(); + return 0; + } + String[] pairs = arg.split(","); + int[] uids = new int[pairs.length]; + double[][] values = new double[pairs.length][]; + try { + for (int i = 0; i < pairs.length; i++) { + String[] pair = pairs[i].split("="); + if (pair.length != 2) { + getErrPrintWriter().println("Malformed input"); + return -1; + } + uids[i] = Integer.parseInt(pair[0]); + final String[] vals = pair[1].split(":"); + if (vals.length != BATTERY_USAGE_COUNT) { + getErrPrintWriter().println("Malformed input"); + return -1; + } + values[i] = new double[vals.length]; + for (int j = 0; j < vals.length; j++) { + values[i][j] = Double.parseDouble(vals[j]); + } + } + } catch (NumberFormatException e) { + getErrPrintWriter().println("Malformed input"); + return -1; + } + batteryTracker.setDebugUidPercentage(uids, values); + return 0; + } + + private int runListBgExemptionsConfig(PrintWriter pw) throws RemoteException { + final ArraySet sysConfigs = mInternal.mAppRestrictionController + .mBgRestrictionExemptioFromSysConfig; + if (sysConfigs != null) { + for (int i = 0, size = sysConfigs.size(); i < size; i++) { + pw.print(sysConfigs.valueAt(i)); + pw.print(' '); + } + pw.println(); + } + return 0; + } + + private @ActivityManager.RestrictionLevel int restrictionNameToLevel(String name) { + String lower = name.toLowerCase(); + switch (lower) { + case "unrestricted": + return ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED; + case "exempted": + return ActivityManager.RESTRICTION_LEVEL_EXEMPTED; + case "adaptive_bucket": + return ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; + case "restricted_bucket": + return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; + case "background_restricted": + return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; + case "hibernation": + return ActivityManager.RESTRICTION_LEVEL_HIBERNATION; + default: + return ActivityManager.RESTRICTION_LEVEL_UNKNOWN; + } + } + + int runSetBgRestrictionLevel(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArgRequired(); + final String value = getNextArgRequired(); + final int level = restrictionNameToLevel(value); + if (level == ActivityManager.RESTRICTION_LEVEL_UNKNOWN) { + pw.println("Error: invalid restriction level"); + return -1; + } + + int uid = INVALID_UID; + try { + final PackageManager pm = mInternal.mContext.getPackageManager(); + uid = pm.getPackageUidAsUser(packageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId); + } catch (PackageManager.NameNotFoundException e) { + pw.println("Error: userId:" + userId + " package:" + packageName + " is not found"); + return -1; + } + mInternal.setBackgroundRestrictionLevel(packageName, uid, userId, level, + REASON_MAIN_FORCED_BY_USER, REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED); + return 0; + } + + int runGetBgRestrictionLevel(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + final String packageName = getNextArgRequired(); + final @ActivityManager.RestrictionLevel int level = + mInternal.getBackgroundRestrictionLevel(packageName, userId); + pw.println(ActivityManager.restrictionLevelToName(level)); + return 0; + } + + int runSetForegroundServiceDelegate(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + final String packageName = getNextArgRequired(); + final String action = getNextArgRequired(); + boolean isStart = true; + if ("start".equals(action)) { + isStart = true; + } else if ("stop".equals(action)) { + isStart = false; + } else { + pw.println("Error: action is either start or stop"); + return -1; + } + + int uid = INVALID_UID; + try { + final PackageManager pm = mInternal.mContext.getPackageManager(); + uid = pm.getPackageUidAsUser(packageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId); + } catch (PackageManager.NameNotFoundException e) { + pw.println("Error: userId:" + userId + " package:" + packageName + " is not found"); + return -1; + } + mInternal.setForegroundServiceDelegate(packageName, uid, isStart, + ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SPECIAL_USE, + "FgsDelegate"); + return 0; + } + + int runResetDropboxRateLimiter() throws RemoteException { + mInternal.resetDropboxRateLimiter(); + return 0; + } + + int runListDisplaysForStartingUsers(PrintWriter pw) throws RemoteException { + int[] displayIds = mInterface.getDisplayIdsForStartingVisibleBackgroundUsers(); + // NOTE: format below cannot be changed as it's used by ITestDevice + pw.println(displayIds == null || displayIds.length == 0 + ? "none" + : Arrays.toString(displayIds)); + return 0; + } + + private Resources getResources(PrintWriter pw) throws RemoteException { + // system resources does not contain all the device configuration, construct it manually. + Configuration config = mInterface.getConfiguration(); + if (config == null) { + pw.println("Error: Activity manager has no configuration"); + return null; + } + + final DisplayMetrics metrics = new DisplayMetrics(); + metrics.setToDefaults(); + + return new Resources(AssetManager.getSystem(), metrics, config); + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + dumpHelp(pw, mDumping); + } + + @NeverCompile // Avoid size overhead of debugging code. + static void dumpHelp(PrintWriter pw, boolean dumping) { + if (dumping) { + pw.println("Activity manager dump options:"); + pw.println(" [-a] [-c] [-p PACKAGE] [-h] [WHAT] ..."); + pw.println(" WHAT may be one of:"); + pw.println(" a[ctivities]: activity stack state"); + pw.println(" r[recents]: recent activities state"); + pw.println(" b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state"); + pw.println(" broadcast-stats [PACKAGE_NAME]: aggregated broadcast statistics"); + pw.println(" i[ntents] [PACKAGE_NAME]: pending intent state"); + pw.println(" p[rocesses] [PACKAGE_NAME]: process state"); + pw.println(" o[om]: out of memory management"); + pw.println(" perm[issions]: URI permission grant state"); + pw.println(" prov[iders] [COMP_SPEC ...]: content provider state"); + pw.println(" provider [COMP_SPEC]: provider client-side state"); + pw.println(" s[ervices] [COMP_SPEC ...]: service state"); + pw.println(" allowed-associations: current package association restrictions"); + pw.println(" as[sociations]: tracked app associations"); + pw.println(" start-info [PACKAGE_NAME]: historical process start information"); + pw.println(" exit-info [PACKAGE_NAME]: historical process exit information"); + pw.println(" lmk: stats on low memory killer"); + pw.println(" lru: raw LRU process list"); + pw.println(" binder-proxies: stats on binder objects and IPCs"); + pw.println(" settings: currently applied config settings"); + pw.println(" timers: the current ANR timer state"); + pw.println(" service [COMP_SPEC]: service client-side state"); + pw.println(" package [PACKAGE_NAME]: all state related to given package"); + pw.println(" all: dump all activities"); + pw.println(" top: dump the top activity"); + pw.println(" users: user state"); + pw.println(" WHAT may also be a COMP_SPEC to dump activities."); + pw.println(" COMP_SPEC may be a component name (com.foo/.myApp),"); + pw.println(" a partial substring in a component name, a"); + pw.println(" hex object identifier."); + pw.println(" -a: include all available server state."); + pw.println(" -c: include client state."); + pw.println(" -p: limit output to given package."); + pw.println(" -d: limit output to given display."); + pw.println(" --checkin: output checkin format, resetting data."); + pw.println(" --C: output checkin format, not resetting data."); + pw.println(" --proto: output dump in protocol buffer format."); + pw.printf(" %s: dump just the DUMPABLE-related state of an activity. Use the %s " + + "option to list the supported DUMPABLEs\n", Activity.DUMP_ARG_DUMP_DUMPABLE, + Activity.DUMP_ARG_LIST_DUMPABLES); + pw.printf(" %s: show the available dumpables in an activity\n", + Activity.DUMP_ARG_LIST_DUMPABLES); + } else { + pw.println("Activity manager (activity) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" start-activity [-D] [-N] [-W] [-P ] [--start-profiler ]"); + pw.println(" [--sampling INTERVAL] [--clock-type ] [--streaming]"); + pw.println(" [-R COUNT] [-S] [--track-allocation]"); + pw.println(" [--user | current] [--suspend] "); + pw.println(" Start an Activity. Options are:"); + pw.println(" -D: enable debugging"); + pw.println(" --suspend: debugged app suspend threads at startup (only with -D)"); + pw.println(" -N: enable native debugging"); + pw.println(" -W: wait for launch to complete (initial display)"); + pw.println(" --start-profiler : start profiler and send results to "); + pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds"); + pw.println(" between samples (use with --start-profiler)"); + pw.println(" --clock-type : type can be wall / thread-cpu / dual. Specify"); + pw.println(" the clock that is used to report the timestamps when profiling"); + pw.println(" The default value is dual. (use with --start-profiler)"); + pw.println(" --streaming: stream the profiling output to the specified file"); + pw.println(" (use with --start-profiler)"); + pw.println(" -P : like above, but profiling stops when app goes idle"); + pw.println(" --attach-agent : attach the given agent before binding"); + pw.println(" --attach-agent-bind : attach the given agent during binding"); + pw.println(" -R: repeat the activity launch times. Prior to each repeat,"); + pw.println(" the top activity will be finished."); + pw.println(" -S: force stop the target app before starting the activity"); + pw.println(" --track-allocation: enable tracking of object allocations"); + pw.println(" --user | current: Specify which user to run as; if not"); + pw.println(" specified then run as the current user."); + pw.println(" --windowingMode : The windowing mode to launch the activity into."); + pw.println(" --activityType : The activity type to launch the activity as."); + pw.println(" --display : The display to launch the activity into."); + pw.println(" --splashscreen-icon: Show the splash screen icon on launch."); + pw.println(" start-service [--user | current] "); + pw.println(" Start a Service. Options are:"); + pw.println(" --user | current: Specify which user to run as; if not"); + pw.println(" specified then run as the current user."); + pw.println(" start-foreground-service [--user | current] "); + pw.println(" Start a foreground Service. Options are:"); + pw.println(" --user | current: Specify which user to run as; if not"); + pw.println(" specified then run as the current user."); + pw.println(" stop-service [--user | current] "); + pw.println(" Stop a Service. Options are:"); + pw.println(" --user | current: Specify which user to run as; if not"); + pw.println(" specified then run as the current user."); + pw.println(" broadcast [--user | all | current]"); + pw.println(" [--receiver-permission ]"); + pw.println(" [--allow-background-activity-starts]"); + pw.println(" [--async] "); + pw.println(" Send a broadcast Intent. Options are:"); + pw.println(" --user | all | current: Specify which user to send to; if not"); + pw.println(" specified then send to all users."); + pw.println(" --receiver-permission : Require receiver to hold permission."); + pw.println(" --allow-background-activity-starts: The receiver may start activities"); + pw.println(" even if in the background."); + pw.println(" --async: Send without waiting for the completion of the receiver."); + pw.println(" compact [some|full] [--user ]"); + pw.println(" Perform a single process compaction."); + pw.println(" some: execute file compaction."); + pw.println(" full: execute anon + file compaction."); + pw.println(" system: system compaction."); + pw.println(" compact system"); + pw.println(" Perform a full system compaction."); + pw.println(" compact native [some|full] "); + pw.println(" Perform a native compaction for process with ."); + pw.println(" some: execute file compaction."); + pw.println(" full: execute anon + file compaction."); + pw.println(" freeze [--sticky] [--user ]"); + pw.println(" Freeze a process."); + pw.println(" --sticky: persists the frozen state for the process lifetime or"); + pw.println(" until an unfreeze is triggered via shell"); + pw.println(" unfreeze [--sticky] [--user ]"); + pw.println(" Unfreeze a process."); + pw.println(" --sticky: persists the unfrozen state for the process lifetime or"); + pw.println(" until a freeze is triggered via shell"); + pw.println(" instrument [-r] [-e ] [-p ] [-w]"); + pw.println(" [--user | current]"); + pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); + pw.println(" [--no-isolated-storage]"); + pw.println(" [--no-window-animation] [--abi ] "); + pw.println(" Start an Instrumentation. Typically this target is in the"); + pw.println(" form / or only if there"); + pw.println(" is only one instrumentation. Options are:"); + pw.println(" -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT). Use with"); + pw.println(" [-e perf true] to generate raw output for performance measurements."); + pw.println(" -e : set argument to . For test runners a"); + pw.println(" common form is [-e [,...]]."); + pw.println(" -p : write profiling data to "); + pw.println(" -m: Write output as protobuf to stdout (machine readable)"); + pw.println(" -f : Write output as protobuf to a file (machine"); + pw.println(" readable). If path is not specified, default directory and file name will"); + pw.println(" be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto"); + pw.println(" -w: wait for instrumentation to finish before returning. Required for"); + pw.println(" test runners."); + pw.println(" --user | current: Specify user instrumentation runs in;"); + pw.println(" current user if not specified."); + pw.println(" --no-hidden-api-checks: disable restrictions on use of hidden API."); + pw.println(" --no-test-api-access: do not allow access to test APIs, if hidden"); + pw.println(" API checks are enabled."); + pw.println(" --no-isolated-storage: don't use isolated storage sandbox and "); + pw.println(" mount full external storage"); + pw.println(" --no-window-animation: turn off window animations while running."); + pw.println(" --abi : Launch the instrumented process with the selected ABI."); + pw.println(" This assumes that the process supports the selected ABI."); + pw.println(" trace-ipc [start|stop] [--dump-file ]"); + pw.println(" Trace IPC transactions."); + pw.println(" start: start tracing IPC transactions."); + pw.println(" stop: stop tracing IPC transactions and dump the results to file."); + pw.println(" --dump-file : Specify the file the trace should be dumped to."); + pw.println(" profile start [--user current]"); + pw.println(" [--clock-type ]"); + pw.println(" [--sampling INTERVAL | --streaming] "); + pw.println(" Start profiler on a process. The given argument"); + pw.println(" may be either a process name or pid. Options are:"); + pw.println(" --user | current: When supplying a process name,"); + pw.println(" specify user of process to profile; uses current user if not"); + pw.println(" specified."); + pw.println(" --clock-type : use the specified clock to report timestamps."); + pw.println(" The type can be one of wall | thread-cpu | dual. The default"); + pw.println(" value is dual."); + pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds"); + pw.println(" between samples."); + pw.println(" --streaming: stream the profiling output to the specified file."); + pw.println(" profile stop [--user current] "); + pw.println(" Stop profiler on a process. The given argument"); + pw.println(" may be either a process name or pid. Options are:"); + pw.println(" --user | current: When supplying a process name,"); + pw.println(" specify user of process to profile; uses current user if not"); + pw.println(" specified."); + pw.println(" dumpheap [--user current] [-n] [-g] "); + pw.println(" Dump the heap of a process. The given argument may"); + pw.println(" be either a process name or pid. Options are:"); + pw.println(" -n: dump native heap instead of managed heap"); + pw.println(" -g: force GC before dumping the heap"); + pw.println(" --user | current: When supplying a process name,"); + pw.println(" specify user of process to dump; uses current user if not specified."); + pw.println(" set-debug-app [-w] [--persistent] "); + pw.println(" Set application to debug. Options are:"); + pw.println(" -w: wait for debugger when application starts"); + pw.println(" --persistent: retain this value"); + pw.println(" clear-debug-app"); + pw.println(" Clear the previously set-debug-app."); + pw.println(" set-watch-heap "); + pw.println(" Start monitoring pss size of , if it is at or"); + pw.println(" above then a heap dump is collected for the user to report."); + pw.println(" clear-watch-heap"); + pw.println(" Clear the previously set-watch-heap."); + pw.println(" clear-start-info [--user | all | current] [package]"); + pw.println(" Clear the process start-info for given package"); + pw.println(" clear-exit-info [--user | all | current] [package]"); + pw.println(" Clear the process exit-info for given package"); + pw.println(" bug-report [--progress | --telephony]"); + pw.println(" Request bug report generation; will launch a notification"); + pw.println(" when done to select where it should be delivered. Options are:"); + pw.println(" --progress: will launch a notification right away to show its progress."); + pw.println(" --telephony: will dump only telephony sections."); + pw.println(" fgs-notification-rate-limit {enable | disable}"); + pw.println(" Enable/disable rate limit on FGS notification deferral policy."); + pw.println(" force-stop [--user | all | current] "); + pw.println(" Completely stop the given application package."); + pw.println(" stop-app [--user | all | current] "); + pw.println(" Stop an app and all of its services. Unlike `force-stop` this does"); + pw.println(" not cancel the app's scheduled alarms and jobs."); + pw.println(" crash [--user ] "); + pw.println(" Induce a VM crash in the specified package or process"); + pw.println(" kill [--user | all | current] "); + pw.println(" Kill all background processes associated with the given application."); + pw.println(" kill-all"); + pw.println(" Kill all processes that are safe to kill (cached, etc)."); + pw.println(" make-uid-idle [--user | all | current] "); + pw.println(" If the given application's uid is in the background and waiting to"); + pw.println(" become idle (not allowing background services), do that now."); + pw.println( + " set-deterministic-uid-idle [--user | all | current] "); + pw.println(" If true, sets the timing of making UIDs idle consistent and"); + pw.println(" deterministic. If false, the timing will be variable depending on"); + pw.println(" other activity on the device. The default is false."); + pw.println(" monitor [--gdb ] [-p ] [-s] [-c] [-k]"); + pw.println(" Start monitoring for crashes or ANRs."); + pw.println(" --gdb: start gdbserv on the given port at crash/ANR"); + pw.println(" -p: only show events related to a specific process / package"); + pw.println(" -s: simple mode, only show a summary line for each event"); + pw.println(" -c: assume the input is always [c]ontinue"); + pw.println(" -k: assume the input is always [k]ill"); + pw.println(" -c and -k are mutually exclusive."); + pw.println(" watch-uids [--oom ] [--mask ]"); + pw.println(" Start watching for and reporting uid state changes."); + pw.println(" --oom: specify a uid for which to report detailed change messages."); + pw.println(" --mask: Specify PROCESS_CAPABILITY_XXX mask to report. "); + pw.println(" By default, it only reports FOREGROUND_LOCATION (1)"); + pw.println(" FOREGROUND_CAMERA (2), FOREGROUND_MICROPHONE (4)"); + pw.println(" and NETWORK (8). New capabilities added on or after"); + pw.println(" Android UDC will not be reported by default."); + pw.println(" hang [--allow-restart]"); + pw.println(" Hang the system."); + pw.println(" --allow-restart: allow watchdog to perform normal system restart"); + pw.println(" restart"); + pw.println(" Restart the user-space system."); + pw.println(" idle-maintenance"); + pw.println(" Perform idle maintenance now."); + pw.println(" screen-compat [on|off] "); + pw.println(" Control screen compatibility mode of ."); + pw.println(" package-importance "); + pw.println(" Print current importance of ."); + pw.println(" to-uri [INTENT]"); + pw.println(" Print the given Intent specification as a URI."); + pw.println(" to-intent-uri [INTENT]"); + pw.println(" Print the given Intent specification as an intent: URI."); + pw.println(" to-app-uri [INTENT]"); + pw.println(" Print the given Intent specification as an android-app: URI."); + pw.println(" switch-user "); + pw.println(" Switch to put USER_ID in the foreground, starting"); + pw.println(" execution of that user if it is currently stopped."); + pw.println(" get-current-user"); + pw.println(" Returns id of the current foreground user."); + pw.println(" start-user [-w] [--display DISPLAY_ID] "); + pw.println(" Start USER_ID in background if it is currently stopped;"); + pw.println(" use switch-user if you want to start the user in foreground."); + pw.println(" -w: wait for start-user to complete and the user to be unlocked."); + pw.println(" --display : starts the user visible in that display, " + + "which allows the user to launch activities on it."); + pw.println(" (not supported on all devices; typically only on automotive builds " + + "where the vehicle has passenger displays)"); + pw.println(" unlock-user "); + pw.println(" Unlock the given user. This will only work if the user doesn't"); + pw.println(" have an LSKF (PIN/pattern/password)."); + pw.println(" stop-user [-w] [-f] "); + pw.println(" Stop execution of USER_ID, not allowing it to run any"); + pw.println(" code until a later explicit start or switch to it."); + pw.println(" -w: wait for stop-user to complete."); + pw.println(" -f: force stop even if there are related users that cannot be stopped."); + pw.println(" is-user-stopped "); + pw.println(" Returns whether has been stopped or not."); + pw.println(" get-started-user-state "); + pw.println(" Gets the current state of the given started user."); + pw.println(" track-associations"); + pw.println(" Enable association tracking."); + pw.println(" untrack-associations"); + pw.println(" Disable and clear association tracking."); + pw.println(" get-uid-state "); + pw.println(" Gets the process state of an app given its ."); + pw.println(" attach-agent "); + pw.println(" Attach an agent to the specified , which may be either a process name or a PID."); + pw.println(" get-config [--days N] [--device] [--proto] [--display ]"); + pw.println(" Retrieve the configuration and any recent configurations of the device."); + pw.println(" --days: also return last N days of configurations that have been seen."); + pw.println(" --device: also output global device configuration info."); + pw.println(" --proto: return result as a proto; does not include --days info."); + pw.println(" --display: Specify for which display to run the command; if not "); + pw.println(" specified then run for the default display."); + pw.println(" supports-multiwindow"); + pw.println(" Returns true if the device supports multiwindow."); + pw.println(" supports-split-screen-multi-window"); + pw.println(" Returns true if the device supports split screen multiwindow."); + pw.println(" suppress-resize-config-changes "); + pw.println(" Suppresses configuration changes due to user resizing an activity/task."); + pw.println(" set-inactive [--user ] true|false"); + pw.println(" Sets the inactive state of an app."); + pw.println(" get-inactive [--user ] "); + pw.println(" Returns the inactive state of an app."); + pw.println(" set-standby-bucket [--user ] active|working_set|frequent|rare|restricted"); + pw.println(" Puts an app in the standby bucket."); + pw.println(" get-standby-bucket [--user ] "); + pw.println(" Returns the standby bucket of an app."); + pw.println(" send-trim-memory [--user ] "); + pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]"); + pw.println(" Send a memory trim event to a . May also supply a raw trim int level."); + pw.println(" display [COMMAND] [...]: sub-commands for operating on displays."); + pw.println(" move-stack "); + pw.println(" Move from its current display to ."); + pw.println(" stack [COMMAND] [...]: sub-commands for operating on activity stacks."); + pw.println(" move-task [true|false]"); + pw.println(" Move from its current stack to the top (true) or"); + pw.println(" bottom (false) of ."); + pw.println(" list"); + pw.println(" List all of the activity stacks and their sizes."); + pw.println(" info "); + pw.println(" Display the information about activity stack in and ."); + pw.println(" remove "); + pw.println(" Remove stack ."); + pw.println(" task [COMMAND] [...]: sub-commands for operating on activity tasks."); + pw.println(" lock "); + pw.println(" Bring to the front and don't allow other tasks to run."); + pw.println(" lock stop"); + pw.println(" End the current task lock."); + pw.println(" resizeable [0|1|2|3]"); + pw.println(" Change resizeable mode of to one of the following:"); + pw.println(" 0: unresizeable"); + pw.println(" 1: crop_windows"); + pw.println(" 2: resizeable"); + pw.println(" 3: resizeable_and_pipable"); + pw.println(" resize "); + pw.println(" Makes sure is in a stack with the specified bounds."); + pw.println(" Forces the task to be resizeable and creates a stack if no existing stack"); + pw.println(" has the specified bounds."); + pw.println(" update-appinfo [...]"); + pw.println(" Update the ApplicationInfo objects of the listed packages for "); + pw.println(" without restarting any processes."); + pw.println(" write"); + pw.println(" Write all pending state to storage."); + pw.println(" compat [COMMAND] [...]: sub-commands for toggling app-compat changes."); + pw.println(" enable|disable [--no-kill] "); + pw.println(" Toggles a change either by id or by name for ."); + pw.println(" It kills (to allow the toggle to take effect) unless --no-kill is provided."); + pw.println(" reset "); + pw.println(" Toggles a change either by id or by name for ."); + pw.println(" It kills (to allow the toggle to take effect)."); + pw.println(" enable-all|disable-all "); + pw.println(" Toggles all changes that are gated by ."); + pw.println(" reset-all [--no-kill] "); + pw.println(" Removes all existing overrides for all changes for "); + pw.println(" (back to default behaviour)."); + pw.println(" It kills (to allow the toggle to take effect) unless --no-kill is provided."); + pw.println(" memory-factor [command] [...]: sub-commands for overriding memory pressure factor"); + pw.println(" set "); + pw.println(" Overrides memory pressure factor. May also supply a raw int level"); + pw.println(" show"); + pw.println(" Shows the existing memory pressure factor"); + pw.println(" reset"); + pw.println(" Removes existing override for memory pressure factor"); + pw.println(" service-restart-backoff [...]: sub-commands to toggle service restart backoff policy."); + pw.println(" enable|disable "); + pw.println(" Toggles the restart backoff policy on/off for ."); + pw.println(" show "); + pw.println(" Shows the restart backoff policy state for ."); + pw.println(" get-isolated-pids "); + pw.println(" Get the PIDs of isolated processes with packages in this "); + pw.println(" set-stop-user-on-switch [true|false]"); + pw.println(" Sets whether the current user (and its profiles) should be stopped" + + " when switching to a different user."); + pw.println(" Without arguments, it resets to the value defined by platform."); + pw.println(" set-bg-abusive-uids [uid=percentage][,uid=percentage...]"); + pw.println(" Force setting the battery usage of the given UID."); + pw.println(" set-bg-restriction-level [--user ] unrestricted|exempted|adaptive_bucket|restricted_bucket|background_restricted|hibernation"); + pw.println(" Set an app's background restriction level which in turn map to a app standby bucket."); + pw.println(" get-bg-restriction-level [--user ] "); + pw.println(" Get an app's background restriction level."); + pw.println(" list-displays-for-starting-users"); + pw.println(" Lists the id of displays that can be used to start users on " + + "background."); + pw.println(" set-foreground-service-delegate [--user ] start|stop"); + pw.println(" Start/stop an app's foreground service delegate."); + pw.println(" set-ignore-delivery-group-policy "); + pw.println(" Start ignoring delivery group policy set for a broadcast action"); + pw.println(" clear-ignore-delivery-group-policy "); + pw.println(" Stop ignoring delivery group policy set for a broadcast action"); + pw.println(" capabilities [--protobuf]"); + pw.println(" Output am supported features (text format). Options are:"); + pw.println(" --protobuf: format output using protobuffer"); + Intent.printIntentArgsHelp(pw, ""); + } + } +} diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/HwActivityManagerShellCommand.java b/aosp/frameworks/base/services/core/java/com/android/server/am/HwActivityManagerShellCommand.java new file mode 100644 index 000000000..9c7cc93ed --- /dev/null +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/HwActivityManagerShellCommand.java @@ -0,0 +1,143 @@ +/* ****************************************************************************** + * Copyright Notice: + * Copyright 2023-2025, Huawei Technologies Co., Ltd. ALL Rights Reserved. + * + * Warning: This computer software sourcecode is protected by copyright law + * and international treaties. Unauthorized reproduction or distribution + * of this sourcecode, or any portion of it, may result in severe civil and + * criminal penalties, and will be prosecuted to the maximum extent + * possible under the law. + * ******************************************************************************* + */ +package com.android.server.am; + +import com.android.config.EnableRootManager; + +import android.app.IActivityManager; +import android.graphics.Rect; +import android.os.RemoteException; + +import java.io.PrintWriter; + +public class HwActivityManagerShellCommand { + + private ActivityManagerShellCommand mCommand; + private final IActivityManager mInterface; + private static final int ERR_CODE_UNKNOWN_COMMAND = -1; + private static final int ERR_CODE_UNKNOWN_OPTION = -2; + + private Rect mLaunchBounds; + + protected HwActivityManagerShellCommand(ActivityManagerShellCommand command, ActivityManagerService service) { + mCommand = command; + mInterface = service; + } + + public int onCommand(String cmd) { + if (cmd == null || !EnableRootManager.enableRootManager()) { + return ERR_CODE_UNKNOWN_COMMAND; + } + PrintWriter pw = mCommand.getOutPrintWriter(); + try { + switch (cmd) { + case "add-root-permission": + return runAddRootPermission(); + case "remove-root-permission": + return runRemoveRootPermission(); + case "clear-root-permission": + return runClearRootPermission(); + case "show-root-permission": + return runShowRootPermission(pw); + case "root-permission": + return runRootPermission(pw); + default: + return ERR_CODE_UNKNOWN_COMMAND; + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return ERR_CODE_UNKNOWN_COMMAND; + } + + private int runAddRootPermission() throws RemoteException { + String packageName = mCommand.getNextArgRequired(); + mInterface.addPackageNameToSuWhiteList(packageName); + return 0; + } + + private int runRemoveRootPermission() throws RemoteException { + String packageName = mCommand.getNextArgRequired(); + mInterface.removePackageNameFromSuWhiteList(packageName); + return 0; + } + + private int runClearRootPermission() throws RemoteException { + mInterface.clearSuWhiteList(); + return 0; + } + + private int runShowRootPermission(PrintWriter pw) throws RemoteException { + String result = mInterface.getAllPackageNamesOfSuWhiteList(); + pw.println(result); + return 0; + } + + private int runRootPermission(PrintWriter pw) throws RemoteException { + String opt = mCommand.getNextOption(); + if (opt == null) { + mCommand.getErrPrintWriter().println("Error: need 1 option"); + return ERR_CODE_UNKNOWN_OPTION; + } + + String packageName = null; + if (opt.equals("-a") || opt.equals("-d")) { + packageName = mCommand.getNextArgRequired(); + if (packageName == null || packageName.length() == 0) { + mCommand.getErrPrintWriter().println("Error: need 1 argment"); + return ERR_CODE_UNKNOWN_OPTION; + } + } + + switch (opt) { + case "-a": + mInterface.addPackageNameToSuWhiteList(packageName); + break; + case "-d": + mInterface.removePackageNameFromSuWhiteList(packageName); + break; + case "-c": + mInterface.clearSuWhiteList(); + break; + case "-s": + String result = mInterface.getAllPackageNamesOfSuWhiteList(); + pw.println(result); + break; + default: + mCommand.getErrPrintWriter().println("Error: Unknown option: " + opt); + return ERR_CODE_UNKNOWN_OPTION; + } + return 0; + } + + protected static void onHelp(PrintWriter pw) { + if (!EnableRootManager.enableRootManager()) { + return; + } + pw.println(" add-root-permission "); + pw.println(" Allow the given application package to have the root access."); + pw.println(" remove-root-permission "); + pw.println(" Prevent the given application package from having the root access."); + pw.println(" clear-root-permission"); + pw.println(" Remove all the user added application packages which have the root access."); + pw.println(" show-root-permission"); + pw.println(" Show all the application packages which have the root access."); + pw.println(" root-permission [-a | -d | -c | -s] "); + pw.println(" Application root access management. Options are:"); + pw.println(" -a: Allow the given application package to have the root access."); + pw.println(" -d: Prevent the given application package from having the root access."); + pw.println(" -c: Remove all the user added application packages which have the root access."); + pw.println(" -s: Show all the application packages which have the root access."); + pw.println(); + } + +} \ No newline at end of file diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java b/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java new file mode 100644 index 000000000..89c89944e --- /dev/null +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java @@ -0,0 +1,5824 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; +import static android.app.ActivityThread.PROC_START_SEQ_IDENT; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; +import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; +import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.os.Process.SYSTEM_UID; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; +import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; +import static android.os.Process.getAdvertisedMem; +import static android.os.Process.getFreeMemory; +import static android.os.Process.getTotalMemory; +import static android.os.Process.killProcessQuiet; +import static android.os.Process.startWebView; +import static android.system.OsConstants.EAGAIN; + +import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG; +import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESS_DIED_UI_MSG; +import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG; +import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_DELAY_MS; +import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_MSG; +import static com.android.server.am.ActivityManagerService.PERSISTENT_MASK; +import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT; +import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT_MSG; +import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT_WITH_WRAPPER; +import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; +import static com.android.server.am.ActivityManagerService.TAG_LRU; +import static com.android.server.am.ActivityManagerService.TAG_NETWORK; +import static com.android.server.am.ActivityManagerService.TAG_PROCESSES; +import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.ProcessCapability; +import android.app.ActivityThread; +import android.app.AppGlobals; +import android.app.AppProtoEnums; +import android.app.ApplicationExitInfo; +import android.app.ApplicationExitInfo.Reason; +import android.app.ApplicationExitInfo.SubReason; +import android.app.IApplicationThread; +import android.app.IProcessObserver; +import android.app.UidObserver; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.content.res.Resources; +import android.graphics.Point; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.AppZygote; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.DropBoxManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.OomKillRecord; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.StrictMode; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +import android.os.storage.StorageManagerInternal; +import android.provider.DeviceConfig; +import android.system.Os; +import android.system.OsConstants; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.DebugUtils; +import android.util.EventLog; +import android.util.LongSparseArray; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; +import android.view.Display; + +import com.android.internal.annotations.CompositeRWLock; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ProcessMap; +import com.android.internal.os.Zygote; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.MemInfoReader; +import com.android.server.AppStateTracker; +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.SystemConfig; +import com.android.server.Watchdog; +import com.android.server.am.ActivityManagerService.ProcessChangeItem; +import com.android.server.compat.PlatformCompat; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.wm.ActivityServiceConnectionsHolder; +import com.android.server.wm.WindowManagerService; +import com.android.server.wm.WindowProcessController; + +import dalvik.system.VMRuntime; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Activity manager code dealing with processes. + */ +public final class ProcessList { + static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; + + static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS; + + // A system property to control if app data isolation is enabled. + static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.zygote.app_data_isolation"; + + // A system property to control if obb app data isolation is enabled in vold. + static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.sys.vold_app_data_isolation_enabled"; + + private static final String APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = ":isSdkSandboxAudit"; + private static final String APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = ":isSdkSandboxNext"; + + // OOM adjustments for processes in various states: + + // Uninitialized value for any major or minor adj fields + public static final int INVALID_ADJ = -10000; + + // Adjustment used in certain places where we don't know it yet. + // (Generally this is something that is going to be cached, but we + // don't know the exact value in the cached range to assign yet.) + public static final int UNKNOWN_ADJ = 1001; + + // This is a process only hosting activities that are not visible, + // so it can be killed without any disruption. + public static final int CACHED_APP_MAX_ADJ = 999; + public static final int CACHED_APP_MIN_ADJ = 900; + + // This is the oom_adj level that we allow to die first. This cannot be equal to + // CACHED_APP_MAX_ADJ unless processes are actively being assigned an oom_score_adj of + // CACHED_APP_MAX_ADJ. + public static final int CACHED_APP_LMK_FIRST_ADJ = 950; + + // Number of levels we have available for different service connection group importance + // levels. + static final int CACHED_APP_IMPORTANCE_LEVELS = 5; + + // The B list of SERVICE_ADJ -- these are the old and decrepit + // services that aren't as shiny and interesting as the ones in the A list. + public static final int SERVICE_B_ADJ = 800; + + // This is the process of the previous application that the user was in. + // This process is kept above other things, because it is very common to + // switch back to the previous app. This is important both for recent + // task switch (toggling between the two top recent apps) as well as normal + // UI flow such as clicking on a URI in the e-mail app to view in the browser, + // and then pressing back to return to e-mail. + public static final int PREVIOUS_APP_ADJ = 700; + + // This is a process holding the home application -- we want to try + // avoiding killing it, even if it would normally be in the background, + // because the user interacts with it so much. + public static final int HOME_APP_ADJ = 600; + + // This is a process holding an application service -- killing it will not + // have much of an impact as far as the user is concerned. + public static final int SERVICE_ADJ = 500; + + // This is a process with a heavy-weight application. It is in the + // background, but we want to try to avoid killing it. Value set in + // system/rootdir/init.rc on startup. + public static final int HEAVY_WEIGHT_APP_ADJ = 400; + + // This is a process currently hosting a backup operation. Killing it + // is not entirely fatal but is generally a bad idea. + public static final int BACKUP_APP_ADJ = 300; + + // This is a process bound by the system (or other app) that's more important than services but + // not so perceptible that it affects the user immediately if killed. + public static final int PERCEPTIBLE_LOW_APP_ADJ = 250; + + // This is a process hosting services that are not perceptible to the user but the + // client (system) binding to it requested to treat it as if it is perceptible and avoid killing + // it if possible. + public static final int PERCEPTIBLE_MEDIUM_APP_ADJ = 225; + + // This is a process only hosting components that are perceptible to the + // user, and we really want to avoid killing them, but they are not + // immediately visible. An example is background music playback. + public static final int PERCEPTIBLE_APP_ADJ = 200; + + // This is a process only hosting activities that are visible to the + // user, so we'd prefer they don't disappear. + public static final int VISIBLE_APP_ADJ = 100; + static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1; + + // This is a process that was recently TOP and moved to FGS. Continue to treat it almost + // like a foreground app for a while. + // @see TOP_TO_FGS_GRACE_PERIOD + public static final int PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ = 50; + + // This is the process running the current foreground app. We'd really + // rather not kill it! + public static final int FOREGROUND_APP_ADJ = 0; + + // This is a process that the system or a persistent process has bound to, + // and indicated it is important. + public static final int PERSISTENT_SERVICE_ADJ = -700; + + // This is a system persistent process, such as telephony. Definitely + // don't want to kill it, but doing so is not completely fatal. + public static final int PERSISTENT_PROC_ADJ = -800; + + // The system process runs at the default adjustment. + public static final int SYSTEM_ADJ = -900; + + // Special code for native processes that are not being managed by the system (so + // don't have an oom adj assigned by the system). + public static final int NATIVE_ADJ = -1000; + + // Memory page size. + static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE); + + // Activity manager's version of an undefined schedule group + static final int SCHED_GROUP_UNDEFINED = Integer.MIN_VALUE; + // Activity manager's version of Process.THREAD_GROUP_BACKGROUND + static final int SCHED_GROUP_BACKGROUND = 0; + // Activity manager's version of Process.THREAD_GROUP_RESTRICTED + static final int SCHED_GROUP_RESTRICTED = 1; + // Activity manager's version of Process.THREAD_GROUP_DEFAULT + static final int SCHED_GROUP_DEFAULT = 2; + // Activity manager's version of Process.THREAD_GROUP_TOP_APP + public static final int SCHED_GROUP_TOP_APP = 3; + // Activity manager's version of Process.THREAD_GROUP_TOP_APP + // Disambiguate between actual top app and processes bound to the top app + static final int SCHED_GROUP_TOP_APP_BOUND = 4; + + // The minimum number of cached apps we want to be able to keep around, + // without empty apps being able to push them out of memory. + static final int MIN_CACHED_APPS = 2; + + // Threshold of number of cached+empty where we consider memory critical. + static final int TRIM_CRITICAL_THRESHOLD = 3; + + // Threshold of number of cached+empty where we consider memory critical. + static final int TRIM_LOW_THRESHOLD = 5; + + /** + * State indicating that there is no need for any blocking for network. + */ + @VisibleForTesting + static final int NETWORK_STATE_NO_CHANGE = 0; + + /** + * State indicating that the main thread needs to be informed about the network wait. + */ + @VisibleForTesting + static final int NETWORK_STATE_BLOCK = 1; + + /** + * State indicating that any threads waiting for network state to get updated can be unblocked. + */ + @VisibleForTesting + static final int NETWORK_STATE_UNBLOCK = 2; + + // If true, then we pass the flag to ART to load the app image startup cache. + private static final String PROPERTY_USE_APP_IMAGE_STARTUP_CACHE = + "persist.device_config.runtime_native.use_app_image_startup_cache"; + + // The socket path for zygote to send unsolicited msg. + // Must keep sync with com_android_internal_os_Zygote.cpp. + private static final String UNSOL_ZYGOTE_MSG_SOCKET_PATH = "/data/system/unsolzygotesocket"; + + // Low Memory Killer Daemon command codes. + // These must be kept in sync with lmk_cmd definitions in lmkd.h + // + // LMK_TARGET ... (up to 6 pairs) + // LMK_PROCPRIO + // LMK_PROCREMOVE + // LMK_PROCPURGE + // LMK_GETKILLCNT + // LMK_SUBSCRIBE + // LMK_PROCKILL + // LMK_UPDATE_PROPS + // LMK_KILL_OCCURRED + // LMK_START_MONITORING + static final byte LMK_TARGET = 0; + static final byte LMK_PROCPRIO = 1; + static final byte LMK_PROCREMOVE = 2; + static final byte LMK_PROCPURGE = 3; + static final byte LMK_GETKILLCNT = 4; + static final byte LMK_SUBSCRIBE = 5; + static final byte LMK_PROCKILL = 6; // Note: this is an unsolicited command + static final byte LMK_UPDATE_PROPS = 7; + static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event + static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier + + // Low Memory Killer Daemon command codes. + // These must be kept in sync with async_event_type definitions in lmkd.h + // + static final int LMK_ASYNC_EVENT_KILL = 0; + static final int LMK_ASYNC_EVENT_STAT = 1; + + // lmkd reconnect delay in msecs + private static final long LMKD_RECONNECT_DELAY_MS = 1000; + + /** + * The cuttoff adj for the freezer, app processes with adj greater than this value will be + * eligible for the freezer. + */ + static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ; + + /** + * Apps have no access to the private data directories of any other app, even if the other + * app has made them world-readable. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long APP_DATA_DIRECTORY_ISOLATION = 143937733; // See b/143937733 + + ActivityManagerService mService = null; + + // To kill process groups asynchronously + static KillHandler sKillHandler = null; + static ServiceThread sKillThread = null; + + // These are the various interesting memory levels that we will give to + // the OOM killer. Note that the OOM killer only supports 6 slots, so we + // can't give it a different value for every possible kind of process. + private final int[] mOomAdj = new int[] { + FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, + PERCEPTIBLE_LOW_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ + }; + // These are the low-end OOM level limits. This is appropriate for an + // HVGA or smaller phone with less than 512MB. Values are in KB. + private final int[] mOomMinFreeLow = new int[] { + 12288, 18432, 24576, + 36864, 43008, 49152 + }; + // These are the high-end OOM level limits. This is appropriate for a + // 1280x800 or larger screen with around 1GB RAM. Values are in KB. + private final int[] mOomMinFreeHigh = new int[] { + 73728, 92160, 110592, + 129024, 147456, 184320 + }; + // The actual OOM killer memory levels we are using. + private final int[] mOomMinFree = new int[mOomAdj.length]; + + private final long mTotalMemMb; + + private long mCachedRestoreLevel; + + private boolean mHaveDisplaySize; + + private static LmkdConnection sLmkdConnection = null; + + private static OomConnection sOomConnection = null; + + private boolean mOomLevelsSet = false; + + private boolean mAppDataIsolationEnabled = false; + + private boolean mVoldAppDataIsolationEnabled = false; + + private ArrayList mAppDataIsolationAllowlistedApps; + + /** + * Temporary to avoid allocations. Protected by main lock. + */ + @GuardedBy("mService") + final StringBuilder mStringBuilder = new StringBuilder(256); + + /** + * A global counter for generating sequence numbers. + * This value will be used when incrementing sequence numbers in individual uidRecords. + * + * Having a global counter ensures that seq numbers are monotonically increasing for a + * particular uid even when the uidRecord is re-created. + */ + @VisibleForTesting + volatile long mProcStateSeqCounter = 0; + + /** + * A global counter for generating sequence numbers to uniquely identify pending process starts. + */ + @GuardedBy("mService") + private long mProcStartSeqCounter = 0; + + /** + * Contains {@link ProcessRecord} objects for pending process starts. + * + * Mapping: {@link #mProcStartSeqCounter} -> {@link ProcessRecord} + */ + @GuardedBy("mService") + final LongSparseArray mPendingStarts = new LongSparseArray<>(); + + /** + * List of running applications, sorted by recent usage. + * The first entry in the list is the least recently used. + */ + @CompositeRWLock({"mService", "mProcLock"}) + private final ArrayList mLruProcesses = new ArrayList(); + + /** + * Where in mLruProcesses that the processes hosting activities start. + */ + @CompositeRWLock({"mService", "mProcLock"}) + private int mLruProcessActivityStart = 0; + + /** + * Where in mLruProcesses that the processes hosting services start. + * This is after (lower index) than mLruProcessesActivityStart. + */ + @CompositeRWLock({"mService", "mProcLock"}) + private int mLruProcessServiceStart = 0; + + /** + * Current sequence id for process LRU updating. + */ + @CompositeRWLock({"mService", "mProcLock"}) + private int mLruSeq = 0; + + @CompositeRWLock({"mService", "mProcLock"}) + ActiveUids mActiveUids; + + /** + * The currently running isolated processes. + */ + @GuardedBy("mService") + final SparseArray mIsolatedProcesses = new SparseArray<>(); + + /** + * The currently running application zygotes. + */ + @GuardedBy("mService") + final ProcessMap mAppZygotes = new ProcessMap(); + + /** Manages the {@link android.app.ApplicationStartInfo} records. */ + @GuardedBy("mAppStartInfoTracker") + private final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker(); + + /** + * The currently running SDK sandbox processes for a uid. + */ + @GuardedBy("mService") + final SparseArray> mSdkSandboxes = new SparseArray<>(); + + /** + * Managees the {@link android.app.ApplicationExitInfo} records. + */ + @GuardedBy("mAppExitInfoTracker") + final AppExitInfoTracker mAppExitInfoTracker = new AppExitInfoTracker(); + + /** + * The processes that are forked off an application zygote. + */ + @GuardedBy("mService") + final ArrayMap> mAppZygoteProcesses = + new ArrayMap>(); + + /** + * The list of apps in background restricted mode. + */ + @GuardedBy("mService") + final ArraySet mAppsInBackgroundRestricted = new ArraySet<>(); + + private PlatformCompat mPlatformCompat = null; + + /** + * The server socket in system_server, zygote will connect to it + * in order to send unsolicited messages to system_server. + */ + private LocalSocket mSystemServerSocketForZygote; + + /** + * Maximum number of bytes that an incoming unsolicited zygote message could be. + * To be updated if new message type needs to be supported. + */ + private static final int MAX_ZYGOTE_UNSOLICITED_MESSAGE_SIZE = 16; + + /** + * The buffer to be used to receive the incoming unsolicited zygote message. + */ + private final byte[] mZygoteUnsolicitedMessage = new byte[MAX_ZYGOTE_UNSOLICITED_MESSAGE_SIZE]; + + /** + * The buffer to be used to receive the SIGCHLD data, it includes pid/uid/status. + */ + private final int[] mZygoteSigChldMessage = new int[3]; + + ActivityManagerGlobalLock mProcLock; + + private static final String PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = + "apply_sdk_sandbox_audit_restrictions"; + private static final boolean DEFAULT_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = false; + + private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = + "apply_sdk_sandbox_next_restrictions"; + private static final boolean DEFAULT_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = false; + + @GuardedBy("mService") + private ProcessListSettingsListener mProcessListSettingsListener; + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + ProcessListSettingsListener getProcessListSettingsListener() { + synchronized (mService) { + if (mProcessListSettingsListener == null) { + mProcessListSettingsListener = new ProcessListSettingsListener(mService.mContext); + mProcessListSettingsListener.registerObserver(); + } + return mProcessListSettingsListener; + } + } + + static class ProcessListSettingsListener implements DeviceConfig.OnPropertiesChangedListener { + + private final Context mContext; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private boolean mSdkSandboxApplyRestrictionsAudit = + DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ADSERVICES, + PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS, + DEFAULT_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS); + + @GuardedBy("mLock") + private boolean mSdkSandboxApplyRestrictionsNext = + DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ADSERVICES, + PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, + DEFAULT_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS); + + ProcessListSettingsListener(Context context) { + mContext = context; + } + + private void registerObserver() { + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_ADSERVICES, mContext.getMainExecutor(), this); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + void unregisterObserver() { + DeviceConfig.removeOnPropertiesChangedListener(this); + } + + boolean applySdkSandboxRestrictionsAudit() { + synchronized (mLock) { + return mSdkSandboxApplyRestrictionsAudit; + } + } + + boolean applySdkSandboxRestrictionsNext() { + synchronized (mLock) { + return mSdkSandboxApplyRestrictionsNext; + } + } + + @Override + public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + synchronized (mLock) { + for (String name : properties.getKeyset()) { + if (name == null) { + continue; + } + + switch (name) { + case PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS: + mSdkSandboxApplyRestrictionsAudit = + properties.getBoolean( + PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS, + DEFAULT_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS); + break; + case PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS: + mSdkSandboxApplyRestrictionsNext = + properties.getBoolean( + PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, + DEFAULT_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS); + break; + default: + } + } + } + } + } + + final class IsolatedUidRange { + @VisibleForTesting + public final int mFirstUid; + @VisibleForTesting + public final int mLastUid; + + @GuardedBy("ProcessList.this.mService") + private final SparseBooleanArray mUidUsed = new SparseBooleanArray(); + + @GuardedBy("ProcessList.this.mService") + private int mNextUid; + + IsolatedUidRange(int firstUid, int lastUid) { + mFirstUid = firstUid; + mLastUid = lastUid; + mNextUid = firstUid; + } + + @GuardedBy("ProcessList.this.mService") + int allocateIsolatedUidLocked(int userId) { + int uid; + int stepsLeft = (mLastUid - mFirstUid + 1); + for (int i = 0; i < stepsLeft; ++i) { + if (mNextUid < mFirstUid || mNextUid > mLastUid) { + mNextUid = mFirstUid; + } + uid = UserHandle.getUid(userId, mNextUid); + mNextUid++; + if (!mUidUsed.get(uid, false)) { + mUidUsed.put(uid, true); + return uid; + } + } + return -1; + } + + @GuardedBy("ProcessList.this.mService") + void freeIsolatedUidLocked(int uid) { + mUidUsed.delete(uid); + } + }; + + /** + * A class that allocates ranges of isolated UIDs per application, and keeps track of them. + */ + final class IsolatedUidRangeAllocator { + private final int mFirstUid; + private final int mNumUidRanges; + private final int mNumUidsPerRange; + /** + * We map the uid range [mFirstUid, mFirstUid + mNumUidRanges * mNumUidsPerRange) + * back to an underlying bitset of [0, mNumUidRanges) and allocate out of that. + */ + @GuardedBy("ProcessList.this.mService") + private final BitSet mAvailableUidRanges; + @GuardedBy("ProcessList.this.mService") + private final ProcessMap mAppRanges = new ProcessMap(); + + IsolatedUidRangeAllocator(int firstUid, int lastUid, int numUidsPerRange) { + mFirstUid = firstUid; + mNumUidsPerRange = numUidsPerRange; + mNumUidRanges = (lastUid - firstUid + 1) / numUidsPerRange; + mAvailableUidRanges = new BitSet(mNumUidRanges); + // Mark all as available + mAvailableUidRanges.set(0, mNumUidRanges); + } + + @GuardedBy("ProcessList.this.mService") + IsolatedUidRange getIsolatedUidRangeLocked(String processName, int uid) { + return mAppRanges.get(processName, uid); + } + + @GuardedBy("ProcessList.this.mService") + IsolatedUidRange getOrCreateIsolatedUidRangeLocked(String processName, int uid) { + IsolatedUidRange range = getIsolatedUidRangeLocked(processName, uid); + if (range == null) { + int uidRangeIndex = mAvailableUidRanges.nextSetBit(0); + if (uidRangeIndex < 0) { + // No free range + return null; + } + mAvailableUidRanges.clear(uidRangeIndex); + int actualUid = mFirstUid + uidRangeIndex * mNumUidsPerRange; + range = new IsolatedUidRange(actualUid, actualUid + mNumUidsPerRange - 1); + mAppRanges.put(processName, uid, range); + } + return range; + } + + @GuardedBy("ProcessList.this.mService") + void freeUidRangeLocked(ApplicationInfo info) { + // Find the UID range + IsolatedUidRange range = mAppRanges.get(info.processName, info.uid); + if (range != null) { + // Map back to starting uid + final int uidRangeIndex = (range.mFirstUid - mFirstUid) / mNumUidsPerRange; + // Mark it as available in the underlying bitset + mAvailableUidRanges.set(uidRangeIndex); + // And the map + mAppRanges.remove(info.processName, info.uid); + } + } + } + + /** + * The available isolated UIDs for processes that are not spawned from an application zygote. + */ + @VisibleForTesting + @GuardedBy("mService") + IsolatedUidRange mGlobalIsolatedUids = new IsolatedUidRange(Process.FIRST_ISOLATED_UID, + Process.LAST_ISOLATED_UID); + + /** + * An allocator for isolated UID ranges for apps that use an application zygote. + */ + @VisibleForTesting + @GuardedBy("mService") + IsolatedUidRangeAllocator mAppIsolatedUidRangeAllocator = + new IsolatedUidRangeAllocator(Process.FIRST_APP_ZYGOTE_ISOLATED_UID, + Process.LAST_APP_ZYGOTE_ISOLATED_UID, Process.NUM_UIDS_PER_APP_ZYGOTE); + + /** + * Processes that are being forcibly torn down. + */ + @GuardedBy("mService") + final ArrayList mRemovedProcesses = new ArrayList(); + + /** + * Processes that are killed by us and being waiting for the death notification. + */ + @GuardedBy("mService") + final ProcessMap mDyingProcesses = new ProcessMap<>(); + + // Self locked with the inner lock within the RemoteCallbackList + private final RemoteCallbackList mProcessObservers = + new RemoteCallbackList<>(); + + // No lock is needed as it's accessed from single thread only + private ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5]; + + @GuardedBy("mProcessChangeLock") + private final ArrayList mPendingProcessChanges = new ArrayList<>(); + + @GuardedBy("mProcessChangeLock") + final ArrayList mAvailProcessChanges = new ArrayList<>(); + + /** + * A dedicated lock for dispatching the process changes as it occurs frequently + */ + private final Object mProcessChangeLock = new Object(); + + /** + * All of the applications we currently have running organized by name. + * The keys are strings of the application package name (as + * returned by the package manager), and the keys are ApplicationRecord + * objects. + */ + @CompositeRWLock({"mService", "mProcLock"}) + private final MyProcessMap mProcessNames = new MyProcessMap(); + + final class MyProcessMap extends ProcessMap { + @Override + public ProcessRecord put(String name, int uid, ProcessRecord value) { + final ProcessRecord r = super.put(name, uid, value); + mService.mAtmInternal.onProcessAdded(r.getWindowProcessController()); + return r; + } + + @Override + public ProcessRecord remove(String name, int uid) { + final ProcessRecord r = super.remove(name, uid); + mService.mAtmInternal.onProcessRemoved(name, uid); + return r; + } + } + + final class KillHandler extends Handler { + static final int KILL_PROCESS_GROUP_MSG = 4000; + static final int LMKD_RECONNECT_MSG = 4001; + + public KillHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case KILL_PROCESS_GROUP_MSG: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup"); + Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case LMKD_RECONNECT_MSG: + if (!sLmkdConnection.connect()) { + Slog.i(TAG, "Failed to connect to lmkd, retry after " + + LMKD_RECONNECT_DELAY_MS + " ms"); + // retry after LMKD_RECONNECT_DELAY_MS + sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage( + KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS); + } + break; + default: + super.handleMessage(msg); + } + } + } + + /** + * A runner to handle the imperceptible killings. + */ + ImperceptibleKillRunner mImperceptibleKillRunner; + + //////////////////// END FIELDS //////////////////// + + ProcessList() { + MemInfoReader minfo = new MemInfoReader(); + minfo.readMemInfo(); + mTotalMemMb = minfo.getTotalSize()/(1024*1024); + updateOomLevels(0, 0, false); + } + + void init(ActivityManagerService service, ActiveUids activeUids, + PlatformCompat platformCompat) { + mService = service; + mActiveUids = activeUids; + mPlatformCompat = platformCompat; + mProcLock = service.mProcLock; + // Get this after boot, and won't be changed until it's rebooted, as we don't + // want some apps enabled while some apps disabled + mAppDataIsolationEnabled = + SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true); + mVoldAppDataIsolationEnabled = SystemProperties.getBoolean( + ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false); + mAppDataIsolationAllowlistedApps = new ArrayList<>( + SystemConfig.getInstance().getAppDataIsolationWhitelistedApps()); + + if (sKillHandler == null) { + sKillThread = new ServiceThread(TAG + ":kill", + THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + sKillThread.start(); + sKillHandler = new KillHandler(sKillThread.getLooper()); + sOomConnection = new OomConnection(new OomConnection.OomConnectionListener() { + @Override + public void handleOomEvent(OomKillRecord[] oomKills) { + for (OomKillRecord oomKill: oomKills) { + synchronized (mProcLock) { + noteAppKill( + oomKill.getPid(), + oomKill.getUid(), + ApplicationExitInfo.REASON_LOW_MEMORY, + ApplicationExitInfo.SUBREASON_OOM_KILL, + "oom"); + + oomKill.logKillOccurred(); + } + } + } + }); + sLmkdConnection = new LmkdConnection(sKillThread.getLooper().getQueue(), + new LmkdConnection.LmkdConnectionListener() { + @Override + public boolean onConnect(OutputStream ostream) { + Slog.i(TAG, "Connection with lmkd established"); + return onLmkdConnect(ostream); + } + + @Override + public void onDisconnect() { + Slog.w(TAG, "Lost connection to lmkd"); + // start reconnection after delay to let lmkd restart + sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage( + KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS); + } + + @Override + public boolean isReplyExpected(ByteBuffer replyBuf, + ByteBuffer dataReceived, int receivedLen) { + // compare the preambule (currently one integer) to check if + // this is the reply packet we are waiting for + return (receivedLen == replyBuf.array().length && + dataReceived.getInt(0) == replyBuf.getInt(0)); + } + + @Override + public boolean handleUnsolicitedMessage(DataInputStream inputData, + int receivedLen) { + if (receivedLen < 4) { + return false; + } + + try { + switch (inputData.readInt()) { + case LMK_PROCKILL: + if (receivedLen != 12) { + return false; + } + final int pid = inputData.readInt(); + final int uid = inputData.readInt(); + mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid); + return true; + case LMK_KILL_OCCURRED: + if (receivedLen + < LmkdStatsReporter.KILL_OCCURRED_MSG_SIZE) { + return false; + } + // Note: directly access + // ActiveServices.sNumForegroundServices, do not try to + // hold AMS lock here, otherwise it is a potential deadlock. + Pair foregroundServices = + ActiveServices.sNumForegroundServices.get(); + LmkdStatsReporter.logKillOccurred(inputData, + foregroundServices.first, + foregroundServices.second); + return true; + default: + return false; + } + } catch (IOException e) { + Slog.e(TAG, "Invalid buffer data. Failed to log LMK_KILL_OCCURRED"); + } + return false; + } + } + ); + // Start listening on incoming connections from zygotes. + mSystemServerSocketForZygote = createSystemServerSocketForZygote(); + if (mSystemServerSocketForZygote != null) { + sKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener( + mSystemServerSocketForZygote.getFileDescriptor(), + EVENT_INPUT, this::handleZygoteMessages); + } + mAppStartInfoTracker.init(mService); + mAppExitInfoTracker.init(mService); + mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper()); + } + } + + void onSystemReady() { + mAppStartInfoTracker.onSystemReady(); + mAppExitInfoTracker.onSystemReady(); + } + + void applyDisplaySize(WindowManagerService wm) { + if (!mHaveDisplaySize) { + Point p = new Point(); + // TODO(multi-display): Compute based on sum of all connected displays' resolutions. + wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p); + if (p.x != 0 && p.y != 0) { + updateOomLevels(p.x, p.y, true); + mHaveDisplaySize = true; + } + } + } + + /** + * Get a map of pid and package name that process of that pid Android/data and Android/obb + * directory is not mounted to lowerfs to speed up access. + */ + Map getProcessesWithPendingBindMounts(int userId) { + final Map pidPackageMap = new HashMap<>(); + synchronized (mProcLock) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + final ProcessRecord record = mLruProcesses.get(i); + if (record.userId != userId || !record.isBindMountPending()) { + continue; + } + final int pid = record.getPid(); + // It can happen when app process is starting, but zygote work is not done yet so + // system does not this pid record yet. + if (pid == 0) { + throw new IllegalStateException("Pending process is not started yet," + + "retry later"); + } + pidPackageMap.put(pid, record.info.packageName); + } + } + return pidPackageMap; + } + + private void updateOomLevels(int displayWidth, int displayHeight, boolean write) { + // Scale buckets from avail memory: at 300MB we use the lowest values to + // 700MB or more for the top values. + float scaleMem = ((float) (mTotalMemMb - 350)) / (700 - 350); + + // Scale buckets from screen size. + int minSize = 480 * 800; // 384000 + int maxSize = 1280 * 800; // 1024000 230400 870400 .264 + float scaleDisp = ((float)(displayWidth * displayHeight) - minSize) / (maxSize - minSize); + if (false) { + Slog.i("XXXXXX", "scaleMem=" + scaleMem); + Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + + " dh=" + displayHeight); + } + + float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp; + if (scale < 0) scale = 0; + else if (scale > 1) scale = 1; + int minfree_adj = Resources.getSystem().getInteger( + com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust); + int minfree_abs = Resources.getSystem().getInteger( + com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute); + if (false) { + Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); + } + + final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0; + + for (int i = 0; i < mOomAdj.length; i++) { + int low = mOomMinFreeLow[i]; + int high = mOomMinFreeHigh[i]; + if (is64bit) { + // Increase the high min-free levels for cached processes for 64-bit + if (i == 4) high = (high * 3) / 2; + else if (i == 5) high = (high * 7) / 4; + } + mOomMinFree[i] = (int)(low + ((high - low) * scale)); + } + + if (minfree_abs >= 0) { + for (int i = 0; i < mOomAdj.length; i++) { + mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i] + / mOomMinFree[mOomAdj.length - 1]); + } + } + + if (minfree_adj != 0) { + for (int i = 0; i < mOomAdj.length; i++) { + mOomMinFree[i] += (int)((float) minfree_adj * mOomMinFree[i] + / mOomMinFree[mOomAdj.length - 1]); + if (mOomMinFree[i] < 0) { + mOomMinFree[i] = 0; + } + } + } + + // The maximum size we will restore a process from cached to background, when under + // memory duress, is 1/3 the size we have reserved for kernel caches and other overhead + // before killing background processes. + mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ) / 1024) / 3; + + // Ask the kernel to try to keep enough memory free to allocate 3 full + // screen 32bpp buffers without entering direct reclaim. + int reserve = displayWidth * displayHeight * 4 * 3 / 1024; + int reserve_adj = Resources.getSystem().getInteger( + com.android.internal.R.integer.config_extraFreeKbytesAdjust); + int reserve_abs = Resources.getSystem().getInteger( + com.android.internal.R.integer.config_extraFreeKbytesAbsolute); + + if (reserve_abs >= 0) { + reserve = reserve_abs; + } + + if (reserve_adj != 0) { + reserve += reserve_adj; + if (reserve < 0) { + reserve = 0; + } + } + + if (write) { + ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1)); + buf.putInt(LMK_TARGET); + for (int i = 0; i < mOomAdj.length; i++) { + buf.putInt((mOomMinFree[i] * 1024)/PAGE_SIZE); + buf.putInt(mOomAdj[i]); + } + + writeLmkd(buf, null); + SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); + mOomLevelsSet = true; + } + // GB: 2048,3072,4096,6144,7168,8192 + // HC: 8192,10240,12288,14336,16384,20480 + } + + public static int computeEmptyProcessLimit(int totalProcessLimit) { + return totalProcessLimit/2; + } + + private static String buildOomTag(String prefix, String compactPrefix, String space, int val, + int base, boolean compact) { + final int diff = val - base; + if (diff == 0) { + if (compact) { + return compactPrefix; + } + if (space == null) return prefix; + return prefix + space; + } + if (diff < 10) { + return prefix + (compact ? "+" : "+ ") + Integer.toString(diff); + } + return prefix + "+" + Integer.toString(diff); + } + + public static String makeOomAdjString(int setAdj, boolean compact) { + if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + return buildOomTag("cch", "cch", " ", setAdj, + ProcessList.CACHED_APP_MIN_ADJ, compact); + } else if (setAdj >= ProcessList.SERVICE_B_ADJ) { + return buildOomTag("svcb ", "svcb", null, setAdj, + ProcessList.SERVICE_B_ADJ, compact); + } else if (setAdj >= ProcessList.PREVIOUS_APP_ADJ) { + return buildOomTag("prev ", "prev", null, setAdj, + ProcessList.PREVIOUS_APP_ADJ, compact); + } else if (setAdj >= ProcessList.HOME_APP_ADJ) { + return buildOomTag("home ", "home", null, setAdj, + ProcessList.HOME_APP_ADJ, compact); + } else if (setAdj >= ProcessList.SERVICE_ADJ) { + return buildOomTag("svc ", "svc", null, setAdj, + ProcessList.SERVICE_ADJ, compact); + } else if (setAdj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) { + return buildOomTag("hvy ", "hvy", null, setAdj, + ProcessList.HEAVY_WEIGHT_APP_ADJ, compact); + } else if (setAdj >= ProcessList.BACKUP_APP_ADJ) { + return buildOomTag("bkup ", "bkup", null, setAdj, + ProcessList.BACKUP_APP_ADJ, compact); + } else if (setAdj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { + return buildOomTag("prcl ", "prcl", null, setAdj, + ProcessList.PERCEPTIBLE_LOW_APP_ADJ, compact); + } else if (setAdj >= ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ) { + return buildOomTag("prcm ", "prcm", null, setAdj, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, compact); + } else if (setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { + return buildOomTag("prcp ", "prcp", null, setAdj, + ProcessList.PERCEPTIBLE_APP_ADJ, compact); + } else if (setAdj >= ProcessList.VISIBLE_APP_ADJ) { + return buildOomTag("vis", "vis", " ", setAdj, + ProcessList.VISIBLE_APP_ADJ, compact); + } else if (setAdj >= ProcessList.FOREGROUND_APP_ADJ) { + return buildOomTag("fg ", "fg ", " ", setAdj, + ProcessList.FOREGROUND_APP_ADJ, compact); + } else if (setAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) { + return buildOomTag("psvc ", "psvc", null, setAdj, + ProcessList.PERSISTENT_SERVICE_ADJ, compact); + } else if (setAdj >= ProcessList.PERSISTENT_PROC_ADJ) { + return buildOomTag("pers ", "pers", null, setAdj, + ProcessList.PERSISTENT_PROC_ADJ, compact); + } else if (setAdj >= ProcessList.SYSTEM_ADJ) { + return buildOomTag("sys ", "sys", null, setAdj, + ProcessList.SYSTEM_ADJ, compact); + } else if (setAdj >= ProcessList.NATIVE_ADJ) { + return buildOomTag("ntv ", "ntv", null, setAdj, + ProcessList.NATIVE_ADJ, compact); + } else { + return Integer.toString(setAdj); + } + } + + public static String makeProcStateString(int curProcState) { + return ActivityManager.procStateToString(curProcState); + } + + public static int makeProcStateProtoEnum(int curProcState) { + switch (curProcState) { + case ActivityManager.PROCESS_STATE_PERSISTENT: + return AppProtoEnums.PROCESS_STATE_PERSISTENT; + case ActivityManager.PROCESS_STATE_PERSISTENT_UI: + return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI; + case ActivityManager.PROCESS_STATE_TOP: + return AppProtoEnums.PROCESS_STATE_TOP; + case ActivityManager.PROCESS_STATE_BOUND_TOP: + return AppProtoEnums.PROCESS_STATE_BOUND_TOP; + case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE: + return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE; + case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE: + return AppProtoEnums.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + case ActivityManager.PROCESS_STATE_TOP_SLEEPING: + return AppProtoEnums.PROCESS_STATE_TOP_SLEEPING; + case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: + return AppProtoEnums.PROCESS_STATE_IMPORTANT_FOREGROUND; + case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: + return AppProtoEnums.PROCESS_STATE_IMPORTANT_BACKGROUND; + case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND: + return AppProtoEnums.PROCESS_STATE_TRANSIENT_BACKGROUND; + case ActivityManager.PROCESS_STATE_BACKUP: + return AppProtoEnums.PROCESS_STATE_BACKUP; + case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: + return AppProtoEnums.PROCESS_STATE_HEAVY_WEIGHT; + case ActivityManager.PROCESS_STATE_SERVICE: + return AppProtoEnums.PROCESS_STATE_SERVICE; + case ActivityManager.PROCESS_STATE_RECEIVER: + return AppProtoEnums.PROCESS_STATE_RECEIVER; + case ActivityManager.PROCESS_STATE_HOME: + return AppProtoEnums.PROCESS_STATE_HOME; + case ActivityManager.PROCESS_STATE_LAST_ACTIVITY: + return AppProtoEnums.PROCESS_STATE_LAST_ACTIVITY; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + case ActivityManager.PROCESS_STATE_CACHED_RECENT: + return AppProtoEnums.PROCESS_STATE_CACHED_RECENT; + case ActivityManager.PROCESS_STATE_CACHED_EMPTY: + return AppProtoEnums.PROCESS_STATE_CACHED_EMPTY; + case ActivityManager.PROCESS_STATE_NONEXISTENT: + return AppProtoEnums.PROCESS_STATE_NONEXISTENT; + case ActivityManager.PROCESS_STATE_UNKNOWN: + return AppProtoEnums.PROCESS_STATE_UNKNOWN; + default: + return AppProtoEnums.PROCESS_STATE_UNKNOWN_TO_PROTO; + } + } + + public static void appendRamKb(StringBuilder sb, long ramKb) { + for (int j = 0, fact = 10; j < 6; j++, fact *= 10) { + if (ramKb < fact) { + sb.append(' '); + } + } + sb.append(ramKb); + } + + // How long after a state change that it is safe to collect PSS without it being dirty. + public static final int PSS_SAFE_TIME_FROM_STATE_CHANGE = 1000; + + // The minimum time interval after a state change it is safe to collect PSS. + public static final int PSS_MIN_TIME_FROM_STATE_CHANGE = 15*1000; + + // The maximum amount of time we want to go between PSS collections. + public static final int PSS_MAX_INTERVAL = 60*60*1000; + + // The minimum amount of time between successive PSS requests for *all* processes. + public static final int PSS_ALL_INTERVAL = 20*60*1000; + + // The amount of time until PSS when a persistent process first appears. + private static final int PSS_FIRST_PERSISTENT_INTERVAL = 30*1000; + + // The amount of time until PSS when a process first becomes top. + private static final int PSS_FIRST_TOP_INTERVAL = 10*1000; + + // The amount of time until PSS when a process first goes into the background. + private static final int PSS_FIRST_BACKGROUND_INTERVAL = 20*1000; + + // The amount of time until PSS when a process first becomes cached. + private static final int PSS_FIRST_CACHED_INTERVAL = 20*1000; + + // The amount of time until PSS when an important process stays in the same state. + private static final int PSS_SAME_PERSISTENT_INTERVAL = 10*60*1000; + + // The amount of time until PSS when the top process stays in the same state. + private static final int PSS_SAME_TOP_INTERVAL = 1*60*1000; + + // The amount of time until PSS when an important process stays in the same state. + private static final int PSS_SAME_IMPORTANT_INTERVAL = 10*60*1000; + + // The amount of time until PSS when a service process stays in the same state. + private static final int PSS_SAME_SERVICE_INTERVAL = 5*60*1000; + + // The amount of time until PSS when a cached process stays in the same state. + private static final int PSS_SAME_CACHED_INTERVAL = 10*60*1000; + + // The amount of time until PSS when a persistent process first appears. + private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000; + + // The amount of time until PSS when a process first becomes top. + private static final int PSS_FIRST_ASLEEP_TOP_INTERVAL = 20*1000; + + // The amount of time until PSS when a process first goes into the background. + private static final int PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL = 30*1000; + + // The amount of time until PSS when a process first becomes cached. + private static final int PSS_FIRST_ASLEEP_CACHED_INTERVAL = 1*60*1000; + + // The minimum time interval after a state change it is safe to collect PSS. + public static final int PSS_TEST_MIN_TIME_FROM_STATE_CHANGE = 10*1000; + + // The amount of time during testing until PSS when a process first becomes top. + private static final int PSS_TEST_FIRST_TOP_INTERVAL = 3*1000; + + // The amount of time during testing until PSS when a process first goes into the background. + private static final int PSS_TEST_FIRST_BACKGROUND_INTERVAL = 5*1000; + + // The amount of time during testing until PSS when an important process stays in same state. + private static final int PSS_TEST_SAME_IMPORTANT_INTERVAL = 10*1000; + + // The amount of time during testing until PSS when a background process stays in same state. + private static final int PSS_TEST_SAME_BACKGROUND_INTERVAL = 15*1000; + + public static final int PROC_MEM_PERSISTENT = 0; + public static final int PROC_MEM_TOP = 1; + public static final int PROC_MEM_IMPORTANT = 2; + public static final int PROC_MEM_SERVICE = 3; + public static final int PROC_MEM_CACHED = 4; + public static final int PROC_MEM_NUM = 5; + + // Map large set of system process states to + private static final int[] sProcStateToProcMem = new int[] { + PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT + PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI + PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_BOUND_TOP + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP + PROC_MEM_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_RECEIVER + PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_HOME + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + }; + + private static final long[] sFirstAwakePssTimes = new long[] { + PSS_FIRST_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_FIRST_TOP_INTERVAL, // PROC_MEM_TOP + PSS_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_IMPORTANT + PSS_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_FIRST_CACHED_INTERVAL, // PROC_MEM_CACHED + }; + + private static final long[] sSameAwakePssTimes = new long[] { + PSS_SAME_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_SAME_TOP_INTERVAL, // PROC_MEM_TOP + PSS_SAME_IMPORTANT_INTERVAL, // PROC_MEM_IMPORTANT + PSS_SAME_SERVICE_INTERVAL, // PROC_MEM_SERVICE + PSS_SAME_CACHED_INTERVAL, // PROC_MEM_CACHED + }; + + private static final long[] sFirstAsleepPssTimes = new long[] { + PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_FIRST_ASLEEP_TOP_INTERVAL, // PROC_MEM_TOP + PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // PROC_MEM_IMPORTANT + PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_FIRST_ASLEEP_CACHED_INTERVAL, // PROC_MEM_CACHED + }; + + private static final long[] sSameAsleepPssTimes = new long[] { + PSS_SAME_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_SAME_TOP_INTERVAL, // PROC_MEM_TOP + PSS_SAME_IMPORTANT_INTERVAL, // PROC_MEM_IMPORTANT + PSS_SAME_SERVICE_INTERVAL, // PROC_MEM_SERVICE + PSS_SAME_CACHED_INTERVAL, // PROC_MEM_CACHED + }; + + private static final long[] sTestFirstPssTimes = new long[] { + PSS_TEST_FIRST_TOP_INTERVAL, // PROC_MEM_PERSISTENT + PSS_TEST_FIRST_TOP_INTERVAL, // PROC_MEM_TOP + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_IMPORTANT + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_CACHED + }; + + private static final long[] sTestSamePssTimes = new long[] { + PSS_TEST_SAME_BACKGROUND_INTERVAL, // PROC_MEM_PERSISTENT + PSS_TEST_SAME_IMPORTANT_INTERVAL, // PROC_MEM_TOP + PSS_TEST_SAME_IMPORTANT_INTERVAL, // PROC_MEM_IMPORTANT + PSS_TEST_SAME_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_TEST_SAME_BACKGROUND_INTERVAL, // PROC_MEM_CACHED + }; + + public static final class ProcStateMemTracker { + final int[] mHighestMem = new int[PROC_MEM_NUM]; + final float[] mScalingFactor = new float[PROC_MEM_NUM]; + int mTotalHighestMem = PROC_MEM_CACHED; + + int mPendingMemState; + int mPendingHighestMemState; + float mPendingScalingFactor; + + public ProcStateMemTracker() { + for (int i = PROC_MEM_PERSISTENT; i < PROC_MEM_NUM; i++) { + mHighestMem[i] = PROC_MEM_NUM; + mScalingFactor[i] = 1.0f; + } + mPendingMemState = -1; + } + + public void dumpLine(PrintWriter pw) { + pw.print("best="); + pw.print(mTotalHighestMem); + pw.print(" ("); + boolean needSep = false; + for (int i = 0; i < PROC_MEM_NUM; i++) { + if (mHighestMem[i] < PROC_MEM_NUM) { + if (needSep) { + pw.print(", "); + needSep = false; + } + pw.print(i); + pw.print("="); + pw.print(mHighestMem[i]); + pw.print(" "); + pw.print(mScalingFactor[i]); + pw.print("x"); + needSep = true; + } + } + pw.print(")"); + if (mPendingMemState >= 0) { + pw.print(" / pending state="); + pw.print(mPendingMemState); + pw.print(" highest="); + pw.print(mPendingHighestMemState); + pw.print(" "); + pw.print(mPendingScalingFactor); + pw.print("x"); + } + pw.println(); + } + } + + public static boolean procStatesDifferForMem(int procState1, int procState2) { + return sProcStateToProcMem[procState1] != sProcStateToProcMem[procState2]; + } + + public static long minTimeFromStateChange(boolean test) { + return test ? PSS_TEST_MIN_TIME_FROM_STATE_CHANGE : PSS_MIN_TIME_FROM_STATE_CHANGE; + } + + public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, + boolean sleeping, long now, long earliest) { + boolean first; + float scalingFactor; + final int memState = sProcStateToProcMem[procState]; + if (tracker != null) { + final int highestMemState = memState < tracker.mTotalHighestMem + ? memState : tracker.mTotalHighestMem; + first = highestMemState < tracker.mHighestMem[memState]; + tracker.mPendingMemState = memState; + tracker.mPendingHighestMemState = highestMemState; + if (first) { + tracker.mPendingScalingFactor = scalingFactor = 1.0f; + } else { + scalingFactor = tracker.mScalingFactor[memState]; + tracker.mPendingScalingFactor = scalingFactor * 1.5f; + } + } else { + first = true; + scalingFactor = 1.0f; + } + final long[] table = test + ? (first + ? sTestFirstPssTimes + : sTestSamePssTimes) + : (first + ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes) + : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes)); + long delay = (long)(table[memState] * scalingFactor); + if (delay > PSS_MAX_INTERVAL) { + delay = PSS_MAX_INTERVAL; + } + return Math.max(now + delay, earliest); + } + + long getMemLevel(int adjustment) { + for (int i = 0; i < mOomAdj.length; i++) { + if (adjustment <= mOomAdj[i]) { + return mOomMinFree[i] * 1024; + } + } + return mOomMinFree[mOomAdj.length - 1] * 1024; + } + + /** + * Return the maximum pss size in kb that we consider a process acceptable to + * restore from its cached state for running in the background when RAM is low. + */ + long getCachedRestoreThresholdKb() { + return mCachedRestoreLevel; + } + + AppStartInfoTracker getAppStartInfoTracker() { + return mAppStartInfoTracker; + } + + /** + * Set the out-of-memory badness adjustment for a process. + * If {@code pid <= 0}, this method will be a no-op. + * + * @param pid The process identifier to set. + * @param uid The uid of the app + * @param amt Adjustment value -- lmkd allows -1000 to +1000 + * + * {@hide} + */ + public static void setOomAdj(int pid, int uid, int amt) { + // This indicates that the process is not started yet and so no need to proceed further. + if (pid <= 0) { + return; + } + if (amt == UNKNOWN_ADJ) + return; + + long start = SystemClock.elapsedRealtime(); + ByteBuffer buf = ByteBuffer.allocate(4 * 4); + buf.putInt(LMK_PROCPRIO); + buf.putInt(pid); + buf.putInt(uid); + buf.putInt(amt); + writeLmkd(buf, null); + long now = SystemClock.elapsedRealtime(); + if ((now-start) > 250) { + Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid + + " = " + amt); + } + } + + /* + * {@hide} + */ + public static final void remove(int pid) { + // This indicates that the process is not started yet and so no need to proceed further. + if (pid <= 0) { + return; + } + ByteBuffer buf = ByteBuffer.allocate(4 * 2); + buf.putInt(LMK_PROCREMOVE); + buf.putInt(pid); + writeLmkd(buf, null); + } + + /* + * {@hide} + */ + public static final Integer getLmkdKillCount(int min_oom_adj, int max_oom_adj) { + ByteBuffer buf = ByteBuffer.allocate(4 * 3); + ByteBuffer repl = ByteBuffer.allocate(4 * 2); + buf.putInt(LMK_GETKILLCNT); + buf.putInt(min_oom_adj); + buf.putInt(max_oom_adj); + // indicate what we are waiting for + repl.putInt(LMK_GETKILLCNT); + repl.rewind(); + if (writeLmkd(buf, repl) && repl.getInt() == LMK_GETKILLCNT) { + return new Integer(repl.getInt()); + } + return null; + } + + public boolean onLmkdConnect(OutputStream ostream) { + try { + // Purge any previously registered pids + ByteBuffer buf = ByteBuffer.allocate(4); + buf.putInt(LMK_PROCPURGE); + ostream.write(buf.array(), 0, buf.position()); + if (mOomLevelsSet) { + // Reset oom_adj levels + buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1)); + buf.putInt(LMK_TARGET); + for (int i = 0; i < mOomAdj.length; i++) { + buf.putInt((mOomMinFree[i] * 1024)/PAGE_SIZE); + buf.putInt(mOomAdj[i]); + } + ostream.write(buf.array(), 0, buf.position()); + } + // Subscribe for kill event notifications + buf = ByteBuffer.allocate(4 * 2); + buf.putInt(LMK_SUBSCRIBE); + buf.putInt(LMK_ASYNC_EVENT_KILL); + ostream.write(buf.array(), 0, buf.position()); + + // Subscribe for stats event notifications + buf = ByteBuffer.allocate(4 * 2); + buf.putInt(LMK_SUBSCRIBE); + buf.putInt(LMK_ASYNC_EVENT_STAT); + ostream.write(buf.array(), 0, buf.position()); + } catch (IOException ex) { + return false; + } + return true; + } + + /** + * {@hide} + */ + public static void startPsiMonitoringAfterBoot() { + ByteBuffer buf = ByteBuffer.allocate(4); + buf.putInt(LMK_START_MONITORING); + writeLmkd(buf, null); + } + + private static boolean writeLmkd(ByteBuffer buf, ByteBuffer repl) { + if (!sLmkdConnection.isConnected()) { + // try to connect immediately and then keep retrying + sKillHandler.sendMessage( + sKillHandler.obtainMessage(KillHandler.LMKD_RECONNECT_MSG)); + + // wait for connection retrying 3 times (up to 3 seconds) + if (!sLmkdConnection.waitForConnection(3 * LMKD_RECONNECT_DELAY_MS)) { + return false; + } + } + + return sLmkdConnection.exchange(buf, repl); + } + + static void killProcessGroup(int uid, int pid) { + /* static; one-time init here */ + if (sKillHandler != null) { + sKillHandler.sendMessage( + sKillHandler.obtainMessage(KillHandler.KILL_PROCESS_GROUP_MSG, uid, pid)); + } else { + Slog.w(TAG, "Asked to kill process group before system bringup!"); + Process.killProcessGroup(uid, pid); + } + } + + @GuardedBy("mService") + ProcessRecord getProcessRecordLocked(String processName, int uid) { + if (uid == SYSTEM_UID) { + // The system gets to run in any process. If there are multiple + // processes with the same uid, just pick the first (this + // should never happen). + SparseArray procs = mProcessNames.getMap().get(processName); + if (procs == null) return null; + final int procCount = procs.size(); + for (int i = 0; i < procCount; i++) { + final int procUid = procs.keyAt(i); + if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) { + // Don't use an app process or different user process for system component. + continue; + } + return procs.valueAt(i); + } + } + return mProcessNames.get(processName, uid); + } + + void getMemoryInfo(ActivityManager.MemoryInfo outInfo) { + final long homeAppMem = getMemLevel(HOME_APP_ADJ); + final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ); + outInfo.advertisedMem = getAdvertisedMem(); + outInfo.availMem = getFreeMemory(); + outInfo.totalMem = getTotalMemory(); + outInfo.threshold = homeAppMem; + outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2)); + outInfo.hiddenAppThreshold = cachedAppMem; + outInfo.secondaryServerThreshold = getMemLevel(SERVICE_ADJ); + outInfo.visibleAppThreshold = getMemLevel(VISIBLE_APP_ADJ); + outInfo.foregroundAppThreshold = getMemLevel(FOREGROUND_APP_ADJ); + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + ProcessRecord findAppProcessLOSP(IBinder app, String reason) { + final int NP = mProcessNames.getMap().size(); + for (int ip = 0; ip < NP; ip++) { + SparseArray apps = mProcessNames.getMap().valueAt(ip); + final int NA = apps.size(); + for (int ia = 0; ia < NA; ia++) { + ProcessRecord p = apps.valueAt(ia); + final IApplicationThread thread = p.getThread(); + if (thread != null && thread.asBinder() == app) { + return p; + } + } + } + + Slog.w(TAG, "Can't find mystery application for " + reason + + " from pid=" + Binder.getCallingPid() + + " uid=" + Binder.getCallingUid() + ": " + app); + return null; + } + + private void checkSlow(long startTime, String where) { + long now = SystemClock.uptimeMillis(); + if ((now - startTime) > 50) { + // If we are taking more than 50ms, log about it. + Slog.w(TAG, "Slow operation: " + (now - startTime) + "ms so far, now at " + where); + } + } + + private int[] computeGidsForProcess(int mountExternal, int uid, int[] permGids, + boolean externalStorageAccess) { + ArrayList gidList = new ArrayList<>(permGids.length + 5); + + final int sharedAppGid = UserHandle.getSharedAppGid(UserHandle.getAppId(uid)); + final int cacheAppGid = UserHandle.getCacheAppGid(UserHandle.getAppId(uid)); + final int userGid = UserHandle.getUserGid(UserHandle.getUserId(uid)); + + // Add shared application and profile GIDs so applications can share some + // resources like shared libraries and access user-wide resources + for (int permGid : permGids) { + gidList.add(permGid); + } + if (sharedAppGid != UserHandle.ERR_GID) { + gidList.add(sharedAppGid); + } + if (cacheAppGid != UserHandle.ERR_GID) { + gidList.add(cacheAppGid); + } + if (userGid != UserHandle.ERR_GID) { + gidList.add(userGid); + } + if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE + || mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) { + // For DownloadProviders and MTP: To grant access to /sdcard/Android/ + // And a special case for the FUSE daemon since it runs an MTP server and should have + // access to Android/ + // Note that we must add in the user id, because sdcardfs synthesizes this permission + // based on the user + gidList.add(UserHandle.getUid(UserHandle.getUserId(uid), Process.SDCARD_RW_GID)); + + // For devices without sdcardfs, these GIDs are needed instead; note that we + // consciously don't add the user_id in the GID, since these apps are anyway + // isolated to only their own user + gidList.add(Process.EXT_DATA_RW_GID); + gidList.add(Process.EXT_OBB_RW_GID); + } + if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) { + // For devices without sdcardfs, this GID is needed to allow installers access to OBBs + gidList.add(Process.EXT_OBB_RW_GID); + } + if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) { + // For the FUSE daemon: To grant access to the lower filesystem. + // EmulatedVolumes: /data/media and /mnt/expand//data/media + // PublicVolumes: /mnt/media_rw/ + gidList.add(Process.MEDIA_RW_GID); + } + if (externalStorageAccess) { + // Apps with MANAGE_EXTERNAL_STORAGE PERMISSION need the external_storage gid to access + // USB OTG (unreliable) volumes on /mnt/media_rw/ + gidList.add(Process.EXTERNAL_STORAGE_GID); + } + + int[] gidArray = new int[gidList.size()]; + for (int i = 0; i < gidArray.length; i++) { + gidArray[i] = gidList.get(i); + } + return gidArray; + } + + /** + * @return {@code true} if process start is successful, false otherwise. + */ + @GuardedBy("mService") + boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, + int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks, + String abiOverride) { + if (app.isPendingStart()) { + return true; + } + final long startUptime = SystemClock.uptimeMillis(); + final long startElapsedTime = SystemClock.elapsedRealtime(); + if (app.getPid() > 0 && app.getPid() != ActivityManagerService.MY_PID) { + checkSlow(startUptime, "startProcess: removing from pids map"); + mService.removePidLocked(app.getPid(), app); + app.setBindMountPending(false); + mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); + checkSlow(startUptime, "startProcess: done removing from pids map"); + app.setPid(0); + app.setStartSeq(0); + } + // Clear any residual death recipient link as the ProcessRecord could be reused. + app.unlinkDeathRecipient(); + app.setDyingPid(0); + + if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v( + TAG_PROCESSES, + "startProcessLocked removing on hold: " + app); + mService.mProcessesOnHold.remove(app); + + checkSlow(startUptime, "startProcess: starting to update cpu stats"); + mService.updateCpuStats(); + checkSlow(startUptime, "startProcess: done updating cpu stats"); + + try { + final int userId = UserHandle.getUserId(app.uid); + try { + AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + + int uid = app.uid; + int[] gids = null; + int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; + boolean externalStorageAccess = false; + if (!app.isolated) { + int[] permGids = null; + try { + checkSlow(startUptime, "startProcess: getting gids from package manager"); + final IPackageManager pm = AppGlobals.getPackageManager(); + permGids = pm.getPackageGids(app.info.packageName, + MATCH_DIRECT_BOOT_AUTO, app.userId); + StorageManagerInternal storageManagerInternal = LocalServices.getService( + StorageManagerInternal.class); + mountExternal = storageManagerInternal.getExternalStorageMountMode(uid, + app.info.packageName); + externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid, + app.info.packageName); + if (mService.isAppFreezerExemptInstPkg() + && pm.checkPermission(Manifest.permission.INSTALL_PACKAGES, + app.info.packageName, userId) + == PackageManager.PERMISSION_GRANTED) { + Slog.i(TAG, app.info.packageName + " is exempt from freezer"); + app.mOptRecord.setFreezeExempt(true); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + + // Remove any gids needed if the process has been denied permissions. + // NOTE: eventually we should probably have the package manager pre-compute + // this for us? + if (app.processInfo != null && app.processInfo.deniedPermissions != null) { + for (int i = app.processInfo.deniedPermissions.size() - 1; i >= 0; i--) { + int[] denyGids = mService.mPackageManagerInt.getPermissionGids( + app.processInfo.deniedPermissions.valueAt(i), app.userId); + if (denyGids != null) { + for (int gid : denyGids) { + permGids = ArrayUtils.removeInt(permGids, gid); + } + } + } + } + + gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess); + } + app.setMountMode(mountExternal); + checkSlow(startUptime, "startProcess: building args"); + if (app.getWindowProcessController().isFactoryTestProcess()) { + uid = 0; + } + int runtimeFlags = 0; + + boolean debuggableFlag = (app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + boolean isProfileableByShell = app.info.isProfileableByShell(); + boolean isProfileable = app.info.isProfileable(); + + if (app.isSdkSandbox) { + ApplicationInfo clientInfo = app.getClientInfoForSdkSandbox(); + if (clientInfo != null) { + debuggableFlag |= (clientInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + isProfileableByShell |= clientInfo.isProfileableByShell(); + isProfileable |= clientInfo.isProfileable(); + } + } + + if (debuggableFlag) { + runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + runtimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; + runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE; + // Also turn on CheckJNI for debuggable apps. It's quite + // awkward to turn on otherwise. + runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; + + // Check if the developer does not want ART verification + if (android.provider.Settings.Global.getInt(mService.mContext.getContentResolver(), + android.provider.Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE, 1) == 0) { + runtimeFlags |= Zygote.DISABLE_VERIFIER; + Slog.w(TAG_PROCESSES, app + ": ART verification disabled"); + } + } + // Run the app in safe mode if its manifest requests so or the + // system is booted in safe mode. + if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 || mService.mSafeMode) { + runtimeFlags |= Zygote.DEBUG_ENABLE_SAFEMODE; + } + if (isProfileableByShell) { + runtimeFlags |= Zygote.PROFILE_FROM_SHELL; + } + if (isProfileable) { + runtimeFlags |= Zygote.PROFILEABLE; + } + if ("1".equals(SystemProperties.get("debug.checkjni"))) { + runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; + } + String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info"); + if ("1".equals(genDebugInfoProperty) || "true".equals(genDebugInfoProperty)) { + runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; + } + String genMiniDebugInfoProperty = SystemProperties.get("dalvik.vm.minidebuginfo"); + if ("1".equals(genMiniDebugInfoProperty) || "true".equals(genMiniDebugInfoProperty)) { + runtimeFlags |= Zygote.DEBUG_GENERATE_MINI_DEBUG_INFO; + } + if ("1".equals(SystemProperties.get("debug.jni.logging"))) { + runtimeFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; + } + if ("1".equals(SystemProperties.get("debug.assert"))) { + runtimeFlags |= Zygote.DEBUG_ENABLE_ASSERT; + } + if ("1".equals(SystemProperties.get("debug.ignoreappsignalhandler"))) { + runtimeFlags |= Zygote.DEBUG_IGNORE_APP_SIGNAL_HANDLER; + } + if (mService.mNativeDebuggingApp != null + && mService.mNativeDebuggingApp.equals(app.processName)) { + // Enable all debug flags required by the native debugger. + runtimeFlags |= Zygote.DEBUG_ALWAYS_JIT; // Don't interpret anything + runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; // Generate debug info + runtimeFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE; // Disbale optimizations + mService.mNativeDebuggingApp = null; + } + + if (app.info.isEmbeddedDexUsed() + || (app.processInfo != null && app.processInfo.useEmbeddedDex)) { + runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES; + } + + if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) { + app.info.maybeUpdateHiddenApiEnforcementPolicy( + mService.mHiddenApiBlacklist.getPolicy()); + @ApplicationInfo.HiddenApiEnforcementPolicy int policy = + app.info.getHiddenApiEnforcementPolicy(); + int policyBits = (policy << Zygote.API_ENFORCEMENT_POLICY_SHIFT); + if ((policyBits & Zygote.API_ENFORCEMENT_POLICY_MASK) != policyBits) { + throw new IllegalStateException("Invalid API policy: " + policy); + } + runtimeFlags |= policyBits; + + if (disableTestApiChecks) { + runtimeFlags |= Zygote.DISABLE_TEST_API_ENFORCEMENT_POLICY; + } + } + + String useAppImageCache = SystemProperties.get( + PROPERTY_USE_APP_IMAGE_STARTUP_CACHE, ""); + // Property defaults to true currently. + if (!TextUtils.isEmpty(useAppImageCache) && !useAppImageCache.equals("false")) { + runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; + } + + String invokeWith = null; + if (debuggableFlag) { + // Debuggable apps may include a wrapper script with their library directory. + String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh"; + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + if (new File(wrapperFileName).exists()) { + invokeWith = "/system/bin/logwrapper " + wrapperFileName; + } + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi; + if (requiredAbi == null) { + requiredAbi = Build.SUPPORTED_ABIS[0]; + } + + String instructionSet = null; + if (app.info.primaryCpuAbi != null) { + // If ABI override is specified, use the isa derived from the value of ABI override. + // Otherwise, use the isa derived from primary ABI + instructionSet = VMRuntime.getInstructionSet(requiredAbi); + } + + app.setGids(gids); + app.setRequiredAbi(requiredAbi); + app.setInstructionSet(instructionSet); + + // If this was an external service, the package name and uid in the passed in + // ApplicationInfo have been changed to match those of the calling package; + // that will incorrectly apply compat feature overrides for the calling package instead + // of the defining one. + ApplicationInfo definingAppInfo; + if (hostingRecord.getDefiningPackageName() != null) { + definingAppInfo = new ApplicationInfo(app.info); + definingAppInfo.packageName = hostingRecord.getDefiningPackageName(); + definingAppInfo.uid = uid; + } else { + definingAppInfo = app.info; + } + + runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags( + definingAppInfo, app.processInfo, instructionSet, mPlatformCompat); + + // the per-user SELinux context must be set + if (TextUtils.isEmpty(app.info.seInfoUser)) { + Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined", + new IllegalStateException("SELinux tag not defined for " + + app.info.packageName + " (uid " + app.uid + ")")); + } + + String seInfo = updateSeInfo(app); + + // Start the process. It will either succeed and return a result containing + // the PID of the new process, or else throw a RuntimeException. + final String entryPoint = "android.app.ActivityThread"; + + return startProcessLocked(hostingRecord, entryPoint, app, uid, gids, + runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi, + instructionSet, invokeWith, startUptime, startElapsedTime); + } catch (RuntimeException e) { + Slog.e(ActivityManagerService.TAG, "Failure starting process " + app.processName, e); + + // Something went very wrong while trying to start this process; one + // common case is when the package is frozen due to an active + // upgrade. To recover, clean up any active bookkeeping related to + // starting this process. (We already invoked this method once when + // the package was initially frozen through KILL_APPLICATION_MSG, so + // it doesn't hurt to use it again.) + mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid), + false, false, true, false, false, false, app.userId, "start failure"); + return false; + } + } + + @VisibleForTesting + @GuardedBy("mService") + String updateSeInfo(ProcessRecord app) { + String extraInfo = ""; + // By the time the first the SDK sandbox process is started, device config service + // should be available. If both Next and Audit are enabled, Next takes precedence. + if (app.isSdkSandbox) { + if (getProcessListSettingsListener().applySdkSandboxRestrictionsNext()) { + extraInfo = APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS; + } else if (selinuxSdkSandboxAudit() + && getProcessListSettingsListener().applySdkSandboxRestrictionsAudit()) { + extraInfo = APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS; + } + } + + return app.info.seInfo + + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + extraInfo; + } + + + @GuardedBy("mService") + boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, + int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, + String seInfo, String requiredAbi, String instructionSet, String invokeWith, + long startUptime, long startElapsedTime) { + app.setPendingStart(true); + app.setRemoved(false); + synchronized (mProcLock) { + app.setKilledByAm(false); + app.setKilled(false); + } + if (app.getStartSeq() != 0) { + Slog.wtf(TAG, "startProcessLocked processName:" + app.processName + + " with non-zero startSeq:" + app.getStartSeq()); + } + if (app.getPid() != 0) { + Slog.wtf(TAG, "startProcessLocked processName:" + app.processName + + " with non-zero pid:" + app.getPid()); + } + app.setDisabledCompatChanges(null); + if (mPlatformCompat != null) { + app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info)); + } + final long startSeq = ++mProcStartSeqCounter; + app.setStartSeq(startSeq); + app.setStartParams(uid, hostingRecord, seInfo, startUptime, startElapsedTime); + app.setUsingWrapper(invokeWith != null + || Zygote.getWrapProperty(app.processName) != null); + mPendingStarts.put(startSeq, app); + + if (mService.mConstants.FLAG_PROCESS_START_ASYNC) { + if (DEBUG_PROCESSES) Slog.i(TAG_PROCESSES, + "Posting procStart msg for " + app.toShortString()); + mService.mProcStartHandler.post(() -> handleProcessStart( + app, entryPoint, gids, runtimeFlags, zygotePolicyFlags, mountExternal, + requiredAbi, instructionSet, invokeWith, startSeq)); + return true; + } else { + try { + final Process.ProcessStartResult startResult = startProcess(hostingRecord, + entryPoint, app, + uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, + requiredAbi, instructionSet, invokeWith, startUptime); + handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper, + startSeq, false); + } catch (RuntimeException e) { + Slog.e(ActivityManagerService.TAG, "Failure starting process " + + app.processName, e); + app.setPendingStart(false); + mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid), + false, false, true, false, false, false, app.userId, "start failure"); + } + return app.getPid() > 0; + } + } + + /** + * Main handler routine to start the given process from the ProcStartHandler. + * + *

    Note: this function doesn't hold the global AM lock intentionally.

    + */ + private void handleProcessStart(final ProcessRecord app, final String entryPoint, + final int[] gids, final int runtimeFlags, int zygotePolicyFlags, + final int mountExternal, final String requiredAbi, final String instructionSet, + final String invokeWith, final long startSeq) { + final Runnable startRunnable = () -> { + try { + final Process.ProcessStartResult startResult = startProcess(app.getHostingRecord(), + entryPoint, app, app.getStartUid(), gids, runtimeFlags, zygotePolicyFlags, + mountExternal, app.getSeInfo(), requiredAbi, instructionSet, invokeWith, + app.getStartTime()); + + synchronized (mService) { + handleProcessStartedLocked(app, startResult, startSeq); + } + } catch (RuntimeException e) { + synchronized (mService) { + Slog.e(ActivityManagerService.TAG, "Failure starting process " + + app.processName, e); + mPendingStarts.remove(startSeq); + app.setPendingStart(false); + mService.forceStopPackageLocked(app.info.packageName, + UserHandle.getAppId(app.uid), + false, false, true, false, false, false, app.userId, "start failure"); + app.doEarlyCleanupIfNecessaryLocked(); + } + } + }; + // Use local reference since we are not using locks here + final ProcessRecord predecessor = app.mPredecessor; + if (predecessor != null && predecessor.getDyingPid() > 0) { + handleProcessStartWithPredecessor(predecessor, startRunnable); + } else { + // Kick off the process start for real. + startRunnable.run(); + } + } + + /** + * Handle the case where the given process is killed but still not gone, but we'd need to start + * the new instance of it. + */ + private void handleProcessStartWithPredecessor(final ProcessRecord predecessor, + final Runnable successorStartRunnable) { + // If there is a preceding instance of the process, wait for its death with a timeout. + if (predecessor.mSuccessorStartRunnable != null) { + // It's been watched already, this shouldn't happen. + Slog.wtf(TAG, "We've been watching for the death of " + predecessor); + return; + } + predecessor.mSuccessorStartRunnable = successorStartRunnable; + mService.mProcStartHandler.sendMessageDelayed(mService.mProcStartHandler.obtainMessage( + ProcStartHandler.MSG_PROCESS_KILL_TIMEOUT, predecessor), + mService.mConstants.mProcessKillTimeoutMs); + } + + static final class ProcStartHandler extends Handler { + static final int MSG_PROCESS_DIED = 1; + static final int MSG_PROCESS_KILL_TIMEOUT = 2; + + private final ActivityManagerService mService; + + ProcStartHandler(ActivityManagerService service, Looper looper) { + super(looper); + mService = service; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PROCESS_DIED: + mService.mProcessList.handlePredecessorProcDied((ProcessRecord) msg.obj); + break; + case MSG_PROCESS_KILL_TIMEOUT: + synchronized (mService) { + mService.handleProcessStartOrKillTimeoutLocked((ProcessRecord) msg.obj, + /* isKillTimeout */ true); + } + break; + } + } + } + + /** + * Called when the dying process we're waiting for is really gone. + */ + private void handlePredecessorProcDied(ProcessRecord app) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, app.toString() + " is really gone now"); + } + + // Now kick off the subsequent process start if there is any. + final Runnable start = app.mSuccessorStartRunnable; + if (start != null) { + app.mSuccessorStartRunnable = null; + start.run(); + } + } + + @GuardedBy("mService") + public void killAppZygoteIfNeededLocked(AppZygote appZygote, boolean force) { + final ApplicationInfo appInfo = appZygote.getAppInfo(); + ArrayList zygoteProcesses = mAppZygoteProcesses.get(appZygote); + if (zygoteProcesses != null && (force || zygoteProcesses.size() == 0)) { + // Only remove if no longer in use now, or forced kill + mAppZygotes.remove(appInfo.processName, appInfo.uid); + mAppZygoteProcesses.remove(appZygote); + mAppIsolatedUidRangeAllocator.freeUidRangeLocked(appInfo); + appZygote.stopZygote(); + } + } + + @GuardedBy("mService") + private void removeProcessFromAppZygoteLocked(final ProcessRecord app) { + // Free the isolated uid for this process + final IsolatedUidRange appUidRange = + mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info.processName, + app.getHostingRecord().getDefiningUid()); + if (appUidRange != null) { + appUidRange.freeIsolatedUidLocked(app.uid); + } + + final AppZygote appZygote = mAppZygotes.get(app.info.processName, + app.getHostingRecord().getDefiningUid()); + if (appZygote != null) { + ArrayList zygoteProcesses = mAppZygoteProcesses.get(appZygote); + zygoteProcesses.remove(app); + if (zygoteProcesses.size() == 0) { + mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG); + if (app.isRemoved()) { + // If we stopped this process because the package hosting it was removed, + // there's no point in delaying the app zygote kill. + killAppZygoteIfNeededLocked(appZygote, false /* force */); + } else { + Message msg = mService.mHandler.obtainMessage(KILL_APP_ZYGOTE_MSG); + msg.obj = appZygote; + mService.mHandler.sendMessageDelayed(msg, KILL_APP_ZYGOTE_DELAY_MS); + } + } + } + } + + private AppZygote createAppZygoteForProcessIfNeeded(final ProcessRecord app) { + synchronized (mService) { + // The UID for the app zygote should be the UID of the application hosting + // the service. + final int uid = app.getHostingRecord().getDefiningUid(); + AppZygote appZygote = mAppZygotes.get(app.info.processName, uid); + final ArrayList zygoteProcessList; + if (appZygote == null) { + if (DEBUG_PROCESSES) { + Slog.d(TAG_PROCESSES, "Creating new app zygote."); + } + final IsolatedUidRange uidRange = + mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked( + app.info.processName, app.getHostingRecord().getDefiningUid()); + final int userId = UserHandle.getUserId(uid); + // Create the app-zygote and provide it with the UID-range it's allowed + // to setresuid/setresgid to. + final int firstUid = UserHandle.getUid(userId, uidRange.mFirstUid); + final int lastUid = UserHandle.getUid(userId, uidRange.mLastUid); + ApplicationInfo appInfo = new ApplicationInfo(app.info); + // If this was an external service, the package name and uid in the passed in + // ApplicationInfo have been changed to match those of the calling package; + // that is not what we want for the AppZygote though, which needs to have the + // packageName and uid of the defining application. This is because the + // preloading only makes sense in the context of the defining application, + // not the calling one. + appInfo.packageName = app.getHostingRecord().getDefiningPackageName(); + appInfo.uid = uid; + appZygote = new AppZygote(appInfo, app.processInfo, uid, firstUid, lastUid); + mAppZygotes.put(app.info.processName, uid, appZygote); + zygoteProcessList = new ArrayList(); + mAppZygoteProcesses.put(appZygote, zygoteProcessList); + } else { + if (DEBUG_PROCESSES) { + Slog.d(TAG_PROCESSES, "Reusing existing app zygote."); + } + mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG, appZygote); + zygoteProcessList = mAppZygoteProcesses.get(appZygote); + } + // Note that we already add the app to mAppZygoteProcesses here; + // this is so that another thread can't come in and kill the zygote + // before we've even tried to start the process. If the process launch + // goes wrong, we'll clean this up in removeProcessNameLocked() + zygoteProcessList.add(app); + + return appZygote; + } + } + + private Map> getPackageAppDataInfoMap(PackageManagerInternal pmInt, + String[] packages, int uid) { + Map> result = new ArrayMap<>(packages.length); + int userId = UserHandle.getUserId(uid); + for (String packageName : packages) { + final PackageStateInternal packageState = pmInt.getPackageStateInternal(packageName); + if (packageState == null) { + Slog.w(TAG, "Unknown package:" + packageName); + continue; + } + String volumeUuid = packageState.getVolumeUuid(); + long inode = packageState.getUserStateOrDefault(userId).getCeDataInode(); + if (inode == 0) { + Slog.w(TAG, packageName + " inode == 0 (b/152760674)"); + return null; + } + result.put(packageName, Pair.create(volumeUuid, inode)); + } + + return result; + } + + private boolean needsStorageDataIsolation(StorageManagerInternal storageManagerInternal, + ProcessRecord app) { + final int mountMode = app.getMountMode(); + return mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid) + && !storageManagerInternal.isExternalStorageService(app.uid) + // Special mounting mode doesn't need to have data isolation as they won't + // access /mnt/user anyway. + && mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE + && mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH + && mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER + && mountMode != Zygote.MOUNT_EXTERNAL_NONE; + } + + private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint, + ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, + int mountExternal, String seInfo, String requiredAbi, String instructionSet, + String invokeWith, long startTime) { + try { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " + + app.processName); + checkSlow(startTime, "startProcess: asking zygote to start proc"); + final boolean isTopApp = hostingRecord.isTopApp(); + if (isTopApp) { + // Use has-foreground-activities as a temporary hint so the current scheduling + // group won't be lost when the process is attaching. The actual state will be + // refreshed when computing oom-adj. + app.mState.setHasForegroundActivities(true); + } + + Map> pkgDataInfoMap; + Map> allowlistedAppDataInfoMap; + boolean bindMountAppStorageDirs = false; + boolean bindMountAppsData = mAppDataIsolationEnabled + && (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid) + || app.isSdkSandbox) + && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info); + + // Get all packages belongs to the same shared uid. sharedPackages is empty array + // if it doesn't have shared uid. + final PackageManagerInternal pmInt = mService.getPackageManagerInternal(); + + // In the case of sdk sandbox, the pkgDataInfoMap of only the client app associated with + // the sandbox is required to handle app visibility restrictions for the sandbox. + final String[] targetPackagesList; + if (app.isSdkSandbox) { + targetPackagesList = new String[]{app.sdkSandboxClientAppPackage}; + } else { + final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage( + app.info.packageName, app.userId); + targetPackagesList = sharedPackages.length == 0 + ? new String[]{app.info.packageName} : sharedPackages; + } + + final boolean hasAppStorage = hasAppStorage(pmInt, app.info.packageName); + + pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid); + if (pkgDataInfoMap == null) { + // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a + // tmp free pass. + bindMountAppsData = false; + } + + // Remove all packages in pkgDataInfoMap from mAppDataIsolationAllowlistedApps, so + // it won't be mounted twice. + final Set allowlistedApps = new ArraySet<>(mAppDataIsolationAllowlistedApps); + for (String pkg : targetPackagesList) { + allowlistedApps.remove(pkg); + } + + allowlistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt, + allowlistedApps.toArray(new String[0]), uid); + if (allowlistedAppDataInfoMap == null) { + // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a + // tmp free pass. + bindMountAppsData = false; + } + + if (!hasAppStorage && !app.isSdkSandbox) { + bindMountAppsData = false; + pkgDataInfoMap = null; + allowlistedAppDataInfoMap = null; + } + + int userId = UserHandle.getUserId(uid); + StorageManagerInternal storageManagerInternal = LocalServices.getService( + StorageManagerInternal.class); + if (needsStorageDataIsolation(storageManagerInternal, app)) { + // We will run prepareStorageDirs() after we trigger zygote fork, so it won't + // slow down app starting speed as those dirs might not be cached. + if (pkgDataInfoMap != null && storageManagerInternal.isFuseMounted(userId)) { + bindMountAppStorageDirs = true; + } else { + // Fuse is not mounted or inode == 0, + // so we won't mount it in zygote, but resume the mount after unlocking device. + app.setBindMountPending(true); + bindMountAppStorageDirs = false; + } + } + + // If it's an isolated process, it should not even mount its own app data directories, + // since it has no access to them anyway. + if (app.isolated) { + pkgDataInfoMap = null; + allowlistedAppDataInfoMap = null; + } + + boolean bindOverrideSysprops = false; + String[] syspropOverridePkgNames = DeviceConfig.getString( + DeviceConfig.NAMESPACE_APP_COMPAT, + "appcompat_sysprop_override_pkgs", "").split(","); + String[] pkgs = app.getPackageList(); + for (int i = 0; i < pkgs.length; i++) { + if (ArrayUtils.contains(syspropOverridePkgNames, pkgs[i])) { + bindOverrideSysprops = true; + break; + } + } + + AppStateTracker ast = mService.mServices.mAppStateTracker; + if (ast != null) { + final boolean inBgRestricted = ast.isAppBackgroundRestricted( + app.info.uid, app.info.packageName); + if (inBgRestricted) { + synchronized (mService) { + mAppsInBackgroundRestricted.add(app); + } + } + app.mState.setBackgroundRestricted(inBgRestricted); + } + + final Process.ProcessStartResult startResult; + boolean regularZygote = false; + app.mProcessGroupCreated = false; + app.mSkipProcessGroupCreation = false; + if (hostingRecord.usesWebviewZygote()) { + startResult = startWebView(entryPoint, + app.processName, uid, uid, gids, runtimeFlags, mountExternal, + app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, + app.info.dataDir, null, app.info.packageName, + app.getDisabledCompatChanges(), + new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); + } else if (hostingRecord.usesAppZygote()) { + final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app); + + // We can't isolate app data and storage data as parent zygote already did that. + startResult = appZygote.getProcess().start(entryPoint, + app.processName, uid, uid, gids, runtimeFlags, mountExternal, + app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, + app.info.dataDir, null, app.info.packageName, + /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp, + app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap, + false, false, false, + new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); + } else { + regularZygote = true; + startResult = Process.start(entryPoint, + app.processName, uid, uid, gids, runtimeFlags, mountExternal, + app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, + app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags, + isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap, + allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs, + bindOverrideSysprops, + new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); + // By now the process group should have been created by zygote. + app.mProcessGroupCreated = true; + } + + if (!regularZygote) { + // webview and app zygote don't have the permission to create the nodes + synchronized (app) { + if (!app.mSkipProcessGroupCreation) { + // If we're not told to skip the process group creation, go create it. + final int res = Process.createProcessGroup(uid, startResult.pid); + if (res < 0) { + if (res == -OsConstants.ESRCH) { + Slog.e(ActivityManagerService.TAG, + "Unable to create process group for " + + app.processName + " (" + startResult.pid + ")"); + } else { + throw new AssertionError("Unable to create process group for " + + app.processName + " (" + startResult.pid + ")"); + } + } else { + app.mProcessGroupCreated = true; + } + } + } + } + + // This runs after Process.start() as this method may block app process starting time + // if dir is not cached. Running this method after Process.start() can make it + // cache the dir asynchronously, so zygote can use it without waiting for it. + if (bindMountAppStorageDirs) { + storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(), + app.processName); + } + checkSlow(startTime, "startProcess: returned from zygote!"); + return startResult; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + + private boolean hasAppStorage(PackageManagerInternal pmInt, String packageName) { + final AndroidPackage pkg = pmInt.getPackage(packageName); + if (pkg == null) { + Slog.w(TAG, "Unknown package " + packageName); + return false; + } + final PackageManager.Property noAppStorageProp = + pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); + return noAppStorageProp == null || !noAppStorageProp.getBoolean(); + } + + @GuardedBy("mService") + void startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, int zygotePolicyFlags) { + startProcessLocked(app, hostingRecord, zygotePolicyFlags, null /* abiOverride */); + } + + @GuardedBy("mService") + boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, + int zygotePolicyFlags, String abiOverride) { + return startProcessLocked(app, hostingRecord, zygotePolicyFlags, + false /* disableHiddenApiChecks */, false /* disableTestApiChecks */, + abiOverride); + } + + @GuardedBy("mService") + ProcessRecord startProcessLocked(String processName, ApplicationInfo info, + boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, + int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid, + boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage, + String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) { + long startTime = SystemClock.uptimeMillis(); + final long startTimeNs = SystemClock.elapsedRealtimeNanos(); + ProcessRecord app; + if (!isolated) { + app = getProcessRecordLocked(processName, info.uid); + checkSlow(startTime, "startProcess: after getProcessRecord"); + + if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) { + // If we are in the background, then check to see if this process + // is bad. If so, we will just silently fail. + if (mService.mAppErrors.isBadProcess(processName, info.uid)) { + if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid + + "/" + processName); + return null; + } + } else { + // When the user is explicitly starting a process, then clear its + // crash count so that we won't make it bad until they see at + // least one crash dialog again, and make the process good again + // if it had been bad. + if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid + + "/" + processName); + mService.mAppErrors.resetProcessCrashTime(processName, info.uid); + if (mService.mAppErrors.isBadProcess(processName, info.uid)) { + EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, + UserHandle.getUserId(info.uid), info.uid, + info.processName); + mService.mAppErrors.clearBadProcess(processName, info.uid); + if (app != null) { + app.mErrorState.setBad(false); + } + } + } + } else { + // If this is an isolated process, it can't re-use an existing process. + app = null; + } + + // We don't have to do anything more if: + // (1) There is an existing application record; and + // (2) The caller doesn't think it is dead, OR there is no thread + // object attached to it so we know it couldn't have crashed; and + // (3) There is a pid assigned to it, so it is either starting or + // already running. + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "startProcess: name=" + processName + + " app=" + app + " knownToBeDead=" + knownToBeDead + + " thread=" + (app != null ? app.getThread() : null) + + " pid=" + (app != null ? app.getPid() : -1)); + ProcessRecord predecessor = null; + if (app != null && app.getPid() > 0) { + if ((!knownToBeDead && !app.isKilled()) || app.getThread() == null) { + // We already have the app running, or are waiting for it to + // come up (we have a pid but not yet its thread), so keep it. + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App already running: " + app); + // If this is a new package in the process, add the package to the list + app.addPackage(info.packageName, info.longVersionCode, mService.mProcessStats); + checkSlow(startTime, "startProcess: done, added package to proc"); + return app; + } + + // An application record is attached to a previous process, + // clean it up now. + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App died: " + app); + checkSlow(startTime, "startProcess: bad proc running, killing"); + ProcessList.killProcessGroup(app.uid, app.getPid()); + checkSlow(startTime, "startProcess: done killing old proc"); + + if (!app.isKilled()) { + // Throw a wtf if it's not killed + Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + } else { + Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + } + // We are not going to re-use the ProcessRecord, as we haven't dealt with the cleanup + // routine of it yet, but we'd set it as the predecessor of the new process. + predecessor = app; + app = null; + } else if (!isolated) { + // This app may have been removed from process name maps, probably because we killed it + // and did the cleanup before the actual death notification. Check the dying processes. + predecessor = mDyingProcesses.get(processName, info.uid); + if (predecessor != null) { + // The process record could have existed but its pid is set to 0. In this case, + // the 'app' and 'predecessor' could end up pointing to the same instance; + // so make sure we check this case here. + if (app != null && app != predecessor) { + app.mPredecessor = predecessor; + predecessor.mSuccessor = app; + } else { + app = null; + } + Slog.w(TAG_PROCESSES, predecessor.toString() + " is attached to a previous process " + + predecessor.getDyingPid()); + } + } + + if (app == null) { + checkSlow(startTime, "startProcess: creating new process record"); + app = newProcessRecordLocked(info, processName, isolated, isolatedUid, isSdkSandbox, + sdkSandboxUid, sdkSandboxClientAppPackage, hostingRecord); + if (app == null) { + Slog.w(TAG, "Failed making new process record for " + + processName + "/" + info.uid + " isolated=" + isolated); + return null; + } + app.mErrorState.setCrashHandler(crashHandler); + app.setIsolatedEntryPoint(entryPoint); + app.setIsolatedEntryPointArgs(entryPointArgs); + if (predecessor != null) { + app.mPredecessor = predecessor; + predecessor.mSuccessor = app; + } + checkSlow(startTime, "startProcess: done creating new process record"); + } else { + // If this is a new package in the process, add the package to the list + app.addPackage(info.packageName, info.longVersionCode, mService.mProcessStats); + checkSlow(startTime, "startProcess: added package to existing proc"); + } + + // If the system is not ready yet, then hold off on starting this + // process until it is. + if (!mService.mProcessesReady + && !mService.isAllowedWhileBooting(info) + && !allowWhileBooting) { + if (!mService.mProcessesOnHold.contains(app)) { + mService.mProcessesOnHold.add(app); + } + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, + "System not ready, putting on hold: " + app); + checkSlow(startTime, "startProcess: returning with proc on hold"); + return app; + } + + checkSlow(startTime, "startProcess: stepping in to startProcess"); + final boolean success = + startProcessLocked(app, hostingRecord, zygotePolicyFlags, abiOverride); + checkSlow(startTime, "startProcess: done starting proc!"); + return success ? app : null; + } + + @GuardedBy("mService") + String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) { + StringBuilder sb = null; + if (app.isKilledByAm()) { + if (sb == null) sb = new StringBuilder(); + sb.append("killedByAm=true;"); + } + if (mProcessNames.get(app.processName, app.uid) != app) { + if (sb == null) sb = new StringBuilder(); + sb.append("No entry in mProcessNames;"); + } + if (!app.isPendingStart()) { + if (sb == null) sb = new StringBuilder(); + sb.append("pendingStart=false;"); + } + if (app.getStartSeq() > expectedStartSeq) { + if (sb == null) sb = new StringBuilder(); + sb.append("seq=" + app.getStartSeq() + ",expected=" + expectedStartSeq + ";"); + } + try { + AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, app.userId); + } catch (RemoteException e) { + // unexpected; ignore + } catch (SecurityException e) { + if (mService.mConstants.FLAG_PROCESS_START_ASYNC) { + if (sb == null) sb = new StringBuilder(); + sb.append("Package is frozen;"); + } else { + // we're not being started async and so should throw to the caller. + throw e; + } + } + return sb == null ? null : sb.toString(); + } + + @GuardedBy("mService") + private boolean handleProcessStartedLocked(ProcessRecord pending, + Process.ProcessStartResult startResult, long expectedStartSeq) { + // Indicates that this process start has been taken care of. + if (mPendingStarts.get(expectedStartSeq) == null) { + if (pending.getPid() == startResult.pid) { + pending.setUsingWrapper(startResult.usingWrapper); + // TODO: Update already existing clients of usingWrapper + } + return false; + } + return handleProcessStartedLocked(pending, startResult.pid, startResult.usingWrapper, + expectedStartSeq, false); + } + + @GuardedBy("mService") + boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper, + long expectedStartSeq, boolean procAttached) { + mPendingStarts.remove(expectedStartSeq); + final String reason = isProcStartValidLocked(app, expectedStartSeq); + if (reason != null) { + Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" + + pid + + ", " + reason); + app.setPendingStart(false); + killProcessQuiet(pid); + final int appPid = app.getPid(); + if (appPid != 0) { + Process.killProcessGroup(app.uid, appPid); + } + noteAppKill(app, ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_INVALID_START, reason); + app.doEarlyCleanupIfNecessaryLocked(); + return false; + } + mService.mBatteryStatsService.noteProcessStart(app.processName, app.info.uid); + checkSlow(app.getStartTime(), "startProcess: done updating battery stats"); + + EventLog.writeEvent(EventLogTags.AM_PROC_START, + UserHandle.getUserId(app.getStartUid()), pid, app.getStartUid(), + app.processName, app.getHostingRecord().getType(), + app.getHostingRecord().getName() != null ? app.getHostingRecord().getName() : ""); + + try { + AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.info.packageName, + app.processName, app.uid, app.getSeInfo(), app.info.sourceDir, pid); + } catch (RemoteException ex) { + // Ignore + } + + Watchdog.getInstance().processStarted(app.processName, pid); + + checkSlow(app.getStartTime(), "startProcess: building log message"); + StringBuilder buf = mStringBuilder; + buf.setLength(0); + buf.append("Start proc "); + buf.append(pid); + buf.append(':'); + buf.append(app.processName); + buf.append('/'); + UserHandle.formatUid(buf, app.getStartUid()); + if (app.getIsolatedEntryPoint() != null) { + buf.append(" ["); + buf.append(app.getIsolatedEntryPoint()); + buf.append("]"); + } + buf.append(" for "); + buf.append(app.getHostingRecord().getType()); + if (app.getHostingRecord().getName() != null) { + buf.append(" "); + buf.append(app.getHostingRecord().getName()); + } + mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.getStartUid()); + synchronized (mProcLock) { + app.setPid(pid); + app.setUsingWrapper(usingWrapper); + app.setPendingStart(false); + } + checkSlow(app.getStartTime(), "startProcess: starting to update pids map"); + ProcessRecord oldApp; + synchronized (mService.mPidsSelfLocked) { + oldApp = mService.mPidsSelfLocked.get(pid); + } + // If there is already an app occupying that pid that hasn't been cleaned up + if (oldApp != null && !app.isolated) { + // Clean up anything relating to this pid first + Slog.wtf(TAG, "handleProcessStartedLocked process:" + app.processName + + " startSeq:" + app.getStartSeq() + + " pid:" + pid + + " belongs to another existing app:" + oldApp.processName + + " startSeq:" + oldApp.getStartSeq()); + mService.cleanUpApplicationRecordLocked(oldApp, pid, false, false, -1, + true /*replacingPid*/, false /* fromBinderDied */); + } + mService.addPidLocked(app); + synchronized (mService.mPidsSelfLocked) { + if (!procAttached) { + Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); + msg.obj = app; + mService.mHandler.sendMessageDelayed(msg, usingWrapper + ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT); + } + } + dispatchProcessStarted(app, pid); + checkSlow(app.getStartTime(), "startProcess: done updating pids map"); + return true; + } + + @GuardedBy("mService") + void removeLruProcessLocked(ProcessRecord app) { + int lrui = mLruProcesses.lastIndexOf(app); + if (lrui >= 0) { + synchronized (mProcLock) { + if (!app.isKilled()) { + if (app.isPersistent()) { + Slog.w(TAG, "Removing persistent process that hasn't been killed: " + app); + } else { + Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app); + if (app.getPid() > 0) { + killProcessQuiet(app.getPid()); + ProcessList.killProcessGroup(app.uid, app.getPid()); + noteAppKill(app, ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_REMOVE_LRU, "hasn't been killed"); + } else { + app.setPendingStart(false); + } + } + } + if (lrui < mLruProcessActivityStart) { + mLruProcessActivityStart--; + } + if (lrui < mLruProcessServiceStart) { + mLruProcessServiceStart--; + } + mLruProcesses.remove(lrui); + } + } + mService.removeOomAdjTargetLocked(app, true); + } + + @GuardedBy({"mService", "mProcLock"}) + boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj, + int reasonCode, int subReason, String reason) { + return killPackageProcessesLSP(packageName, appId, userId, minOomAdj, + false /* callerWillRestart */, true /* allowRestart */, true /* doit */, + false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */, + reasonCode, subReason, reason); + } + + @GuardedBy("mService") + void killAppZygotesLocked(String packageName, int appId, int userId, boolean force) { + // See if there are any app zygotes running for this packageName / UID combination, + // and kill it if so. + final ArrayList zygotesToKill = new ArrayList<>(); + for (SparseArray appZygotes : mAppZygotes.getMap().values()) { + for (int i = 0; i < appZygotes.size(); ++i) { + final int appZygoteUid = appZygotes.keyAt(i); + if (userId != UserHandle.USER_ALL && UserHandle.getUserId(appZygoteUid) != userId) { + continue; + } + if (appId >= 0 && UserHandle.getAppId(appZygoteUid) != appId) { + continue; + } + final AppZygote appZygote = appZygotes.valueAt(i); + if (packageName != null + && !packageName.equals(appZygote.getAppInfo().packageName)) { + continue; + } + zygotesToKill.add(appZygote); + } + } + for (AppZygote appZygote : zygotesToKill) { + killAppZygoteIfNeededLocked(appZygote, force); + } + } + + private static boolean freezePackageCgroup(int packageUID, boolean freeze) { + try { + Process.freezeCgroupUid(packageUID, freeze); + } catch (RuntimeException e) { + final String logtxt = freeze ? "freeze" : "unfreeze"; + Slog.e(TAG, "Unable to " + logtxt + " cgroup uid: " + packageUID + ": " + e); + return false; + } + return true; + } + + private static boolean unfreezePackageCgroup(int packageUID) { + return freezePackageCgroup(packageUID, false); + } + + private static void freezeBinderAndPackageCgroup(List> procs, + int packageUID) { + // Freeze all binder processes under the target UID (whose cgroup is about to be frozen). + // Since we're going to kill these, we don't need to unfreze them later. + // The procs list may not include all processes under the UID cgroup, but unincluded + // processes (forks) should not be Binder users. + int N = procs.size(); + for (int i = 0; i < N; i++) { + final int pid = procs.get(i).first.getPid(); + int nRetries = 0; + if (pid > 0) { + try { + int rc; + do { + rc = CachedAppOptimizer.freezeBinder(pid, true, 10 /* timeout_ms */); + } while (rc == -EAGAIN && nRetries++ < 1); + if (rc != 0) Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + rc); + } catch (RuntimeException e) { + Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + e); + } + } + } + + // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze + // despite being added to a child cgroup created after this call that would otherwise be + // unfrozen. + freezePackageCgroup(packageUID, true); + } + + private static List> getUIDSublist( + List> procs, int startIdx) { + final int uid = procs.get(startIdx).first.uid; + int endIdx = startIdx + 1; + while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx; + return procs.subList(startIdx, endIdx); + } + + @GuardedBy({"mService", "mProcLock"}) + boolean killPackageProcessesLSP(String packageName, int appId, + int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, + boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling, + int reasonCode, int subReason, String reason) { + final PackageManagerInternal pm = mService.getPackageManagerInternal(); + final ArrayList> procs = new ArrayList<>(); + + // Remove all processes this package may have touched: all with the + // same UID (except for the system or root user), and all whose name + // matches the package name. + final int NP = mProcessNames.getMap().size(); + for (int ip = 0; ip < NP; ip++) { + SparseArray apps = mProcessNames.getMap().valueAt(ip); + final int NA = apps.size(); + for (int ia = 0; ia < NA; ia++) { + ProcessRecord app = apps.valueAt(ia); + if (app.isPersistent() && !evenPersistent) { + // we don't kill persistent processes + continue; + } + if (app.isRemoved()) { + if (doit) { + boolean shouldAllowRestart = false; + if (!uninstalling && packageName != null) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = !app.getPkgList().containsKey(packageName) + && app.getPkgDeps() != null + && app.getPkgDeps().contains(packageName) + && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, + app.userId); + } + procs.add(new Pair<>(app, shouldAllowRestart)); + } + continue; + } + + // Skip process if it doesn't meet our oom adj requirement. + if (app.mState.getSetAdj() < minOomAdj) { + // Note it is still possible to have a process with oom adj 0 in the killed + // processes, but it does not mean misjudgment. E.g. a bound service process + // and its client activity process are both in the background, so they are + // collected to be killed. If the client activity is killed first, the service + // may be scheduled to unbind and become an executing service (oom adj 0). + continue; + } + + boolean shouldAllowRestart = false; + + // If no package is specified, we call all processes under the + // given user id. + if (packageName == null) { + if (userId != UserHandle.USER_ALL && app.userId != userId) { + continue; + } + if (appId >= 0 && UserHandle.getAppId(app.uid) != appId) { + continue; + } + // Package has been specified, we want to hit all processes + // that match it. We need to qualify this by the processes + // that are running under the specified app and user ID. + } else { + final boolean isDep = app.getPkgDeps() != null + && app.getPkgDeps().contains(packageName); + if (!isDep && UserHandle.getAppId(app.uid) != appId) { + continue; + } + if (userId != UserHandle.USER_ALL && app.userId != userId) { + continue; + } + final boolean isInPkgList = app.getPkgList().containsKey(packageName); + if (!isInPkgList && !isDep) { + continue; + } + if (!isInPkgList && isDep && !uninstalling && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = true; + } + } + + // Process has passed all conditions, kill it! + if (!doit) { + return true; + } + if (setRemoved) { + app.setRemoved(true); + } + procs.add(new Pair<>(app, shouldAllowRestart)); + } + } + + final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID + && appId <= Process.LAST_APPLICATION_UID; + + if (killingUserApp) { + procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid)); + } + + int idx = 0; + while (idx < procs.size()) { + final List> uidProcs = getUIDSublist(procs, idx); + final int packageUID = uidProcs.get(0).first.uid; + + // Do not freeze for system apps or for dependencies of the targeted package, but + // make sure to freeze the targeted package for all users if called with USER_ALL. + final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId; + + if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID); + + for (Pair proc : uidProcs) { + removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second, + reasonCode, subReason, reason, !doFreeze /* async */); + } + killAppZygotesLocked(packageName, appId, userId, false /* force */); + + if (doFreeze) unfreezePackageCgroup(packageUID); + + idx += uidProcs.size(); + } + mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END); + return procs.size() > 0; + } + + @GuardedBy("mService") + boolean removeProcessLocked(ProcessRecord app, + boolean callerWillRestart, boolean allowRestart, int reasonCode, String reason) { + return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode, + ApplicationExitInfo.SUBREASON_UNKNOWN, reason, true); + } + + @GuardedBy("mService") + boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, + boolean allowRestart, int reasonCode, int subReason, String reason) { + return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode, subReason, + reason, true); + } + + @GuardedBy("mService") + boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, + boolean allowRestart, int reasonCode, int subReason, String reason, boolean async) { + final String name = app.processName; + final int uid = app.uid; + if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES, + "Force removing proc " + app.toShortString() + " (" + name + "/" + uid + ")"); + + ProcessRecord old = mProcessNames.get(name, uid); + if (old != app) { + // This process is no longer active, so nothing to do. + Slog.w(TAG, "Ignoring remove of inactive process: " + app); + return false; + } + removeProcessNameLocked(name, uid); + mService.mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController()); + + boolean needRestart = false; + final int pid = app.getPid(); + if ((pid > 0 && pid != ActivityManagerService.MY_PID) + || (pid == 0 && app.isPendingStart())) { + if (pid > 0) { + mService.removePidLocked(pid, app); + app.setBindMountPending(false); + mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); + mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid); + if (app.isolated) { + mService.mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); + mService.getPackageManagerInternal().removeIsolatedUid(app.uid); + } + } + boolean willRestart = false; + if (app.isPersistent() && !app.isolated) { + if (!callerWillRestart) { + willRestart = true; + } else { + needRestart = true; + } + } + app.killLocked(reason, reasonCode, subReason, true, async); + mService.handleAppDiedLocked(app, pid, willRestart, allowRestart, + false /* fromBinderDied */); + if (willRestart) { + removeLruProcessLocked(app); + mService.addAppLocked(app.info, null, false, null /* ABI override */, + ZYGOTE_POLICY_FLAG_EMPTY); + } + } else { + mRemovedProcesses.add(app); + } + + return needRestart; + } + + @GuardedBy("mService") + void addProcessNameLocked(ProcessRecord proc) { + // We shouldn't already have a process under this name, but just in case we + // need to clean up whatever may be there now. + synchronized (mProcLock) { + ProcessRecord old = removeProcessNameLocked(proc.processName, proc.uid); + if (old == proc && proc.isPersistent()) { + // We are re-adding a persistent process. Whatevs! Just leave it there. + Slog.w(TAG, "Re-adding persistent process " + proc); + // Ensure that the mCrashing flag is cleared, since this is a restart + proc.resetCrashingOnRestart(); + } else if (old != null) { + if (old.isKilled()) { + // The old process has been killed, we probably haven't had + // a chance to clean up the old record, just log a warning + Slog.w(TAG, "Existing proc " + old + " was killed " + + (SystemClock.uptimeMillis() - old.getKillTime()) + + "ms ago when adding " + proc); + } else { + Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc); + } + } + UidRecord uidRec = mActiveUids.get(proc.uid); + if (uidRec == null) { + uidRec = new UidRecord(proc.uid, mService); + // This is the first appearance of the uid, report it now! + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Creating new process uid: " + uidRec); + } + if (Arrays.binarySearch(mService.mDeviceIdleTempAllowlist, + UserHandle.getAppId(proc.uid)) >= 0 + || mService.mPendingTempAllowlist.indexOfKey(proc.uid) >= 0) { + uidRec.setCurAllowListed(true); + uidRec.setSetAllowListed(true); + } + uidRec.updateHasInternetPermission(); + mActiveUids.put(proc.uid, uidRec); + EventLogTags.writeAmUidRunning(uidRec.getUid()); + mService.noteUidProcessState(uidRec.getUid(), uidRec.getCurProcState(), + uidRec.getCurCapability()); + } + proc.setUidRecord(uidRec); + uidRec.addProcess(proc); + + // Reset render thread tid if it was already set, so new process can set it again. + proc.setRenderThreadTid(0); + mProcessNames.put(proc.processName, proc.uid, proc); + } + if (proc.isolated) { + mIsolatedProcesses.put(proc.uid, proc); + } + if (proc.isSdkSandbox) { + ArrayList sdkSandboxes = mSdkSandboxes.get(proc.uid); + if (sdkSandboxes == null) { + sdkSandboxes = new ArrayList<>(); + } + sdkSandboxes.add(proc); + mSdkSandboxes.put(Process.getAppUidForSdkSandboxUid(proc.uid), sdkSandboxes); + } + } + + @GuardedBy("mService") + private IsolatedUidRange getOrCreateIsolatedUidRangeLocked(ApplicationInfo info, + HostingRecord hostingRecord) { + if (hostingRecord == null || !hostingRecord.usesAppZygote()) { + // Allocate an isolated UID from the global range + return mGlobalIsolatedUids; + } else { + return mAppIsolatedUidRangeAllocator.getOrCreateIsolatedUidRangeLocked( + info.processName, hostingRecord.getDefiningUid()); + } + } + + ProcessRecord getSharedIsolatedProcess(String processName, int uid, String packageName) { + for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) { + final ProcessRecord app = mIsolatedProcesses.valueAt(i); + if (app.info.uid == uid && app.info.packageName.equals(packageName) + && app.processName.equals(processName)) { + return app; + } + } + return null; + } + @Nullable + @GuardedBy("mService") + List getIsolatedProcessesLocked(int uid) { + List ret = null; + for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) { + final ProcessRecord app = mIsolatedProcesses.valueAt(i); + if (app.info.uid == uid) { + if (ret == null) { + ret = new ArrayList<>(); + } + ret.add(app.getPid()); + } + } + return ret; + } + + /** + * Returns the associated SDK sandbox processes for a UID. Note that this does + * NOT return a copy, so callers should not modify the result, or use it outside + * of the lock scope. + * + * @param uid UID to return sansdbox processes for + */ + @Nullable + @GuardedBy("mService") + List getSdkSandboxProcessesForAppLocked(int uid) { + return mSdkSandboxes.get(uid); + } + + @GuardedBy("mService") + ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, + boolean isolated, int isolatedUid, boolean isSdkSandbox, int sdkSandboxUid, + String sdkSandboxClientAppPackage, HostingRecord hostingRecord) { + String proc = customProcess != null ? customProcess : info.processName; + final int userId = UserHandle.getUserId(info.uid); + int uid = info.uid; + if (isSdkSandbox) { + uid = sdkSandboxUid; + } + if (Process.isSdkSandboxUid(uid) && (!isSdkSandbox || sdkSandboxClientAppPackage == null)) { + Slog.e(TAG, "Abort creating new sandbox process as required parameters are missing."); + return null; + } + if (isolated) { + if (isolatedUid == 0) { + IsolatedUidRange uidRange = getOrCreateIsolatedUidRangeLocked(info, hostingRecord); + if (uidRange == null) { + return null; + } + uid = uidRange.allocateIsolatedUidLocked(userId); + if (uid == -1) { + return null; + } + } else { + // Special case for startIsolatedProcess (internal only), where + // the uid of the isolated process is specified by the caller. + uid = isolatedUid; + } + mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(uid, info.uid); + mService.getPackageManagerInternal().addIsolatedUid(uid, info.uid); + + // Register the isolated UID with this application so BatteryStats knows to + // attribute resource usage to the application. + // + // NOTE: This is done here before addProcessNameLocked, which will tell BatteryStats + // about the process state of the isolated UID *before* it is registered with the + // owning application. + mService.mBatteryStatsService.addIsolatedUid(uid, info.uid); + } + final ProcessRecord r = new ProcessRecord(mService, info, proc, uid, + sdkSandboxClientAppPackage, + hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName()); + final ProcessStateRecord state = r.mState; + + // Check if we should mark the processrecord for first launch after force-stopping + if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) { + try { + final boolean wasPackageEverLaunched = mService.getPackageManagerInternal() + .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId); + // If the package was launched in the past but is currently stopped, only then it + // should be considered as stopped after use. Do not mark it if it's the + // first launch. + if (wasPackageEverLaunched) { + r.setWasForceStopped(true); + } + } catch (IllegalArgumentException e) { + // App doesn't have state yet, so wasn't forcestopped + } + } + + if (!isolated && !isSdkSandbox + && userId == UserHandle.USER_SYSTEM + && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK + && (TextUtils.equals(proc, info.processName))) { + // The system process is initialized to SCHED_GROUP_DEFAULT in init.rc. + state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT); + state.setSetSchedGroup(ProcessList.SCHED_GROUP_DEFAULT); + r.setPersistent(true); + state.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ); + } + if (isolated && isolatedUid != 0) { + // Special case for startIsolatedProcess (internal only) - assume the process + // is required by the system server to prevent it being killed. + state.setMaxAdj(ProcessList.PERSISTENT_SERVICE_ADJ); + } + addProcessNameLocked(r); + return r; + } + + @GuardedBy("mService") + ProcessRecord removeProcessNameLocked(final String name, final int uid) { + return removeProcessNameLocked(name, uid, null); + } + + @GuardedBy("mService") + ProcessRecord removeProcessNameLocked(final String name, final int uid, + final ProcessRecord expecting) { + ProcessRecord old = mProcessNames.get(name, uid); + final ProcessRecord record = expecting != null ? expecting : old; + synchronized (mProcLock) { + // Only actually remove when the currently recorded value matches the + // record that we expected; if it doesn't match then we raced with a + // newly created process and we don't want to destroy the new one. + if ((expecting == null) || (old == expecting)) { + mProcessNames.remove(name, uid); + } + if (record != null) { + final UidRecord uidRecord = record.getUidRecord(); + if (uidRecord != null) { + uidRecord.removeProcess(record); + if (uidRecord.getNumOfProcs() == 0) { + // No more processes using this uid, tell clients it is gone. + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "No more processes in " + uidRecord); + } + mService.enqueueUidChangeLocked(uidRecord, -1, + UidRecord.CHANGE_GONE | UidRecord.CHANGE_PROCSTATE); + EventLogTags.writeAmUidStopped(uid); + mActiveUids.remove(uid); + mService.mFgsStartTempAllowList.removeUid(record.info.uid); + mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT, + ActivityManager.PROCESS_CAPABILITY_NONE); + } + record.setUidRecord(null); + } + } + } + mIsolatedProcesses.remove(uid); + mGlobalIsolatedUids.freeIsolatedUidLocked(uid); + // Remove the (expected) ProcessRecord from the app zygote + if (record != null && record.appZygote) { + removeProcessFromAppZygoteLocked(record); + } + if (record != null && record.isSdkSandbox) { + final int appUid = Process.getAppUidForSdkSandboxUid(uid); + final ArrayList sdkSandboxesForUid = mSdkSandboxes.get(appUid); + if (sdkSandboxesForUid != null) { + sdkSandboxesForUid.remove(record); + if (sdkSandboxesForUid.size() == 0) { + mSdkSandboxes.remove(appUid); + } + } + } + mAppsInBackgroundRestricted.remove(record); + + return old; + } + + /** Call setCoreSettings on all LRU processes, with the new settings. */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void updateCoreSettingsLOSP(Bundle settings) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord processRecord = mLruProcesses.get(i); + final IApplicationThread thread = processRecord.getThread(); + try { + if (thread != null) { + thread.setCoreSettings(settings); + } + } catch (RemoteException re) { + /* ignore */ + } + } + } + + /** + * Kill all background processes except for ones with targetSdk lower than minTargetSdk and + * procstate lower than maxProcState. + * @param minTargetSdk + * @param maxProcState + */ + @GuardedBy({"mService", "mProcLock"}) + void killAllBackgroundProcessesExceptLSP(int minTargetSdk, int maxProcState) { + final ArrayList procs = new ArrayList<>(); + final int NP = mProcessNames.getMap().size(); + for (int ip = 0; ip < NP; ip++) { + final SparseArray apps = mProcessNames.getMap().valueAt(ip); + final int NA = apps.size(); + for (int ia = 0; ia < NA; ia++) { + final ProcessRecord app = apps.valueAt(ia); + if (app.isRemoved() + || ((minTargetSdk < 0 || app.info.targetSdkVersion < minTargetSdk) + && (maxProcState < 0 || app.mState.getSetProcState() > maxProcState))) { + procs.add(app); + } + } + } + + final int N = procs.size(); + for (int i = 0; i < N; i++) { + removeProcessLocked(procs.get(i), false, true, ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_KILL_ALL_BG_EXCEPT, "kill all background except"); + } + } + + /** + * Call updateTimePrefs on all LRU processes + * @param timePref The time pref to pass to each process + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void updateAllTimePrefsLOSP(int timePref) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord r = mLruProcesses.get(i); + final IApplicationThread thread = r.getThread(); + if (thread != null) { + try { + thread.updateTimePrefs(timePref); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to update preferences for: " + + r.info.processName); + } + } + } + } + + void setAllHttpProxy() { + // Update the HTTP proxy for each application thread. + synchronized (mProcLock) { + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = mLruProcesses.get(i); + IApplicationThread thread = r.getThread(); + // Don't dispatch to isolated processes as they can't access ConnectivityManager and + // don't have network privileges anyway. Exclude system server and update it + // separately outside the AMS lock, to avoid deadlock with Connectivity Service. + if (r.getPid() != ActivityManagerService.MY_PID && thread != null && !r.isolated) { + try { + thread.updateHttpProxy(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to update http proxy for: " + + r.info.processName); + } + } + } + } + ActivityThread.updateHttpProxy(mService.mContext); + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void clearAllDnsCacheLOSP() { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord r = mLruProcesses.get(i); + final IApplicationThread thread = r.getThread(); + if (thread != null) { + try { + thread.clearDnsCache(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName); + } + } + } + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void handleAllTrustStorageUpdateLOSP() { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord r = mLruProcesses.get(i); + final IApplicationThread thread = r.getThread(); + if (thread != null) { + try { + thread.handleTrustStorageUpdate(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to handle trust storage update for: " + + r.info.processName); + } + } + } + } + + @GuardedBy({"mService", "mProcLock"}) + private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index, + int lruSeq, String what, Object obj, ProcessRecord srcApp) { + app.setLastActivityTime(now); + + if (app.hasActivitiesOrRecentTasks()) { + // Don't want to touch dependent processes that are hosting activities. + return index; + } + + int lrui = mLruProcesses.lastIndexOf(app); + if (lrui < 0) { + Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + + what + " " + obj + " from " + srcApp); + return index; + } + + if (lrui >= index) { + // Don't want to cause this to move dependent processes *back* in the + // list as if they were less frequently used. + return index; + } + + if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) { + // Don't want to touch dependent processes that are hosting activities. + return index; + } + + mLruProcesses.remove(lrui); + if (index > 0) { + index--; + } + if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index + + " in LRU list: " + app); + mLruProcesses.add(index, app); + app.setLruSeq(lruSeq); + return index; + } + + /** + * Handle the case where we are inserting a process hosting client activities: + * Make sure any groups have their order match their importance, and take care of + * distributing old clients across other activity processes so they can't spam + * the LRU list. Processing of the list will be restricted by the indices provided, + * and not extend out of them. + * + * @param topApp The app at the top that has just been inserted in to the list. + * @param topI The position in the list where topApp was inserted; this is the start (at the + * top) where we are going to do our processing. + * @param bottomI The last position at which we will be processing; this is the end position + * of whichever section of the LRU list we are in. Nothing past it will be + * touched. + * @param endIndex The current end of the top being processed. Typically topI - 1. That is, + * where we are going to start potentially adjusting other entries in the list. + */ + @GuardedBy({"mService", "mProcLock"}) + private void updateClientActivitiesOrderingLSP(final ProcessRecord topApp, final int topI, + final int bottomI, int endIndex) { + final ProcessServiceRecord topPsr = topApp.mServices; + if (topApp.hasActivitiesOrRecentTasks() || topPsr.isTreatedLikeActivity() + || !topPsr.hasClientActivities()) { + // If this is not a special process that has client activities, then there is + // nothing to do. + return; + } + + final int uid = topApp.info.uid; + final int topConnectionGroup = topPsr.getConnectionGroup(); + if (topConnectionGroup > 0) { + int endImportance = topPsr.getConnectionImportance(); + for (int i = endIndex; i >= bottomI; i--) { + final ProcessRecord subProc = mLruProcesses.get(i); + final ProcessServiceRecord subPsr = subProc.mServices; + final int subConnectionGroup = subPsr.getConnectionGroup(); + final int subConnectionImportance = subPsr.getConnectionImportance(); + if (subProc.info.uid == uid + && subConnectionGroup == topConnectionGroup) { + if (i == endIndex && subConnectionImportance >= endImportance) { + // This process is already in the group, and its importance + // is not as strong as the process before it, so keep it + // correctly positioned in the group. + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Keeping in-place above " + subProc + + " endImportance=" + endImportance + + " group=" + subConnectionGroup + + " importance=" + subConnectionImportance); + endIndex--; + endImportance = subConnectionImportance; + } else { + // We want to pull this up to be with the rest of the group, + // and order within the group by importance. + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Pulling up " + subProc + + " to position in group with importance=" + + subConnectionImportance); + boolean moved = false; + for (int pos = topI; pos > endIndex; pos--) { + final ProcessRecord posProc = mLruProcesses.get(pos); + if (subConnectionImportance + <= posProc.mServices.getConnectionImportance()) { + mLruProcesses.remove(i); + mLruProcesses.add(pos, subProc); + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Moving " + subProc + + " from position " + i + " to above " + posProc + + " @ " + pos); + moved = true; + endIndex--; + break; + } + } + if (!moved) { + // Goes to the end of the group. + mLruProcesses.remove(i); + mLruProcesses.add(endIndex, subProc); + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Moving " + subProc + + " from position " + i + " to end of group @ " + + endIndex); + endIndex--; + endImportance = subConnectionImportance; + } + } + } + } + + } + // To keep it from spamming the LRU list (by making a bunch of clients), + // we will distribute other entries owned by it to be in-between other apps. + int i = endIndex; + while (i >= bottomI) { + ProcessRecord subProc = mLruProcesses.get(i); + final ProcessServiceRecord subPsr = subProc.mServices; + final int subConnectionGroup = subPsr.getConnectionGroup(); + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Looking to spread old procs, at " + subProc + " @ " + i); + if (subProc.info.uid != uid) { + // This is a different app... if we have gone through some of the + // target app, pull this up to be before them. We want to pull up + // one activity process, but any number of non-activity processes. + if (i < endIndex) { + boolean hasActivity = false; + int connUid = 0; + int connGroup = 0; + while (i >= bottomI) { + mLruProcesses.remove(i); + mLruProcesses.add(endIndex, subProc); + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Different app, moving to " + endIndex); + i--; + if (i < bottomI) { + break; + } + subProc = mLruProcesses.get(i); + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Looking at next app at " + i + ": " + subProc); + if (subProc.hasActivitiesOrRecentTasks() + || subPsr.isTreatedLikeActivity()) { + if (DEBUG_LRU) Slog.d(TAG_LRU, + "This is hosting an activity!"); + if (hasActivity) { + // Already found an activity, done. + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Already found an activity, done"); + break; + } + hasActivity = true; + } else if (subPsr.hasClientActivities()) { + if (DEBUG_LRU) Slog.d(TAG_LRU, + "This is a client of an activity"); + if (hasActivity) { + if (connUid == 0 || connUid != subProc.info.uid) { + // Already have an activity that is not from from a client + // connection or is a different client connection, done. + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Already found a different activity: connUid=" + + connUid + " uid=" + subProc.info.uid); + break; + } else if (connGroup == 0 || connGroup != subConnectionGroup) { + // Previously saw a different group or not from a group, + // want to treat these as different things. + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Already found a different group: connGroup=" + + connGroup + " group=" + subConnectionGroup); + break; + } + } else { + if (DEBUG_LRU) Slog.d(TAG_LRU, + "This is an activity client! uid=" + + subProc.info.uid + " group=" + subConnectionGroup); + hasActivity = true; + connUid = subProc.info.uid; + connGroup = subConnectionGroup; + } + } + endIndex--; + } + } + // Find the end of the next group of processes for target app. This + // is after any entries of different apps (so we don't change the existing + // relative order of apps) and then after the next last group of processes + // of the target app. + for (endIndex--; endIndex >= bottomI; endIndex--) { + final ProcessRecord endProc = mLruProcesses.get(endIndex); + if (endProc.info.uid == uid) { + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Found next group of app: " + endProc + " @ " + + endIndex); + break; + } + } + if (endIndex >= bottomI) { + final ProcessRecord endProc = mLruProcesses.get(endIndex); + final ProcessServiceRecord endPsr = endProc.mServices; + final int endConnectionGroup = endPsr.getConnectionGroup(); + for (endIndex--; endIndex >= bottomI; endIndex--) { + final ProcessRecord nextEndProc = mLruProcesses.get(endIndex); + final int nextConnectionGroup = nextEndProc.mServices.getConnectionGroup(); + if (nextEndProc.info.uid != uid + || nextConnectionGroup != endConnectionGroup) { + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Found next group or app: " + nextEndProc + " @ " + + endIndex + " group=" + nextConnectionGroup); + break; + } + } + } + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Bumping scan position to " + endIndex); + i = endIndex; + } else { + i--; + } + } + } + + @GuardedBy("mService") + void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) { + final ProcessServiceRecord psr = app.mServices; + final boolean hasActivity = app.hasActivitiesOrRecentTasks() || psr.hasClientActivities() + || psr.isTreatedLikeActivity(); + final boolean hasService = false; // not impl yet. app.services.size() > 0; + if (!activityChange && hasActivity) { + // The process has activities, so we are only allowing activity-based adjustments + // to move it. It should be kept in the front of the list with other + // processes that have activities, and we don't want those to change their + // order except due to activity operations. + return; + } + + if (app.getPid() == 0 && !app.isPendingStart()) { + // This process has been killed and its cleanup is done, don't proceed the LRU update. + return; + } + + synchronized (mProcLock) { + updateLruProcessLSP(app, client, hasActivity, hasService); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void updateLruProcessLSP(ProcessRecord app, ProcessRecord client, + boolean hasActivity, boolean hasService) { + mLruSeq++; + final long now = SystemClock.uptimeMillis(); + final ProcessServiceRecord psr = app.mServices; + app.setLastActivityTime(now); + + // First a quick reject: if the app is already at the position we will + // put it, then there is nothing to do. + if (hasActivity) { + final int N = mLruProcesses.size(); + if (N > 0 && mLruProcesses.get(N - 1) == app) { + if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app); + return; + } + } else { + if (mLruProcessServiceStart > 0 + && mLruProcesses.get(mLruProcessServiceStart-1) == app) { + if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app); + return; + } + } + + int lrui = mLruProcesses.lastIndexOf(app); + + if (app.isPersistent() && lrui >= 0) { + // We don't care about the position of persistent processes, as long as + // they are in the list. + if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, persistent: " + app); + return; + } + + /* In progress: compute new position first, so we can avoid doing work + if the process is not actually going to move. Not yet working. + int addIndex; + int nextIndex; + boolean inActivity = false, inService = false; + if (hasActivity) { + // Process has activities, put it at the very tipsy-top. + addIndex = mLruProcesses.size(); + nextIndex = mLruProcessServiceStart; + inActivity = true; + } else if (hasService) { + // Process has services, put it at the top of the service list. + addIndex = mLruProcessActivityStart; + nextIndex = mLruProcessServiceStart; + inActivity = true; + inService = true; + } else { + // Process not otherwise of interest, it goes to the top of the non-service area. + addIndex = mLruProcessServiceStart; + if (client != null) { + int clientIndex = mLruProcesses.lastIndexOf(client); + if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating " + + app); + if (clientIndex >= 0 && addIndex > clientIndex) { + addIndex = clientIndex; + } + } + nextIndex = addIndex > 0 ? addIndex-1 : addIndex; + } + + Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act=" + + mLruProcessActivityStart + "): " + app); + */ + + if (lrui >= 0) { + if (lrui < mLruProcessActivityStart) { + mLruProcessActivityStart--; + } + if (lrui < mLruProcessServiceStart) { + mLruProcessServiceStart--; + } + /* + if (addIndex > lrui) { + addIndex--; + } + if (nextIndex > lrui) { + nextIndex--; + } + */ + mLruProcesses.remove(lrui); + } + + /* + mLruProcesses.add(addIndex, app); + if (inActivity) { + mLruProcessActivityStart++; + } + if (inService) { + mLruProcessActivityStart++; + } + */ + + int nextIndex; + int nextActivityIndex = -1; + if (hasActivity) { + final int N = mLruProcesses.size(); + nextIndex = mLruProcessServiceStart; + if (!app.hasActivitiesOrRecentTasks() && !psr.isTreatedLikeActivity() + && mLruProcessActivityStart < (N - 1)) { + // Process doesn't have activities, but has clients with + // activities... move it up, but below the app that is binding to it. + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Adding to second-top of LRU activity list: " + app + + " group=" + psr.getConnectionGroup() + + " importance=" + psr.getConnectionImportance()); + int pos = N - 1; + while (pos > mLruProcessActivityStart) { + final ProcessRecord posproc = mLruProcesses.get(pos); + if (posproc.info.uid == app.info.uid) { + // Technically this app could have multiple processes with different + // activities and so we should be looking for the actual process that + // is bound to the target proc... but I don't really care, do you? + break; + } + pos--; + } + mLruProcesses.add(pos, app); + // If this process is part of a group, need to pull up any other processes + // in that group to be with it. + int endIndex = pos - 1; + if (endIndex < mLruProcessActivityStart) { + endIndex = mLruProcessActivityStart; + } + nextActivityIndex = endIndex; + updateClientActivitiesOrderingLSP(app, pos, mLruProcessActivityStart, endIndex); + } else { + // Process has activities, put it at the very tipsy-top. + if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app); + mLruProcesses.add(app); + nextActivityIndex = mLruProcesses.size() - 1; + } + } else if (hasService) { + // Process has services, put it at the top of the service list. + if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU service list: " + app); + mLruProcesses.add(mLruProcessActivityStart, app); + nextIndex = mLruProcessServiceStart; + mLruProcessActivityStart++; + } else { + // Process not otherwise of interest, it goes to the top of the non-service area. + int index = mLruProcessServiceStart; + if (client != null) { + // If there is a client, don't allow the process to be moved up higher + // in the list than that client. + int clientIndex = mLruProcesses.lastIndexOf(client); + if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG_LRU, "Unknown client " + client + + " when updating " + app); + if (clientIndex <= lrui) { + // Don't allow the client index restriction to push it down farther in the + // list than it already is. + clientIndex = lrui; + } + if (clientIndex >= 0 && index > clientIndex) { + index = clientIndex; + } + } + if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding at " + index + " of LRU list: " + app); + mLruProcesses.add(index, app); + nextIndex = index - 1; + mLruProcessActivityStart++; + mLruProcessServiceStart++; + if (index > 1) { + updateClientActivitiesOrderingLSP(app, mLruProcessServiceStart - 1, 0, index - 1); + } + } + + app.setLruSeq(mLruSeq); + + // If the app is currently using a content provider or service, + // bump those processes as well. + for (int j = psr.numberOfConnections() - 1; j >= 0; j--) { + ConnectionRecord cr = psr.getConnectionAt(j); + if (cr.binding != null && !cr.serviceDead && cr.binding.service != null + && cr.binding.service.app != null + && cr.binding.service.app.getLruSeq() != mLruSeq + && cr.notHasFlag(Context.BIND_REDUCTION_FLAGS) + && !cr.binding.service.app.isPersistent()) { + if (cr.binding.service.app.mServices.hasClientActivities()) { + if (nextActivityIndex >= 0) { + nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app, + now, + nextActivityIndex, mLruSeq, + "service connection", cr, app); + } + } else { + nextIndex = updateLruProcessInternalLSP(cr.binding.service.app, + now, + nextIndex, mLruSeq, + "service connection", cr, app); + } + } + } + final ProcessProviderRecord ppr = app.mProviders; + for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) { + ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider; + if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) { + nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq, + "provider reference", cpr, app); + } + } + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + ProcessRecord getLRURecordForAppLOSP(IApplicationThread thread) { + if (thread == null) { + return null; + } + return getLRURecordForAppLOSP(thread.asBinder()); + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + ProcessRecord getLRURecordForAppLOSP(IBinder threadBinder) { + if (threadBinder == null) { + return null; + } + // Find the application record. + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + final ProcessRecord rec = mLruProcesses.get(i); + final IApplicationThread t = rec.getThread(); + if (t != null && t.asBinder() == threadBinder) { + return rec; + } + } + return null; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + boolean haveBackgroundProcessLOSP() { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + final ProcessRecord rec = mLruProcesses.get(i); + if (rec.getThread() != null + && rec.mState.getSetProcState() >= PROCESS_STATE_CACHED_ACTIVITY) { + return true; + } + } + return false; + } + + private static int procStateToImportance(int procState, int memAdj, + ActivityManager.RunningAppProcessInfo currApp, + int clientTargetSdk) { + int imp = ActivityManager.RunningAppProcessInfo.procStateToImportanceForTargetSdk( + procState, clientTargetSdk); + if (imp == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { + currApp.lru = memAdj; + } else { + currApp.lru = 0; + } + return imp; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void fillInProcMemInfoLOSP(ProcessRecord app, + ActivityManager.RunningAppProcessInfo outInfo, + int clientTargetSdk) { + outInfo.pid = app.getPid(); + outInfo.uid = app.info.uid; + if (app.getWindowProcessController().isHeavyWeightProcess()) { + outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE; + } + if (app.isPersistent()) { + outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; + } + if (app.hasActivities()) { + outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES; + } + outInfo.lastTrimLevel = app.mProfile.getTrimMemoryLevel(); + final ProcessStateRecord state = app.mState; + int adj = state.getCurAdj(); + int procState = state.getCurProcState(); + outInfo.importance = procStateToImportance(procState, adj, outInfo, + clientTargetSdk); + outInfo.importanceReasonCode = state.getAdjTypeCode(); + outInfo.processState = procState; + outInfo.isFocused = (app == mService.getTopApp()); + outInfo.lastActivityTime = app.getLastActivityTime(); + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + List getRunningAppProcessesLOSP(boolean allUsers, + int userId, boolean allUids, int callingUid, int clientTargetSdk) { + // Lazy instantiation of list + List runList = null; + + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord app = mLruProcesses.get(i); + final ProcessStateRecord state = app.mState; + final ProcessErrorStateRecord errState = app.mErrorState; + if ((!allUsers && app.userId != userId) + || (!allUids && app.uid != callingUid)) { + continue; + } + if ((app.getThread() != null) + && (!errState.isCrashing() && !errState.isNotResponding())) { + // Generate process state info for running application + ActivityManager.RunningAppProcessInfo currApp = + new ActivityManager.RunningAppProcessInfo(app.processName, + app.getPid(), app.getPackageList()); + if (app.getPkgDeps() != null) { + final int size = app.getPkgDeps().size(); + currApp.pkgDeps = app.getPkgDeps().toArray(new String[size]); + } + fillInProcMemInfoLOSP(app, currApp, clientTargetSdk); + if (state.getAdjSource() instanceof ProcessRecord) { + currApp.importanceReasonPid = ((ProcessRecord) state.getAdjSource()).getPid(); + currApp.importanceReasonImportance = + ActivityManager.RunningAppProcessInfo.procStateToImportance( + state.getAdjSourceProcState()); + } else if (state.getAdjSource() instanceof ActivityServiceConnectionsHolder) { + final ActivityServiceConnectionsHolder r = + (ActivityServiceConnectionsHolder) state.getAdjSource(); + final int pid = r.getActivityPid(); + if (pid != -1) { + currApp.importanceReasonPid = pid; + } + } + if (state.getAdjTarget() instanceof ComponentName) { + currApp.importanceReasonComponent = (ComponentName) state.getAdjTarget(); + } + //Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance + // + " lru=" + currApp.lru); + if (runList == null) { + runList = new ArrayList<>(); + } + runList.add(currApp); + } + } + return runList; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + int getLruSizeLOSP() { + return mLruProcesses.size(); + } + + /** + * Return the reference to the LRU list, call this function for read-only access + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + ArrayList getLruProcessesLOSP() { + return mLruProcesses; + } + + /** + * Return the reference to the LRU list, call this function for read/write access + */ + @GuardedBy({"mService", "mProcLock"}) + ArrayList getLruProcessesLSP() { + return mLruProcesses; + } + + /** + * For test only + */ + @VisibleForTesting + @GuardedBy({"mService", "mProcLock"}) + void setLruProcessServiceStartLSP(int pos) { + mLruProcessServiceStart = pos; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + int getLruProcessServiceStartLOSP() { + return mLruProcessServiceStart; + } + + /** + * Iterate the whole LRU list, invoke the given {@code callback} with each of the ProcessRecord + * in that list. + * + * @param iterateForward If {@code true}, iterate the LRU list from the least recent used + * to most recent used ProcessRecord. + * @param callback The callback interface to accept the current ProcessRecord. + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void forEachLruProcessesLOSP(boolean iterateForward, + @NonNull Consumer callback) { + if (iterateForward) { + for (int i = 0, size = mLruProcesses.size(); i < size; i++) { + callback.accept(mLruProcesses.get(i)); + } + } else { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + callback.accept(mLruProcesses.get(i)); + } + } + } + + /** + * Search in the LRU list, invoke the given {@code callback} with each of the ProcessRecord + * in that list; if the callback returns a non-null object, halt the search, return that + * object as the return value of this search function. + * + * @param iterateForward If {@code true}, iterate the LRU list from the least recent used + * to most recent used ProcessRecord. + * @param callback The callback interface to accept the current ProcessRecord; if it returns + * a non-null object, the search will be halted and this object will be used + * as the return value of this search function. + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + R searchEachLruProcessesLOSP(boolean iterateForward, + @NonNull Function callback) { + if (iterateForward) { + for (int i = 0, size = mLruProcesses.size(); i < size; i++) { + R r; + if ((r = callback.apply(mLruProcesses.get(i))) != null) { + return r; + } + } + } else { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + R r; + if ((r = callback.apply(mLruProcesses.get(i))) != null) { + return r; + } + } + } + return null; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + boolean isInLruListLOSP(ProcessRecord app) { + return mLruProcesses.contains(app); + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + int getLruSeqLOSP() { + return mLruSeq; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + MyProcessMap getProcessNamesLOSP() { + return mProcessNames; + } + + @GuardedBy("mService") + void dumpLruListHeaderLocked(PrintWriter pw) { + pw.print(" Process LRU list (sorted by oom_adj, "); pw.print(mLruProcesses.size()); + pw.print(" total, non-act at "); + pw.print(mLruProcesses.size() - mLruProcessActivityStart); + pw.print(", non-svc at "); + pw.print(mLruProcesses.size() - mLruProcessServiceStart); + pw.println("):"); + } + + @GuardedBy("mService") + private void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) { + pw.print(prefix); + pw.print('#'); + if (index < 10) { + pw.print(' '); + } + pw.print(index); + pw.print(": "); + pw.print(makeOomAdjString(proc.mState.getSetAdj(), false)); + pw.print(' '); + pw.print(makeProcStateString(proc.mState.getCurProcState())); + pw.print(' '); + ActivityManager.printCapabilitiesSummary(pw, proc.mState.getCurCapability()); + pw.print(' '); + pw.print(proc.toShortString()); + final ProcessServiceRecord psr = proc.mServices; + if (proc.hasActivitiesOrRecentTasks() || psr.hasClientActivities() + || psr.isTreatedLikeActivity()) { + pw.print(" act:"); + boolean printed = false; + if (proc.hasActivities()) { + pw.print("activities"); + printed = true; + } + if (proc.hasRecentTasks()) { + if (printed) { + pw.print("|"); + } + pw.print("recents"); + printed = true; + } + if (psr.hasClientActivities()) { + if (printed) { + pw.print("|"); + } + pw.print("client"); + printed = true; + } + if (psr.isTreatedLikeActivity()) { + if (printed) { + pw.print("|"); + } + pw.print("treated"); + } + } + pw.println(); + } + + @GuardedBy("mService") + boolean dumpLruLocked(PrintWriter pw, String dumpPackage, String prefix) { + final int lruSize = mLruProcesses.size(); + final String innerPrefix; + if (prefix == null) { + pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity lru)"); + innerPrefix = " "; + } else { + boolean haveAny = false; + for (int i = lruSize - 1; i >= 0; i--) { + final ProcessRecord r = mLruProcesses.get(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + haveAny = true; + break; + } + if (!haveAny) { + return false; + } + pw.print(prefix); + pw.println("Raw LRU list (dumpsys activity lru):"); + innerPrefix = prefix + " "; + } + int i; + boolean first = true; + for (i = lruSize - 1; i >= mLruProcessActivityStart; i--) { + final ProcessRecord r = mLruProcesses.get(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + if (first) { + pw.print(innerPrefix); + pw.println("Activities:"); + first = false; + } + dumpLruEntryLocked(pw, i, r, innerPrefix); + } + first = true; + for (; i >= mLruProcessServiceStart; i--) { + final ProcessRecord r = mLruProcesses.get(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + if (first) { + pw.print(innerPrefix); + pw.println("Services:"); + first = false; + } + dumpLruEntryLocked(pw, i, r, innerPrefix); + } + first = true; + for (; i >= 0; i--) { + final ProcessRecord r = mLruProcesses.get(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + if (first) { + pw.print(innerPrefix); + pw.println("Other:"); + first = false; + } + dumpLruEntryLocked(pw, i, r, innerPrefix); + } + return true; + } + + @GuardedBy({"mService", "mProcLock"}) + void dumpProcessesLSP(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage, int dumpAppId) { + boolean needSep = false; + int numPers = 0; + + pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)"); + + if (dumpAll || dumpPackage != null) { + final int numOfNames = mProcessNames.getMap().size(); + for (int ip = 0; ip < numOfNames; ip++) { + SparseArray procs = mProcessNames.getMap().valueAt(ip); + for (int ia = 0, size = procs.size(); ia < size; ia++) { + ProcessRecord r = procs.valueAt(ia); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + if (!needSep) { + pw.println(" All known processes:"); + needSep = true; + } + pw.print(r.isPersistent() ? " *PERS*" : " *APP*"); + pw.print(" UID "); pw.print(procs.keyAt(ia)); + pw.print(" "); pw.println(r); + r.dump(pw, " "); + if (r.isPersistent()) { + numPers++; + } + } + } + } + + if (mIsolatedProcesses.size() > 0) { + boolean printed = false; + for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) { + ProcessRecord r = mIsolatedProcesses.valueAt(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + pw.println(" Isolated process list (sorted by uid):"); + printed = true; + needSep = true; + } + pw.print(" Isolated #"); pw.print(i); pw.print(": "); + pw.println(r); + } + } + + needSep = mService.dumpActiveInstruments(pw, dumpPackage, needSep); + + if (dumpOomLocked(fd, pw, needSep, args, opti, dumpAll, dumpPackage, false)) { + needSep = true; + } + + if (mActiveUids.size() > 0) { + needSep |= mActiveUids.dump(pw, dumpPackage, dumpAppId, + "UID states:", needSep); + } + + if (dumpAll) { + needSep |= mService.mUidObserverController.dumpValidateUids(pw, + dumpPackage, dumpAppId, "UID validation:", needSep); + } + + if (needSep) { + pw.println(); + } + if (dumpLruLocked(pw, dumpPackage, " ")) { + needSep = true; + } + + if (getLruSizeLOSP() > 0) { + if (needSep) { + pw.println(); + } + dumpLruListHeaderLocked(pw); + dumpProcessOomList(pw, mService, mLruProcesses, " ", "Proc", "PERS", false, + dumpPackage); + needSep = true; + } + + mService.dumpOtherProcessesInfoLSP(fd, pw, dumpAll, dumpPackage, dumpAppId, numPers, + needSep); + } + + @GuardedBy({"mService", "mProcLock"}) + void writeProcessesToProtoLSP(ProtoOutputStream proto, String dumpPackage) { + int numPers = 0; + + final int numOfNames = mProcessNames.getMap().size(); + for (int ip = 0; ip < numOfNames; ip++) { + SparseArray procs = mProcessNames.getMap().valueAt(ip); + for (int ia = 0, size = procs.size(); ia < size; ia++) { + ProcessRecord r = procs.valueAt(ia); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.PROCS, + mLruProcesses.indexOf(r) + ); + if (r.isPersistent()) { + numPers++; + } + } + } + + for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) { + ProcessRecord r = mIsolatedProcesses.valueAt(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ISOLATED_PROCS, + mLruProcesses.indexOf(r) + ); + } + + final int dumpAppId = mService.getAppId(dumpPackage); + mActiveUids.dumpProto(proto, dumpPackage, dumpAppId, + ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS); + + if (getLruSizeLOSP() > 0) { + long lruToken = proto.start(ActivityManagerServiceDumpProcessesProto.LRU_PROCS); + int total = getLruSizeLOSP(); + proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.SIZE, total); + proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_ACT_AT, + total - mLruProcessActivityStart); + proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_SVC_AT, + total - mLruProcessServiceStart); + writeProcessOomListToProto(proto, + ActivityManagerServiceDumpProcessesProto.LruProcesses.LIST, mService, + mLruProcesses, true, dumpPackage); + proto.end(lruToken); + } + + mService.writeOtherProcessesInfoToProtoLSP(proto, dumpPackage, dumpAppId, numPers); + } + + private static ArrayList> sortProcessOomList( + List origList, String dumpPackage) { + ArrayList> list = + new ArrayList>(origList.size()); + for (int i = 0, size = origList.size(); i < size; i++) { + ProcessRecord r = origList.get(i); + if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) { + continue; + } + list.add(new Pair(origList.get(i), i)); + } + + Comparator> comparator = + new Comparator>() { + @Override + public int compare(Pair object1, + Pair object2) { + final int adj = object2.first.mState.getSetAdj() - object1.first.mState.getSetAdj(); + if (adj != 0) { + return adj; + } + final int procState = object2.first.mState.getSetProcState() + - object1.first.mState.getSetProcState(); + if (procState != 0) { + return procState; + } + final int val = object2.second - object1.second; + if (val != 0) { + return val; + } + return 0; + } + }; + + Collections.sort(list, comparator); + return list; + } + + private static boolean writeProcessOomListToProto(ProtoOutputStream proto, long fieldId, + ActivityManagerService service, List origList, + boolean inclDetails, String dumpPackage) { + ArrayList> list = sortProcessOomList(origList, dumpPackage); + if (list.isEmpty()) return false; + + final long curUptime = SystemClock.uptimeMillis(); + + for (int i = list.size() - 1; i >= 0; i--) { + ProcessRecord r = list.get(i).first; + final ProcessStateRecord state = r.mState; + final ProcessServiceRecord psr = r.mServices; + long token = proto.start(fieldId); + String oomAdj = makeOomAdjString(state.getSetAdj(), true); + proto.write(ProcessOomProto.PERSISTENT, r.isPersistent()); + proto.write(ProcessOomProto.NUM, (origList.size() - 1) - list.get(i).second); + proto.write(ProcessOomProto.OOM_ADJ, oomAdj); + int schedGroup = ProcessOomProto.SCHED_GROUP_UNKNOWN; + switch (state.getSetSchedGroup()) { + case SCHED_GROUP_BACKGROUND: + schedGroup = ProcessOomProto.SCHED_GROUP_BACKGROUND; + break; + case SCHED_GROUP_DEFAULT: + schedGroup = ProcessOomProto.SCHED_GROUP_DEFAULT; + break; + case SCHED_GROUP_TOP_APP: + schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP; + break; + case SCHED_GROUP_TOP_APP_BOUND: + schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP_BOUND; + break; + } + if (schedGroup != ProcessOomProto.SCHED_GROUP_UNKNOWN) { + proto.write(ProcessOomProto.SCHED_GROUP, schedGroup); + } + if (state.hasForegroundActivities()) { + proto.write(ProcessOomProto.ACTIVITIES, true); + } else if (psr.hasForegroundServices()) { + proto.write(ProcessOomProto.SERVICES, true); + } + proto.write(ProcessOomProto.STATE, + makeProcStateProtoEnum(state.getCurProcState())); + proto.write(ProcessOomProto.TRIM_MEMORY_LEVEL, r.mProfile.getTrimMemoryLevel()); + r.dumpDebug(proto, ProcessOomProto.PROC); + proto.write(ProcessOomProto.ADJ_TYPE, state.getAdjType()); + if (state.getAdjSource() != null || state.getAdjTarget() != null) { + if (state.getAdjTarget() instanceof ComponentName) { + ComponentName cn = (ComponentName) state.getAdjTarget(); + cn.dumpDebug(proto, ProcessOomProto.ADJ_TARGET_COMPONENT_NAME); + } else if (state.getAdjTarget() != null) { + proto.write(ProcessOomProto.ADJ_TARGET_OBJECT, state.getAdjTarget().toString()); + } + if (state.getAdjSource() instanceof ProcessRecord) { + ProcessRecord p = (ProcessRecord) state.getAdjSource(); + p.dumpDebug(proto, ProcessOomProto.ADJ_SOURCE_PROC); + } else if (state.getAdjSource() != null) { + proto.write(ProcessOomProto.ADJ_SOURCE_OBJECT, state.getAdjSource().toString()); + } + } + if (inclDetails) { + long detailToken = proto.start(ProcessOomProto.DETAIL); + proto.write(ProcessOomProto.Detail.MAX_ADJ, state.getMaxAdj()); + proto.write(ProcessOomProto.Detail.CUR_RAW_ADJ, state.getCurRawAdj()); + proto.write(ProcessOomProto.Detail.SET_RAW_ADJ, state.getSetRawAdj()); + proto.write(ProcessOomProto.Detail.CUR_ADJ, state.getCurAdj()); + proto.write(ProcessOomProto.Detail.SET_ADJ, state.getSetAdj()); + proto.write(ProcessOomProto.Detail.CURRENT_STATE, + makeProcStateProtoEnum(state.getCurProcState())); + proto.write(ProcessOomProto.Detail.SET_STATE, + makeProcStateProtoEnum(state.getSetProcState())); + proto.write(ProcessOomProto.Detail.LAST_PSS, DebugUtils.sizeValueToString( + r.mProfile.getLastPss() * 1024, new StringBuilder())); + proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString( + r.mProfile.getLastSwapPss() * 1024, new StringBuilder())); + // TODO(b/296454553): This proto field should be replaced with last cached RSS once + // AppProfiler is no longer collecting PSS. + proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString( + r.mProfile.getLastCachedPss() * 1024, new StringBuilder())); + proto.write(ProcessOomProto.Detail.CACHED, state.isCached()); + proto.write(ProcessOomProto.Detail.EMPTY, state.isEmpty()); + proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, psr.hasAboveClient()); + + if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_SERVICE) { + long lastCpuTime = r.mProfile.mLastCpuTime.get(); + long uptimeSince = curUptime - service.mLastPowerCheckUptime; + if (lastCpuTime != 0 && uptimeSince > 0) { + long timeUsed = r.mProfile.mCurCpuTime.get() - lastCpuTime; + long cpuTimeToken = proto.start(ProcessOomProto.Detail.SERVICE_RUN_TIME); + proto.write(ProcessOomProto.Detail.CpuRunTime.OVER_MS, uptimeSince); + proto.write(ProcessOomProto.Detail.CpuRunTime.USED_MS, timeUsed); + proto.write(ProcessOomProto.Detail.CpuRunTime.ULTILIZATION, + (100.0 * timeUsed) / uptimeSince); + proto.end(cpuTimeToken); + } + } + proto.end(detailToken); + } + proto.end(token); + } + + return true; + } + + private static boolean dumpProcessOomList(PrintWriter pw, + ActivityManagerService service, List origList, + String prefix, String normalLabel, String persistentLabel, + boolean inclDetails, String dumpPackage) { + + ArrayList> list = sortProcessOomList(origList, dumpPackage); + if (list.isEmpty()) return false; + + final long curUptime = SystemClock.uptimeMillis(); + final long uptimeSince = curUptime - service.mLastPowerCheckUptime; + + for (int i = list.size() - 1; i >= 0; i--) { + ProcessRecord r = list.get(i).first; + final ProcessStateRecord state = r.mState; + final ProcessServiceRecord psr = r.mServices; + String oomAdj = makeOomAdjString(state.getSetAdj(), false); + char schedGroup; + switch (state.getSetSchedGroup()) { + case SCHED_GROUP_BACKGROUND: + schedGroup = 'b'; + break; + case SCHED_GROUP_DEFAULT: + schedGroup = 'F'; + break; + case SCHED_GROUP_TOP_APP: + schedGroup = 'T'; + break; + case SCHED_GROUP_RESTRICTED: + schedGroup = 'R'; + break; + case SCHED_GROUP_TOP_APP_BOUND: + schedGroup = 'B'; + break; + default: + schedGroup = '?'; + break; + } + char foreground; + if (state.hasForegroundActivities()) { + foreground = 'A'; + } else if (psr.hasForegroundServices()) { + foreground = 'S'; + } else { + foreground = ' '; + } + String procState = makeProcStateString(state.getCurProcState()); + pw.print(prefix); + pw.print(r.isPersistent() ? persistentLabel : normalLabel); + pw.print(" #"); + int num = (origList.size() - 1) - list.get(i).second; + if (num < 10) pw.print(' '); + pw.print(num); + pw.print(": "); + pw.print(oomAdj); + pw.print(' '); + pw.print(schedGroup); + pw.print('/'); + pw.print(foreground); + pw.print('/'); + pw.print(procState); + pw.print(' '); + ActivityManager.printCapabilitiesSummary(pw, state.getCurCapability()); + pw.print(' '); + pw.print(" t:"); + if (r.mProfile.getTrimMemoryLevel() < 10) pw.print(' '); + pw.print(r.mProfile.getTrimMemoryLevel()); + pw.print(' '); + pw.print(r.toShortString()); + pw.print(" ("); + pw.print(state.getAdjType()); + pw.println(')'); + if (state.getAdjSource() != null || state.getAdjTarget() != null) { + pw.print(prefix); + pw.print(" "); + if (state.getAdjTarget() instanceof ComponentName) { + pw.print(((ComponentName) state.getAdjTarget()).flattenToShortString()); + } else if (state.getAdjTarget() != null) { + pw.print(state.getAdjTarget().toString()); + } else { + pw.print("{null}"); + } + pw.print("<="); + if (state.getAdjSource() instanceof ProcessRecord) { + pw.print("Proc{"); + pw.print(((ProcessRecord) state.getAdjSource()).toShortString()); + pw.println("}"); + } else if (state.getAdjSource() != null) { + pw.println(state.getAdjSource().toString()); + } else { + pw.println("{null}"); + } + } + if (inclDetails) { + pw.print(prefix); + pw.print(" "); + pw.print("oom: max="); pw.print(state.getMaxAdj()); + pw.print(" curRaw="); pw.print(state.getCurRawAdj()); + pw.print(" setRaw="); pw.print(state.getSetRawAdj()); + pw.print(" cur="); pw.print(state.getCurAdj()); + pw.print(" set="); pw.println(state.getSetAdj()); + pw.print(prefix); + pw.print(" "); + pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState())); + pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState())); + // These values won't be collected if the flag is enabled. + if (service.mAppProfiler.isProfilingPss()) { + pw.print(" lastPss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024); + pw.print(" lastSwapPss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastSwapPss() * 1024); + pw.print(" lastCachedPss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedPss() * 1024); + } else { + pw.print(" lastRss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastRss() * 1024); + pw.print(" lastCachedRss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedRss() * 1024); + } + pw.println(); + pw.print(prefix); + pw.print(" "); + pw.print("cached="); pw.print(state.isCached()); + pw.print(" empty="); pw.print(state.isEmpty()); + pw.print(" hasAboveClient="); pw.println(psr.hasAboveClient()); + + if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_SERVICE) { + long lastCpuTime = r.mProfile.mLastCpuTime.get(); + if (lastCpuTime != 0 && uptimeSince > 0) { + long timeUsed = r.mProfile.mCurCpuTime.get() - lastCpuTime; + pw.print(prefix); + pw.print(" "); + pw.print("run cpu over "); + TimeUtils.formatDuration(uptimeSince, pw); + pw.print(" used "); + TimeUtils.formatDuration(timeUsed, pw); + pw.print(" ("); + pw.print((timeUsed * 100) / uptimeSince); + pw.println("%)"); + } + } + } + } + return true; + } + + private void printOomLevel(PrintWriter pw, String name, int adj) { + pw.print(" "); + if (adj >= 0) { + pw.print(' '); + if (adj < 10) pw.print(' '); + } else { + if (adj > -10) pw.print(' '); + } + pw.print(adj); + pw.print(": "); + pw.print(name); + pw.print(" ("); + pw.print(ActivityManagerService.stringifySize(getMemLevel(adj), 1024)); + pw.println(")"); + } + + @GuardedBy("mService") + boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String[] args, + int opti, boolean dumpAll, String dumpPackage, boolean inclGc) { + if (getLruSizeLOSP() > 0) { + if (needSep) pw.println(); + needSep = true; + pw.println(" OOM levels:"); + printOomLevel(pw, "SYSTEM_ADJ", SYSTEM_ADJ); + printOomLevel(pw, "PERSISTENT_PROC_ADJ", PERSISTENT_PROC_ADJ); + printOomLevel(pw, "PERSISTENT_SERVICE_ADJ", PERSISTENT_SERVICE_ADJ); + printOomLevel(pw, "FOREGROUND_APP_ADJ", FOREGROUND_APP_ADJ); + printOomLevel(pw, "VISIBLE_APP_ADJ", VISIBLE_APP_ADJ); + printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", PERCEPTIBLE_APP_ADJ); + printOomLevel(pw, "PERCEPTIBLE_MEDIUM_APP_ADJ", PERCEPTIBLE_MEDIUM_APP_ADJ); + printOomLevel(pw, "PERCEPTIBLE_LOW_APP_ADJ", PERCEPTIBLE_LOW_APP_ADJ); + printOomLevel(pw, "BACKUP_APP_ADJ", BACKUP_APP_ADJ); + printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", HEAVY_WEIGHT_APP_ADJ); + printOomLevel(pw, "SERVICE_ADJ", SERVICE_ADJ); + printOomLevel(pw, "HOME_APP_ADJ", HOME_APP_ADJ); + printOomLevel(pw, "PREVIOUS_APP_ADJ", PREVIOUS_APP_ADJ); + printOomLevel(pw, "SERVICE_B_ADJ", SERVICE_B_ADJ); + printOomLevel(pw, "CACHED_APP_MIN_ADJ", CACHED_APP_MIN_ADJ); + printOomLevel(pw, "CACHED_APP_MAX_ADJ", CACHED_APP_MAX_ADJ); + + if (needSep) pw.println(); + pw.print(" Process OOM control ("); pw.print(getLruSizeLOSP()); + pw.print(" total, non-act at "); + pw.print(getLruSizeLOSP() - mLruProcessActivityStart); + pw.print(", non-svc at "); + pw.print(getLruSizeLOSP() - mLruProcessServiceStart); + pw.println("):"); + dumpProcessOomList(pw, mService, mLruProcesses, + " ", "Proc", "PERS", true, dumpPackage); + needSep = true; + } + + synchronized (mService.mAppProfiler.mProfilerLock) { + mService.mAppProfiler.dumpProcessesToGc(pw, needSep, dumpPackage); + } + + pw.println(); + mService.mAtmInternal.dumpForOom(pw); + + return true; + } + + void registerProcessObserver(IProcessObserver observer) { + mProcessObservers.register(observer); + } + + void unregisterProcessObserver(IProcessObserver observer) { + mProcessObservers.unregister(observer); + } + + void dispatchProcessesChanged() { + int numOfChanges; + synchronized (mProcessChangeLock) { + numOfChanges = mPendingProcessChanges.size(); + if (mActiveProcessChanges.length < numOfChanges) { + mActiveProcessChanges = new ProcessChangeItem[numOfChanges]; + } + mPendingProcessChanges.toArray(mActiveProcessChanges); + mPendingProcessChanges.clear(); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, + "*** Delivering " + numOfChanges + " process changes"); + } + } + + int i = mProcessObservers.beginBroadcast(); + while (i > 0) { + i--; + final IProcessObserver observer = mProcessObservers.getBroadcastItem(i); + if (observer != null) { + try { + for (int j = 0; j < numOfChanges; j++) { + ProcessChangeItem item = mActiveProcessChanges[j]; + if ((item.changes & ProcessChangeItem.CHANGE_ACTIVITIES) != 0) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, + "ACTIVITIES CHANGED pid=" + item.pid + " uid=" + + item.uid + ": " + item.foregroundActivities); + } + observer.onForegroundActivitiesChanged(item.pid, item.uid, + item.foregroundActivities); + } + if ((item.changes & ProcessChangeItem.CHANGE_FOREGROUND_SERVICES) != 0) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, + "FOREGROUND SERVICES CHANGED pid=" + item.pid + " uid=" + + item.uid + ": " + item.foregroundServiceTypes); + } + observer.onForegroundServicesChanged(item.pid, item.uid, + item.foregroundServiceTypes); + } + } + } catch (RemoteException e) { + } + } + } + mProcessObservers.finishBroadcast(); + + synchronized (mProcessChangeLock) { + for (int j = 0; j < numOfChanges; j++) { + mAvailProcessChanges.add(mActiveProcessChanges[j]); + } + } + } + + @GuardedBy("mService") + ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) { + synchronized (mProcessChangeLock) { + int i = mPendingProcessChanges.size() - 1; + ActivityManagerService.ProcessChangeItem item = null; + while (i >= 0) { + item = mPendingProcessChanges.get(i); + if (item.pid == pid) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item); + } + break; + } + i--; + } + + if (i < 0) { + // No existing item in pending changes; need a new one. + final int num = mAvailProcessChanges.size(); + if (num > 0) { + item = mAvailProcessChanges.remove(num - 1); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item); + } + } else { + item = new ActivityManagerService.ProcessChangeItem(); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item); + } + } + item.changes = 0; + item.pid = pid; + item.uid = uid; + if (mPendingProcessChanges.size() == 0) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!"); + } + mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG) + .sendToTarget(); + } + mPendingProcessChanges.add(item); + } + + return item; + } + } + + @GuardedBy("mService") + void scheduleDispatchProcessDiedLocked(int pid, int uid) { + synchronized (mProcessChangeLock) { + for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) { + ProcessChangeItem item = mPendingProcessChanges.get(i); + if (pid > 0 && item.pid == pid) { + mPendingProcessChanges.remove(i); + mAvailProcessChanges.add(item); + } + } + mService.mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, pid, uid, + null).sendToTarget(); + } + } + + void dispatchProcessStarted(ProcessRecord app, int pid) { + // TODO(b/323959187) Add the implementation. + } + + void dispatchProcessDied(int pid, int uid) { + int i = mProcessObservers.beginBroadcast(); + while (i > 0) { + i--; + final IProcessObserver observer = mProcessObservers.getBroadcastItem(i); + if (observer != null) { + try { + observer.onProcessDied(pid, uid); + } catch (RemoteException e) { + } + } + } + mProcessObservers.finishBroadcast(); + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + ArrayList collectProcessesLOSP(int start, boolean allPkgs, String[] args) { + ArrayList procs; + if (args != null && args.length > start + && args[start].charAt(0) != '-') { + procs = new ArrayList(); + int pid = -1; + try { + pid = Integer.parseInt(args[start]); + } catch (NumberFormatException e) { + } + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord proc = mLruProcesses.get(i); + if (proc.getPid() > 0 && proc.getPid() == pid) { + procs.add(proc); + } else if (allPkgs && proc.getPkgList() != null + && proc.getPkgList().containsKey(args[start])) { + procs.add(proc); + } else if (proc.processName.equals(args[start])) { + procs.add(proc); + } + } + if (procs.size() <= 0) { + return null; + } + } else { + procs = new ArrayList(mLruProcesses); + } + return procs; + } + + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void updateApplicationInfoLOSP(List packagesToUpdate, int userId, + boolean updateFrameworkRes) { + final ArrayMap applicationInfoByPackage = new ArrayMap<>(); + for (int i = packagesToUpdate.size() - 1; i >= 0; i--) { + final String packageName = packagesToUpdate.get(i); + final ApplicationInfo ai = mService.getPackageManagerInternal().getApplicationInfo( + packageName, STOCK_PM_FLAGS, Process.SYSTEM_UID, userId); + if (ai != null) { + applicationInfoByPackage.put(packageName, ai); + } + } + mService.mActivityTaskManager.updateActivityApplicationInfo(userId, + applicationInfoByPackage); + + final ArrayList targetProcesses = new ArrayList<>(); + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + final ProcessRecord app = mLruProcesses.get(i); + if (app.getThread() == null) { + continue; + } + + if (userId != UserHandle.USER_ALL && app.userId != userId) { + continue; + } + + app.getPkgList().forEachPackage(packageName -> { + if (updateFrameworkRes || packagesToUpdate.contains(packageName)) { + try { + final ApplicationInfo ai = applicationInfoByPackage.get(packageName); + if (ai != null) { + if (ai.packageName.equals(app.info.packageName)) { + app.info = ai; + PlatformCompatCache.getInstance() + .onApplicationInfoChanged(ai); + } + app.getThread().scheduleApplicationInfoChanged(ai); + targetProcesses.add(app.getWindowProcessController()); + } + } catch (RemoteException e) { + Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s", + packageName, app)); + } + } + }); + } + + mService.mActivityTaskManager.updateAssetConfiguration(targetProcesses, updateFrameworkRes); + } + + @GuardedBy("mService") + void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) { + boolean foundProcess = false; + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord r = mLruProcesses.get(i); + final IApplicationThread thread = r.getThread(); + if (thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) { + try { + for (int index = packages.length - 1; index >= 0 && !foundProcess; index--) { + if (packages[index].equals(r.info.packageName)) { + foundProcess = true; + } + } + thread.dispatchPackageBroadcast(cmd, packages); + } catch (RemoteException ex) { + } + } + } + + if (!foundProcess) { + try { + AppGlobals.getPackageManager().notifyPackagesReplacedReceived(packages); + } catch (RemoteException ignored) { + } + } + } + + /** + * Returns the uid's process state or {@link ActivityManager#PROCESS_STATE_NONEXISTENT} + * if not running + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + int getUidProcStateLOSP(int uid) { + UidRecord uidRec = mActiveUids.get(uid); + return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState(); + } + + /** + * Returns the uid's process capability or {@link ActivityManager#PROCESS_CAPABILITY_NONE} + * if not running + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + @ProcessCapability int getUidProcessCapabilityLOSP(int uid) { + UidRecord uidRec = mActiveUids.get(uid); + return uidRec == null ? PROCESS_CAPABILITY_NONE : uidRec.getCurCapability(); + } + + /** Returns the UidRecord for the given uid, if it exists. */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + UidRecord getUidRecordLOSP(int uid) { + return mActiveUids.get(uid); + } + + /** + * Call {@link ActivityManagerService#doStopUidLocked} + * (which will also stop background services) for all idle UIDs. + */ + @GuardedBy("mService") + void doStopUidForIdleUidsLocked() { + final int size = mActiveUids.size(); + for (int i = 0; i < size; i++) { + final int uid = mActiveUids.keyAt(i); + if (UserHandle.isCore(uid)) { + continue; + } + final UidRecord uidRec = mActiveUids.valueAt(i); + if (!uidRec.isIdle()) { + continue; + } + mService.doStopUidLocked(uidRec.getUid(), uidRec); + } + } + + /** + * Checks if the uid is coming from background to foreground or vice versa and returns + * appropriate block state based on this. + * + * @return blockState based on whether the uid is coming from background to foreground or + * vice versa. If bg->fg or fg->bg, then {@link #NETWORK_STATE_BLOCK} or + * {@link #NETWORK_STATE_UNBLOCK} respectively, otherwise + * {@link #NETWORK_STATE_NO_CHANGE}. + */ + @VisibleForTesting + @GuardedBy(anyOf = {"mService", "mProcLock"}) + int getBlockStateForUid(UidRecord uidRec) { + // Denotes whether uid's process state is currently allowed network access. + final boolean isAllowed = + isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState(), + uidRec.getCurCapability()) + || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState(), + uidRec.getCurCapability()); + // Denotes whether uid's process state was previously allowed network access. + final boolean wasAllowed = + isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState(), + uidRec.getSetCapability()) + || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState(), + uidRec.getSetCapability()); + + // When the uid is coming to foreground, AMS should inform the app thread that it should + // block for the network rules to get updated before launching an activity. + if (!wasAllowed && isAllowed) { + return NETWORK_STATE_BLOCK; + } + // When the uid is going to background, AMS should inform the app thread that if an + // activity launch is blocked for the network rules to get updated, it should be unblocked. + if (wasAllowed && !isAllowed) { + return NETWORK_STATE_UNBLOCK; + } + return NETWORK_STATE_NO_CHANGE; + } + + /** + * Increments the {@link UidRecord#curProcStateSeq} for all uids using global seq counter + * {@link ProcessList#mProcStateSeqCounter} and checks if any uid is coming + * from background to foreground or vice versa and if so, notifies the app if it needs to block. + */ + @VisibleForTesting + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void incrementProcStateSeqAndNotifyAppsLOSP(ActiveUids activeUids) { + for (int i = activeUids.size() - 1; i >= 0; --i) { + final UidRecord uidRec = activeUids.valueAt(i); + uidRec.curProcStateSeq = getNextProcStateSeq(); + } + if (mService.mConstants.mNetworkAccessTimeoutMs <= 0) { + return; + } + // Used for identifying which uids need to block for network. + ArrayList blockingUids = null; + for (int i = activeUids.size() - 1; i >= 0; --i) { + final UidRecord uidRec = activeUids.valueAt(i); + // If the network is not restricted for uid, then nothing to do here. + if (!mService.mInjector.isNetworkRestrictedForUid(uidRec.getUid())) { + continue; + } + if (!UserHandle.isApp(uidRec.getUid()) || !uidRec.hasInternetPermission) { + continue; + } + // If process state and capabilities are not changed, then there's nothing to do. + if (uidRec.getSetProcState() == uidRec.getCurProcState() + && uidRec.getSetCapability() == uidRec.getCurCapability()) { + continue; + } + final int blockState = getBlockStateForUid(uidRec); + // No need to inform the app when the blockState is NETWORK_STATE_NO_CHANGE as + // there's nothing the app needs to do in this scenario. + if (blockState == NETWORK_STATE_NO_CHANGE) { + continue; + } + synchronized (uidRec.networkStateLock) { + if (blockState == NETWORK_STATE_BLOCK) { + if (blockingUids == null) { + blockingUids = new ArrayList<>(); + } + blockingUids.add(uidRec.getUid()); + } else { + if (DEBUG_NETWORK) { + Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking" + + " threads for uid: " + uidRec); + } + if (uidRec.procStateSeqWaitingForNetwork != 0) { + uidRec.networkStateLock.notifyAll(); + } + } + } + } + + // There are no uids that need to block, so nothing more to do. + if (blockingUids == null) { + return; + } + + for (int i = mLruProcesses.size() - 1; i >= 0; --i) { + final ProcessRecord app = mLruProcesses.get(i); + if (!blockingUids.contains(app.uid)) { + continue; + } + final IApplicationThread thread = app.getThread(); + if (!app.isKilledByAm() && thread != null) { + final UidRecord uidRec = getUidRecordLOSP(app.uid); + try { + if (DEBUG_NETWORK) { + Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: " + + uidRec); + } + if (uidRec != null) { + thread.setNetworkBlockSeq(uidRec.curProcStateSeq); + } + } catch (RemoteException ignored) { + } + } + } + } + + long getNextProcStateSeq() { + return ++mProcStateSeqCounter; + } + + /** + * Create a server socket in system_server, zygote will connect to it + * in order to send unsolicited messages to system_server. + */ + private LocalSocket createSystemServerSocketForZygote() { + // The file system entity for this socket is created with 0666 perms, owned + // by system:system. selinux restricts things so that only zygotes can + // access it. + final File socketFile = new File(UNSOL_ZYGOTE_MSG_SOCKET_PATH); + if (socketFile.exists()) { + socketFile.delete(); + } + + LocalSocket serverSocket = null; + try { + serverSocket = new LocalSocket(LocalSocket.SOCKET_DGRAM); + serverSocket.bind(new LocalSocketAddress( + UNSOL_ZYGOTE_MSG_SOCKET_PATH, LocalSocketAddress.Namespace.FILESYSTEM)); + Os.chmod(UNSOL_ZYGOTE_MSG_SOCKET_PATH, 0666); + } catch (Exception e) { + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException ex) { + } + serverSocket = null; + } + } + return serverSocket; + } + + /** + * Handle the unsolicited message from zygote. + */ + private int handleZygoteMessages(FileDescriptor fd, int events) { + final int eventFd = fd.getInt$(); + if ((events & EVENT_INPUT) != 0) { + // An incoming message from zygote + try { + final int len = Os.read(fd, mZygoteUnsolicitedMessage, 0, + mZygoteUnsolicitedMessage.length); + if (len > 0 && mZygoteSigChldMessage.length == Zygote.nativeParseSigChld( + mZygoteUnsolicitedMessage, len, mZygoteSigChldMessage)) { + mAppExitInfoTracker.handleZygoteSigChld( + mZygoteSigChldMessage[0] /* pid */, + mZygoteSigChldMessage[1] /* uid */, + mZygoteSigChldMessage[2] /* status */); + } + } catch (Exception e) { + Slog.w(TAG, "Exception in reading unsolicited zygote message: " + e); + } + } + return EVENT_INPUT; + } + + /** + * Handle the death notification if it's a dying app. + * + * @return {@code true} if it's a dying app that we were tracking. + */ + @GuardedBy("mService") + boolean handleDyingAppDeathLocked(ProcessRecord app, int pid) { + if (mProcessNames.get(app.processName, app.uid) != app + && mDyingProcesses.get(app.processName, app.uid) == app) { + // App has been removed already, meaning cleanup has done. + Slog.v(TAG, "Got obituary of " + pid + ":" + app.processName); + app.unlinkDeathRecipient(); + // It's really gone now, let's remove from the dying process list. + mDyingProcesses.remove(app.processName, app.uid); + app.setDyingPid(0); + handlePrecedingAppDiedLocked(app); + // Remove from the LRU list if it's still there. + removeLruProcessLocked(app); + return true; + } + return false; + } + + /** + * Handle the case where the given app is a preceding instance of another process instance. + * + * @return {@code false} if this given app should not be allowed to restart. + */ + @GuardedBy("mService") + boolean handlePrecedingAppDiedLocked(ProcessRecord app) { + if (app.mSuccessor != null) { + // We don't allow restart with this ProcessRecord now, + // because we have created a new one already. + // If it's persistent, add the successor to mPersistentStartingProcesses + if (app.isPersistent() && !app.isRemoved()) { + if (mService.mPersistentStartingProcesses.indexOf(app.mSuccessor) < 0) { + mService.mPersistentStartingProcesses.add(app.mSuccessor); + } + } + // clean up the field so the successor's proc starter could proceed. + app.mSuccessor.mPredecessor = null; + app.mSuccessor = null; + // Remove any pending timeout msg. + mService.mProcStartHandler.removeMessages( + ProcStartHandler.MSG_PROCESS_KILL_TIMEOUT, app); + // Kick off the proc start for the succeeding instance + mService.mProcStartHandler.obtainMessage( + ProcStartHandler.MSG_PROCESS_DIED, app).sendToTarget(); + return false; + } + return true; + } + + @GuardedBy("mService") + void updateBackgroundRestrictedForUidPackageLocked(int uid, String packageName, + boolean restricted) { + final UidRecord uidRec = getUidRecordLOSP(uid); + if (uidRec != null) { + final long nowElapsed = SystemClock.elapsedRealtime(); + uidRec.forEachProcess(app -> { + if (TextUtils.equals(app.info.packageName, packageName)) { + app.mState.setBackgroundRestricted(restricted); + if (restricted) { + mAppsInBackgroundRestricted.add(app); + final long future = killAppIfBgRestrictedAndCachedIdleLocked( + app, nowElapsed); + if (future > 0 + && (mService.mDeterministicUidIdle + || !mService.mHandler.hasMessages(IDLE_UIDS_MSG))) { + mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, + future - nowElapsed); + } + } else { + mAppsInBackgroundRestricted.remove(app); + } + if (!app.isKilledByAm()) { + mService.enqueueOomAdjTargetLocked(app); + } + } + }); + /* Will be a no-op if nothing pending */ + mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE); + } + } + + /** + * Kill the given app if it's in cached idle and background restricted mode. + * + * @return A future timestamp when the app should be killed at, or a 0 if it shouldn't + * be killed or it has been killed. + */ + @GuardedBy("mService") + long killAppIfBgRestrictedAndCachedIdleLocked(ProcessRecord app, long nowElapsed) { + final UidRecord uidRec = app.getUidRecord(); + final long lastCanKillTime = app.mState.getLastCanKillOnBgRestrictedAndIdleTime(); + if (!mService.mConstants.mKillBgRestrictedAndCachedIdle + || app.isKilled() || app.getThread() == null || uidRec == null || !uidRec.isIdle() + || !app.isCached() || app.mState.shouldNotKillOnBgRestrictedAndIdle() + || !app.mState.isBackgroundRestricted() || lastCanKillTime == 0) { + return 0; + } + final long future = lastCanKillTime + + mService.mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs; + if (future <= nowElapsed) { + app.killLocked("cached idle & background restricted", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_CACHED_IDLE_FORCED_APP_STANDBY, + true); + return 0; + } + return future; + } + + /** + * Called by {@link ActivityManagerService#enqueueUidChangeLocked} only, it doesn't schedule + * the standy killing checks because it should have been scheduled before enqueueing UID idle + * changed. + */ + @GuardedBy("mService") + void killAppIfBgRestrictedAndCachedIdleLocked(UidRecord uidRec) { + final long nowElapsed = SystemClock.elapsedRealtime(); + uidRec.forEachProcess(app -> killAppIfBgRestrictedAndCachedIdleLocked(app, nowElapsed)); + } + + /** + * Called by ActivityManagerService when a process died. + */ + @GuardedBy("mService") + void noteProcessDiedLocked(final ProcessRecord app) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, "note: " + app + " died, saving the exit info"); + } + + Watchdog.getInstance().processDied(app.processName, app.getPid()); + if (app.getDeathRecipient() == null + && mDyingProcesses.get(app.processName, app.uid) == app) { + // If we've done unlinkDeathRecipient before calling into this, remove from dying list. + mDyingProcesses.remove(app.processName, app.uid); + app.setDyingPid(0); + } + mAppExitInfoTracker.scheduleNoteProcessDied(app); + } + + /** + * Called by ActivityManagerService when a recoverable native crash occurs. + */ + @GuardedBy("mService") + void noteAppRecoverableCrash(final ProcessRecord app) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, "note: " + app + " has a recoverable native crash"); + } + mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app); + } + + /** + * Called by ActivityManagerService when it decides to kill an application process. + */ + @GuardedBy("mService") + void noteAppKill(final ProcessRecord app, final @Reason int reason, + final @SubReason int subReason, final String msg) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, "note: " + app + " is being killed, reason: " + reason + + ", sub-reason: " + subReason + ", message: " + msg); + } + if (app.getPid() > 0 && !app.isolated && app.getDeathRecipient() != null) { + // We are killing it, put it into the dying process list. + mDyingProcesses.put(app.processName, app.uid, app); + app.setDyingPid(app.getPid()); + } + mAppExitInfoTracker.scheduleNoteAppKill(app, reason, subReason, msg); + } + + @GuardedBy("mService") + void noteAppKill(final int pid, final int uid, final @Reason int reason, + final @SubReason int subReason, final String msg) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, "note: " + pid + " is being killed, reason: " + reason + + ", sub-reason: " + subReason + ", message: " + msg); + } + + final ProcessRecord app; + synchronized (mService.mPidsSelfLocked) { + app = mService.mPidsSelfLocked.get(pid); + } + if (app != null && app.uid == uid && !app.isolated && app.getDeathRecipient() != null) { + // We are killing it, put it into the dying process list. + mDyingProcesses.put(app.processName, uid, app); + app.setDyingPid(app.getPid()); + } + mAppExitInfoTracker.scheduleNoteAppKill(pid, uid, reason, subReason, msg); + } + + /** + * Schedule to kill the given pids when the device is idle + */ + void killProcessesWhenImperceptible(int[] pids, String reason, int requester) { + if (ArrayUtils.isEmpty(pids)) { + return; + } + + synchronized (mService) { + ProcessRecord app; + for (int i = 0; i < pids.length; i++) { + synchronized (mService.mPidsSelfLocked) { + app = mService.mPidsSelfLocked.get(pids[i]); + } + if (app != null) { + mImperceptibleKillRunner.enqueueLocked(app, reason, requester); + } + } + } + } + + /** + * Get the number of foreground services in all processes and number of processes that have + * foreground service within. + */ + Pair getNumForegroundServices() { + int numForegroundServices = 0; + int procs = 0; + synchronized (mService) { + for (int i = 0, size = mLruProcesses.size(); i < size; i++) { + ProcessRecord pr = mLruProcesses.get(i); + int numFgs = pr.mServices.getNumForegroundServices(); + if (numFgs > 0) { + numForegroundServices += numFgs; + procs++; + } + } + } + return new Pair<>(numForegroundServices, procs); + } + + private final class ImperceptibleKillRunner extends UidObserver { + private static final String EXTRA_PID = "pid"; + private static final String EXTRA_UID = "uid"; + private static final String EXTRA_TIMESTAMP = "timestamp"; + private static final String EXTRA_REASON = "reason"; + private static final String EXTRA_REQUESTER = "requester"; + + private static final String DROPBOX_TAG_IMPERCEPTIBLE_KILL = "imperceptible_app_kill"; + private static final boolean LOG_TO_DROPBOX = false; + + // uid -> killing information mapping + private SparseArray> mWorkItems = new SparseArray>(); + + // The last time the various processes have been killed by us. + private ProcessMap mLastProcessKillTimes = new ProcessMap<>(); + + // Device idle or not. + private volatile boolean mIdle; + private boolean mUidObserverEnabled; + private Handler mHandler; + private IdlenessReceiver mReceiver; + + private final class H extends Handler { + static final int MSG_DEVICE_IDLE = 0; + static final int MSG_UID_GONE = 1; + static final int MSG_UID_STATE_CHANGED = 2; + + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DEVICE_IDLE: + handleDeviceIdle(); + break; + case MSG_UID_GONE: + handleUidGone(msg.arg1 /* uid */); + break; + case MSG_UID_STATE_CHANGED: + handleUidStateChanged(msg.arg1 /* uid */, msg.arg2 /* procState */); + break; + } + } + } + + private final class IdlenessReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final PowerManager pm = mService.mContext.getSystemService(PowerManager.class); + switch (intent.getAction()) { + case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED: + notifyDeviceIdleness(pm.isLightDeviceIdleMode()); + break; + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + notifyDeviceIdleness(pm.isDeviceIdleMode()); + break; + } + } + } + + ImperceptibleKillRunner(Looper looper) { + mHandler = new H(looper); + } + + @GuardedBy("mService") + boolean enqueueLocked(ProcessRecord app, String reason, int requester) { + // Throttle the killing request for potential bad app to avoid cpu thrashing + Long last = app.isolated ? null : mLastProcessKillTimes.get(app.processName, app.uid); + if ((last != null) && (SystemClock.uptimeMillis() + < (last + ActivityManagerConstants.MIN_CRASH_INTERVAL))) { + return false; + } + + final Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_PID, app.getPid()); + bundle.putInt(EXTRA_UID, app.uid); + // Since the pid could be reused, let's get the actual start time of each process + bundle.putLong(EXTRA_TIMESTAMP, app.getStartTime()); + bundle.putString(EXTRA_REASON, reason); + bundle.putInt(EXTRA_REQUESTER, requester); + List list = mWorkItems.get(app.uid); + if (list == null) { + list = new ArrayList(); + mWorkItems.put(app.uid, list); + } + list.add(bundle); + if (mReceiver == null) { + mReceiver = new IdlenessReceiver(); + IntentFilter filter = new IntentFilter( + PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); + filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + mService.mContext.registerReceiver(mReceiver, filter); + } + return true; + } + + void notifyDeviceIdleness(boolean idle) { + // No lock is held regarding mIdle, this function is the only updater and caller + // won't re-entry. + boolean diff = mIdle != idle; + mIdle = idle; + if (diff && idle) { + synchronized (mService) { + if (mWorkItems.size() > 0) { + mHandler.sendEmptyMessage(H.MSG_DEVICE_IDLE); + } + } + } + } + + private void handleDeviceIdle() { + final DropBoxManager dbox = mService.mContext.getSystemService(DropBoxManager.class); + final boolean logToDropbox = LOG_TO_DROPBOX && dbox != null + && dbox.isTagEnabled(DROPBOX_TAG_IMPERCEPTIBLE_KILL); + + synchronized (mService) { + final int size = mWorkItems.size(); + for (int i = size - 1; mIdle && i >= 0; i--) { + List list = mWorkItems.valueAt(i); + final int len = list.size(); + for (int j = len - 1; mIdle && j >= 0; j--) { + Bundle bundle = list.get(j); + if (killProcessLocked( + bundle.getInt(EXTRA_PID), + bundle.getInt(EXTRA_UID), + bundle.getLong(EXTRA_TIMESTAMP), + bundle.getString(EXTRA_REASON), + bundle.getInt(EXTRA_REQUESTER), + dbox, logToDropbox)) { + list.remove(j); + } + } + if (list.size() == 0) { + mWorkItems.removeAt(i); + } + } + registerUidObserverIfNecessaryLocked(); + } + } + + @GuardedBy("mService") + private void registerUidObserverIfNecessaryLocked() { + // If there are still works remaining, register UID observer + if (!mUidObserverEnabled && mWorkItems.size() > 0) { + mUidObserverEnabled = true; + mService.registerUidObserver(this, + ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, "android"); + } else if (mUidObserverEnabled && mWorkItems.size() == 0) { + mUidObserverEnabled = false; + mService.unregisterUidObserver(this); + } + } + + /** + * Kill the given processes, if they are not exempted. + * + * @return True if the process is killed, or it's gone already, or we are not allowed to + * kill it (one of the packages in this process is being exempted). + */ + @GuardedBy("mService") + private boolean killProcessLocked(final int pid, final int uid, final long timestamp, + final String reason, final int requester, final DropBoxManager dbox, + final boolean logToDropbox) { + ProcessRecord app = null; + synchronized (mService.mPidsSelfLocked) { + app = mService.mPidsSelfLocked.get(pid); + } + + if (app == null || app.getPid() != pid || app.uid != uid + || app.getStartTime() != timestamp) { + // This process record has been reused for another process, meaning the old process + // has been gone. + return true; + } + + if (app.getPkgList().searchEachPackage(pkgName -> { + if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.contains(pkgName)) { + // One of the packages in this process is exempted + return Boolean.TRUE; + } + return null; + }) != null) { + return true; + } + + if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.contains( + app.mState.getReportedProcState())) { + // We need to reschedule it. + return false; + } + + app.killLocked(reason, ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_IMPERCEPTIBLE, true); + + if (!app.isolated) { + mLastProcessKillTimes.put(app.processName, app.uid, SystemClock.uptimeMillis()); + } + + if (logToDropbox) { + final long now = SystemClock.elapsedRealtime(); + final StringBuilder sb = new StringBuilder(); + mService.appendDropBoxProcessHeaders(app, app.processName, null, sb); + sb.append("Reason: " + reason).append("\n"); + sb.append("Requester UID: " + requester).append("\n"); + dbox.addText(DROPBOX_TAG_IMPERCEPTIBLE_KILL, sb.toString()); + } + return true; + } + + private void handleUidStateChanged(int uid, int procState) { + final DropBoxManager dbox = mService.mContext.getSystemService(DropBoxManager.class); + final boolean logToDropbox = dbox != null + && dbox.isTagEnabled(DROPBOX_TAG_IMPERCEPTIBLE_KILL); + synchronized (mService) { + if (mIdle && !mService.mConstants + .IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.contains(procState)) { + List list = mWorkItems.get(uid); + if (list != null) { + final int len = list.size(); + for (int j = len - 1; mIdle && j >= 0; j--) { + Bundle bundle = list.get(j); + if (killProcessLocked( + bundle.getInt(EXTRA_PID), + bundle.getInt(EXTRA_UID), + bundle.getLong(EXTRA_TIMESTAMP), + bundle.getString(EXTRA_REASON), + bundle.getInt(EXTRA_REQUESTER), + dbox, logToDropbox)) { + list.remove(j); + } + } + if (list.size() == 0) { + mWorkItems.remove(uid); + } + registerUidObserverIfNecessaryLocked(); + } + } + } + } + + private void handleUidGone(int uid) { + synchronized (mService) { + mWorkItems.remove(uid); + registerUidObserverIfNecessaryLocked(); + } + } + + @Override + public void onUidGone(int uid, boolean disabled) { + mHandler.obtainMessage(H.MSG_UID_GONE, uid, 0).sendToTarget(); + } + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { + mHandler.obtainMessage(H.MSG_UID_STATE_CHANGED, uid, procState).sendToTarget(); + } + }; +} diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/RootManager.java b/aosp/frameworks/base/services/core/java/com/android/server/am/RootManager.java new file mode 100644 index 000000000..3d3c40e63 --- /dev/null +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/RootManager.java @@ -0,0 +1,78 @@ +/* ****************************************************************************** + * Copyright Notice: + * Copyright 2023-2025, Huawei Technologies Co., Ltd. ALL Rights Reserved. + * + * Warning: This computer software sourcecode is protected by copyright law + * and international treaties. Unauthorized reproduction or distribution + * of this sourcecode, or any portion of it, may result in severe civil and + * criminal penalties, and will be prosecuted to the maximum extent + * possible under the law. + * ******************************************************************************* + */ +package com.android.server.am; + +import android.app.SharedPreferencesHelper; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public class RootManager { + + private static final String WHITELIST_KEY = "packages"; + + private static final String WHITELIST_PATH_SYSTEM = "/system/etc/packages_priv.xml"; + private static final String WHITELIST_PATH_USER = "/data/system/packages_priv.xml"; + + private final SharedPreferencesHelper mConfigSystem; + private final SharedPreferencesHelper mConfigUser; + + private final Set mListSystem; + private final Set mListUser; + + public RootManager() { + mConfigSystem = new SharedPreferencesHelper(new File(WHITELIST_PATH_SYSTEM)); + mConfigUser = new SharedPreferencesHelper(new File(WHITELIST_PATH_USER)); + mListSystem = new HashSet<>(); + mListUser = new HashSet<>(); + } + + public void addToWhiteList(String packageName) { + mListUser.add(packageName); + mConfigUser.putStringSet(WHITELIST_KEY, mListUser); + } + + public void removeFromWhiteList(String packageName) { + mListUser.remove(packageName); + mConfigUser.putStringSet(WHITELIST_KEY, mListUser); + } + + public void clearWhiteList() { + mListUser.clear(); + mConfigUser.putStringSet(WHITELIST_KEY, mListUser); + } + + public void loadWhiteList() { + mListSystem.addAll(mConfigSystem.getStringSet(WHITELIST_KEY, new HashSet<>())); + mListUser.addAll(mConfigUser.getStringSet(WHITELIST_KEY, new HashSet<>())); + } + + public boolean isInWhiteList(String packageName) { + return mListSystem.contains(packageName) || mListUser.contains(packageName); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("System Root App List:").append("\n"); + for (String name : mListSystem) { + result.append(name).append("\n"); + } + result.append("\n"); + result.append("User Root App List:").append("\n"); + for (String name : mListUser) { + result.append(name).append("\n"); + } + return result.toString(); + } +} \ No newline at end of file -- Gitee From 4abdeb5ffb94125e41ad8cb09f71a4fb596fdfac Mon Sep 17 00:00:00 2001 From: roger2015 Date: Tue, 12 Aug 2025 10:33:50 +0800 Subject: [PATCH 3/3] modify files --- aosp/bionic/libc/include/paths.h | 2 +- .../java/android/app/IActivityManager.aidl | 9 +++ .../java/com/android/internal/os/Zygote.java | 2 + .../jni/com_android_internal_os_Zygote.cpp | 11 ++- aosp/frameworks/base/services/core/Android.bp | 1 + .../server/am/ActivityManagerService.java | 77 +++++++++++++++++++ .../am/ActivityManagerShellCommand.java | 12 +++ .../com/android/server/am/ProcessList.java | 6 ++ aosp/vendor/common/android/config/Android.bp | 25 ++++++ .../android/config/n/EnableRootManager.java | 20 +++++ .../android/config/y/EnableRootManager.java | 20 +++++ aosp/vendor/common/etc/packages_priv.xml | 5 ++ aosp/vendor/isula/common.mk | 1 + aosp/vendor/isula/config.mk | 8 ++ aosp/vendor/isula/copyfiles.mk | 1 + 15 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 aosp/vendor/common/android/config/Android.bp create mode 100644 aosp/vendor/common/android/config/n/EnableRootManager.java create mode 100644 aosp/vendor/common/android/config/y/EnableRootManager.java create mode 100644 aosp/vendor/common/etc/packages_priv.xml create mode 100644 aosp/vendor/isula/config.mk diff --git a/aosp/bionic/libc/include/paths.h b/aosp/bionic/libc/include/paths.h index cfbc5b3f3..c1ccb3fd1 100644 --- a/aosp/bionic/libc/include/paths.h +++ b/aosp/bionic/libc/include/paths.h @@ -47,7 +47,7 @@ #define _PATH_CONSOLE "/dev/console" /** Default shell search path. */ -#define _PATH_DEFPATH "/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin" +#define _PATH_DEFPATH "/sbin:/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin" /** Path to the directory containing device files. */ #define _PATH_DEV "/dev/" diff --git a/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl b/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl index 7a95720c1..d887f3a38 100644 --- a/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl +++ b/aosp/frameworks/base/core/java/android/app/IActivityManager.aidl @@ -966,4 +966,13 @@ interface IActivityManager { */ oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err); int getBindingUidProcessState(int uid, in String callingPackage); + + /** + * su whitelist + */ + void addPackageNameToSuWhiteList(String packageName); + void removePackageNameFromSuWhiteList(String packageName); + void clearSuWhiteList(); + String getAllPackageNamesOfSuWhiteList(); + boolean isInWhiteList(String packageName); } diff --git a/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java b/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java index cab84bb01..53e6b9647 100644 --- a/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java +++ b/aosp/frameworks/base/core/java/com/android/internal/os/Zygote.java @@ -201,6 +201,8 @@ public final class Zygote { */ public static final int DEBUG_ENABLE_PTRACE = 1 << 25; + public static final int SU_HIDE = 1 << 30; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ diff --git a/aosp/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp b/aosp/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp index d68c7bb47..a03a9d466 100644 --- a/aosp/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp +++ b/aosp/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp @@ -838,7 +838,7 @@ static void MountAppDataTmpFs(const std::string& target_dir, // storage for the given user. static void MountEmulatedStorage(uid_t uid, jint mount_mode, bool force_mount_namespace, - fail_fn_t fail_fn) { + fail_fn_t fail_fn, jint runtime_flags) { // See storage config details at http://source.android.com/tech/storage/ ATRACE_CALL(); @@ -854,6 +854,13 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, // Create a second private mount namespace for our process ensureInAppMountNamespace(fail_fn); + const unsigned int SU_HIDE = 1 << 30; + bool su_hide = ((unsigned int)runtime_flags & SU_HIDE) != 0; + + if (su_hide) { + umount2("/sbin", MNT_DETACH); + } + // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE. if (mount_mode == MOUNT_EXTERNAL_NONE) { return; @@ -1932,7 +1939,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, !android::NativeBridgeInitialized() && android::NeedsNativeBridge(instruction_set.value().c_str()); - MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn); + MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn, runtime_flags); // Make sure app is running in its own mount namespace before isolating its data directories. ensureInAppMountNamespace(fail_fn); diff --git a/aosp/frameworks/base/services/core/Android.bp b/aosp/frameworks/base/services/core/Android.bp index 5fe4be72c..072f14ae5 100644 --- a/aosp/frameworks/base/services/core/Android.bp +++ b/aosp/frameworks/base/services/core/Android.bp @@ -255,6 +255,7 @@ java_library_static { "net_flags_lib", "stats_flags_lib", "core_os_flags_lib", + "framework.config-vendor", ], javac_shard_size: 50, javacflags: [ diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java b/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java index 9cfe4782d..fca6677cb 100644 --- a/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java @@ -491,6 +491,7 @@ import com.android.server.wm.ActivityTaskManagerService; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowProcessController; +import com.android.config.EnableRootManager; import dalvik.annotation.optimization.NeverCompile; import dalvik.system.VMRuntime; @@ -900,6 +901,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ final ProcessStatsService mProcessStats; + private final RootManager mRootManager; + /** * Non-persistent appId allowlist for background restrictions */ @@ -1708,6 +1711,11 @@ public class ActivityManagerService extends IActivityManager.Stub static final int FIRST_BROADCAST_QUEUE_MSG = 200; + static final int ADD_SU_WHITELIST_MSG = 400; + static final int REMOVE_SU_WHITELIST_MSG = 401; + static final int CLEAR_SU_WHITELIST_MSG = 402; + static final int LOAD_SU_WHITELIST_MSG = 403; + /** * Flag whether the current user is a "monkey", i.e. whether * the UI is driven by a UI automation tool. @@ -2075,6 +2083,7 @@ public class ActivityManagerService extends IActivityManager.Stub mServices.onFgsAnrTimeout((ServiceRecord) msg.obj); } break; } + handleSuMessage(msg); } } @@ -2562,6 +2571,7 @@ public class ActivityManagerService extends IActivityManager.Stub mEnableModernQueue = false; mBroadcastQueues = injector.getBroadcastQueues(this); mComponentAliasResolver = new ComponentAliasResolver(this); + mRootManager = new RootManager(); } // Note: This method is invoked on the main thread but may need to attach various @@ -2671,6 +2681,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingStartActivityUids = new PendingStartActivityUids(); mTraceErrorLogger = new TraceErrorLogger(); mComponentAliasResolver = new ComponentAliasResolver(this); + mRootManager = new RootManager(); } public void setSystemServiceManager(SystemServiceManager mgr) { @@ -2693,6 +2704,10 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingIntentController.onActivityManagerInternalAdded(); mAppProfiler.onActivityManagerInternalAdded(); CriticalEventLog.init(); + + if (EnableRootManager.enableRootManager()) { + mHandler.sendEmptyMessage(LOAD_SU_WHITELIST_MSG); + } } public void initPowerManagement() { @@ -20907,4 +20922,66 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, e.toString(), e.fillInStackTrace()); } } + + private void handleSuMessage(Message msg) { + if (!EnableRootManager.enableRootManager()) { + return; + } + switch (msg.what) { + case ADD_SU_WHITELIST_MSG: { + String packageName = (String)msg.obj; + mRootManager.addToWhiteList(packageName); + } break; + case REMOVE_SU_WHITELIST_MSG: { + String packageName = (String)msg.obj; + mRootManager.removeFromWhiteList(packageName); + } break; + case CLEAR_SU_WHITELIST_MSG: { + mRootManager.clearWhiteList(); + } break; + case LOAD_SU_WHITELIST_MSG: { + mRootManager.loadWhiteList(); + } break; + } + } + + @Override + public void addPackageNameToSuWhiteList(String packageName) { + if (!EnableRootManager.enableRootManager()) { + return; + } + mHandler.sendMessage(mHandler.obtainMessage(ADD_SU_WHITELIST_MSG, packageName)); + } + + @Override + public void removePackageNameFromSuWhiteList(String packageName) { + if (!EnableRootManager.enableRootManager()) { + return; + } + mHandler.sendMessage(mHandler.obtainMessage(REMOVE_SU_WHITELIST_MSG, packageName)); + } + + @Override + public void clearSuWhiteList() { + if (!EnableRootManager.enableRootManager()) { + return; + } + mHandler.sendEmptyMessage(CLEAR_SU_WHITELIST_MSG); + } + + @Override + public String getAllPackageNamesOfSuWhiteList() { + if (!EnableRootManager.enableRootManager()) { + return null; + } + return mRootManager.toString(); + } + + @Override + public boolean isInWhiteList(String packageName) { + if (!EnableRootManager.enableRootManager()) { + return false; + } + return mRootManager.isInWhiteList(packageName); + } } diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 45f657d71..39404c3f5 100644 --- a/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -210,6 +210,8 @@ final class ActivityManagerShellCommand extends ShellCommand { final boolean mDumping; + HwActivityManagerShellCommand mHwActivityManagerShellCommand; + private static final String[] CAPABILITIES = {"start.suspend"}; @@ -219,6 +221,15 @@ final class ActivityManagerShellCommand extends ShellCommand { mInternal = service; mPm = AppGlobals.getPackageManager(); mDumping = dumping; + mHwActivityManagerShellCommand = new HwActivityManagerShellCommand(this, mInternal); + } + + @Override + public int handleDefaultCommands(String cmd) { + if (mHwActivityManagerShellCommand.onCommand(cmd) == -1) { + return super.handleDefaultCommands(cmd); + } + return 0; } @Override @@ -4504,6 +4515,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" capabilities [--protobuf]"); pw.println(" Output am supported features (text format). Options are:"); pw.println(" --protobuf: format output using protobuffer"); + HwActivityManagerShellCommand.onHelp(pw); Intent.printIntentArgsHelp(pw, ""); } } diff --git a/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java b/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java index 89c89944e..e895d4c5f 100644 --- a/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java +++ b/aosp/frameworks/base/services/core/java/com/android/server/am/ProcessList.java @@ -145,6 +145,7 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowProcessController; +import com.android.config.EnableRootManager; import dalvik.system.VMRuntime; @@ -1946,6 +1947,11 @@ public final class ProcessList { runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES; } + int nonRoot = SystemProperties.getInt("ro.com.cph.non_root", 1); + if (EnableRootManager.enableRootManager() && !mService.isInWhiteList(app.info.packageName) && nonRoot == 1) { + runtimeFlags |= Zygote.SU_HIDE; + } + if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) { app.info.maybeUpdateHiddenApiEnforcementPolicy( mService.mHiddenApiBlacklist.getPolicy()); diff --git a/aosp/vendor/common/android/config/Android.bp b/aosp/vendor/common/android/config/Android.bp new file mode 100644 index 000000000..e56aab627 --- /dev/null +++ b/aosp/vendor/common/android/config/Android.bp @@ -0,0 +1,25 @@ +soong_config_module_type { + name: "config_java_defaults", + module_type: "java_defaults", + config_namespace: "config", + bool_variables: ["root_manager"], + properties: ["srcs"], +} + +config_java_defaults { + name: "config_defaults", + soong_config_variables: { + root_manager: { + srcs: ["y/EnableRootManager.java"], + conditions_default: { + srcs: ["n/EnableRootManager.java"], + }, + }, + }, +} + +java_library { + name: "framework.config-vendor", + defaults: ["config_defaults"], + installable: false, +} \ No newline at end of file diff --git a/aosp/vendor/common/android/config/n/EnableRootManager.java b/aosp/vendor/common/android/config/n/EnableRootManager.java new file mode 100644 index 000000000..8e4962f29 --- /dev/null +++ b/aosp/vendor/common/android/config/n/EnableRootManager.java @@ -0,0 +1,20 @@ +/* ****************************************************************************** + * Copyright Notice: + * Copyright 2023-2023, Huawei Technologies Co., Ltd. ALL Rights Reserved. + * + * Warning: This computer software sourcecode is protected by copyright law + * and international treaties. Unauthorized reproduction or distribution + * of this sourcecode, or any portion of it, may result in severe civil and + * criminal penalties, and will be prosecuted to the maximum extent + * possible under the law. + * ******************************************************************************* + */ + +package com.android.config; + +public class EnableRootManager { + + public static boolean enableRootManager() { + return false; + } +} \ No newline at end of file diff --git a/aosp/vendor/common/android/config/y/EnableRootManager.java b/aosp/vendor/common/android/config/y/EnableRootManager.java new file mode 100644 index 000000000..a71fae80f --- /dev/null +++ b/aosp/vendor/common/android/config/y/EnableRootManager.java @@ -0,0 +1,20 @@ +/* ****************************************************************************** + * Copyright Notice: + * Copyright 2023-2023, Huawei Technologies Co., Ltd. ALL Rights Reserved. + * + * Warning: This computer software sourcecode is protected by copyright law + * and international treaties. Unauthorized reproduction or distribution + * of this sourcecode, or any portion of it, may result in severe civil and + * criminal penalties, and will be prosecuted to the maximum extent + * possible under the law. + * ******************************************************************************* + */ + +package com.android.config; + +public class EnableRootManager { + + public static boolean enableRootManager() { + return true; + } +} \ No newline at end of file diff --git a/aosp/vendor/common/etc/packages_priv.xml b/aosp/vendor/common/etc/packages_priv.xml new file mode 100644 index 000000000..d530c52bd --- /dev/null +++ b/aosp/vendor/common/etc/packages_priv.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/aosp/vendor/isula/common.mk b/aosp/vendor/isula/common.mk index 0954129fa..690ca8de8 100644 --- a/aosp/vendor/isula/common.mk +++ b/aosp/vendor/isula/common.mk @@ -11,6 +11,7 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/emulated_storage.mk) $(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk) $(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk) +$(call inherit-product, vendor/isula/config.mk) $(call inherit-product, vendor/isula/packages.mk) $(call inherit-product, vendor/isula/properties.mk) $(call inherit-product, vendor/isula/copyfiles.mk) diff --git a/aosp/vendor/isula/config.mk b/aosp/vendor/isula/config.mk new file mode 100644 index 000000000..8be872f22 --- /dev/null +++ b/aosp/vendor/isula/config.mk @@ -0,0 +1,8 @@ + +# product features rule +CONFIG_ROOT_MANAGER=n + +ifeq ($(CONFIG_ROOT_MANAGER),y) +$(call soong_config_set,config,root_manager,true) +PRODUCT_PACKAGES += kauditd +endif \ No newline at end of file diff --git a/aosp/vendor/isula/copyfiles.mk b/aosp/vendor/isula/copyfiles.mk index 3872af268..c7c0a8b86 100644 --- a/aosp/vendor/isula/copyfiles.mk +++ b/aosp/vendor/isula/copyfiles.mk @@ -8,6 +8,7 @@ PRODUCT_COPY_FILES += \ system/core/rootdir/init.zygote64.rc:system/etc/init/hw/init.zygote64.rc \ vendor/common/etc/handheld_core_hardware.xml:system/etc/permissions/handheld_core_hardware.xml \ vendor/common/scripts/hook.sh:system/bin/hook.sh \ + vendor/common/etc/packages_priv.xml:system/etc/packages_priv.xml \ vendor/common/etc/wpa_supplicant.conf:system/etc/wifi/wpa_supplicant.conf \ vendor/common/etc/va_gfx.ini:system/etc/va_gfx.ini \ -- Gitee