Merge "Reland "IPCThreadState: Add a public method to probe if a binder call is being served."""
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f8f0265..2d780f5 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,7 @@
[Hook Scripts]
owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$"
installd_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/installd/"
+dumpstate_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/dumpstate/"
+dumpsys_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/dumpsys/"
+# bugreports matches both cmds/bugreport and cmds/bugreportz
+bugreports_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/bugreport"
diff --git a/cmds/atrace/Android.bp b/cmds/atrace/Android.bp
index bb84a18..cc2a6f7 100644
--- a/cmds/atrace/Android.bp
+++ b/cmds/atrace/Android.bp
@@ -19,6 +19,7 @@
"libz",
"libbase",
"libpdx_default_transport",
+ "android.hardware.atrace@1.0",
],
init_rc: ["atrace.rc"],
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 94dbebc..e897482 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -37,6 +37,7 @@
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
+#include <android/hardware/atrace/1.0/IAtraceDevice.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <hidl/ServiceManagement.h>
@@ -52,6 +53,12 @@
using namespace android;
using pdx::default_transport::ServiceUtility;
+using hardware::hidl_vec;
+using hardware::hidl_string;
+using hardware::Return;
+using hardware::atrace::V1_0::IAtraceDevice;
+using hardware::atrace::V1_0::Status;
+using hardware::atrace::V1_0::toString;
using std::string;
@@ -92,11 +99,7 @@
/* Tracing categories */
static const TracingCategory k_categories[] = {
- { "gfx", "Graphics", ATRACE_TAG_GRAPHICS, {
- { OPT, "events/mdss/enable" },
- { OPT, "events/sde/enable" },
- { OPT, "events/mali_systrace/enable" },
- } },
+ { "gfx", "Graphics", ATRACE_TAG_GRAPHICS, { } },
{ "input", "Input", ATRACE_TAG_INPUT, { } },
{ "view", "View System", ATRACE_TAG_VIEW, { } },
{ "webview", "WebView", ATRACE_TAG_WEBVIEW, { } },
@@ -223,6 +226,23 @@
} },
};
+struct TracingVendorCategory {
+ // The name identifying the category.
+ std::string name;
+
+ // A longer description of the category.
+ std::string description;
+
+ // If the category is enabled through command.
+ bool enabled;
+
+ TracingVendorCategory(string &&name, string &&description, bool enabled)
+ : name(std::move(name))
+ , description(std::move(description))
+ , enabled(enabled)
+ {}
+};
+
/* Command line options */
static int g_traceDurationSeconds = 5;
static bool g_traceOverwrite = false;
@@ -240,6 +260,8 @@
static bool g_traceAborted = false;
static bool g_categoryEnables[arraysize(k_categories)] = {};
static std::string g_traceFolder;
+static sp<IAtraceDevice> g_atraceHal;
+static std::vector<TracingVendorCategory> g_vendorCategories;
/* Sys file paths */
static const char* k_traceClockPath =
@@ -755,13 +777,20 @@
return ok;
}
-static bool setCategoryEnable(const char* name, bool enable)
+static bool setCategoryEnable(const char* name)
{
+ bool vendor_found = false;
+ for (auto &c : g_vendorCategories) {
+ if (strcmp(name, c.name.c_str()) == 0) {
+ c.enabled = true;
+ vendor_found = true;
+ }
+ }
for (size_t i = 0; i < arraysize(k_categories); i++) {
const TracingCategory& c = k_categories[i];
if (strcmp(name, c.name) == 0) {
if (isCategorySupported(c)) {
- g_categoryEnables[i] = enable;
+ g_categoryEnables[i] = true;
return true;
} else {
if (isCategorySupportedForRoot(c)) {
@@ -775,6 +804,9 @@
}
}
}
+ if (vendor_found) {
+ return true;
+ }
fprintf(stderr, "error: unknown tracing category \"%s\"\n", name);
return false;
}
@@ -795,7 +827,7 @@
tokenizer->skipDelimiters(" ");
continue;
}
- ok &= setCategoryEnable(token.string(), true);
+ ok &= setCategoryEnable(token.string());
}
delete tokenizer;
return ok;
@@ -1083,6 +1115,9 @@
printf(" %10s - %s\n", c.name, c.longname);
}
}
+ for (const auto &c : g_vendorCategories) {
+ printf(" %10s - %s (HAL)\n", c.name.c_str(), c.description.c_str());
+ }
}
// Print the command usage help to stderr.
@@ -1139,6 +1174,79 @@
return true;
}
+void initVendorCategories()
+{
+ g_atraceHal = IAtraceDevice::getService();
+
+ if (g_atraceHal == nullptr) {
+ // No atrace HAL
+ return;
+ }
+
+ Return<void> ret = g_atraceHal->listCategories(
+ [](const auto& list) {
+ g_vendorCategories.reserve(list.size());
+ for (const auto& category : list) {
+ g_vendorCategories.emplace_back(category.name, category.description, false);
+ }
+ });
+ if (!ret.isOk()) {
+ fprintf(stderr, "calling atrace HAL failed: %s\n", ret.description().c_str());
+ }
+}
+
+static bool setUpVendorTracing()
+{
+ if (g_atraceHal == nullptr) {
+ // No atrace HAL
+ return true;
+ }
+
+ std::vector<hidl_string> categories;
+ for (const auto &c : g_vendorCategories) {
+ if (c.enabled) {
+ categories.emplace_back(c.name);
+ }
+ }
+
+ if (!categories.size()) {
+ return true;
+ }
+
+ auto ret = g_atraceHal->enableCategories(categories);
+ if (!ret.isOk()) {
+ fprintf(stderr, "calling atrace HAL failed: %s\n", ret.description().c_str());
+ return false;
+ } else if (ret != Status::SUCCESS) {
+ fprintf(stderr, "calling atrace HAL failed: %s\n", toString(ret).c_str());
+ return false;
+ }
+ return true;
+}
+
+static bool cleanUpVendorTracing()
+{
+ if (g_atraceHal == nullptr) {
+ // No atrace HAL
+ return true;
+ }
+
+ if (!g_vendorCategories.size()) {
+ // No vendor categories
+ return true;
+ }
+
+ auto ret = g_atraceHal->disableAllCategories();
+ if (!ret.isOk()) {
+ fprintf(stderr, "calling atrace HAL failed: %s\n", ret.description().c_str());
+ return false;
+ } else if (ret != Status::SUCCESS) {
+ fprintf(stderr, "calling atrace HAL failed: %s\n", toString(ret).c_str());
+ return false;
+ }
+ return true;
+}
+
int main(int argc, char **argv)
{
bool async = false;
@@ -1158,6 +1266,8 @@
exit(-1);
}
+ initVendorCategories();
+
for (;;) {
int ret;
int option_index = 0;
@@ -1176,7 +1286,7 @@
if (ret < 0) {
for (int i = optind; i < argc; i++) {
- if (!setCategoryEnable(argv[i], true)) {
+ if (!setCategoryEnable(argv[i])) {
fprintf(stderr, "error enabling tracing category \"%s\"\n", argv[i]);
exit(1);
}
@@ -1279,6 +1389,7 @@
if (ok && traceStart && !onlyUserspace) {
ok &= setUpKernelTracing();
+ ok &= setUpVendorTracing();
ok &= startTrace();
}
@@ -1347,6 +1458,7 @@
// Reset the trace buffer size to 1.
if (traceStop) {
+ cleanUpVendorTracing();
cleanUpUserspaceTracing();
if (!onlyUserspace)
cleanUpKernelTracing();
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 5903656..d950b7c 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -125,12 +125,6 @@
chmod 0666 /sys/kernel/tracing/events/block/block_rq_complete/enable
chmod 0666 /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
- # graphics
- chmod 0666 /sys/kernel/tracing/events/sde/enable
- chmod 0666 /sys/kernel/debug/tracing/events/sde/enable
- chmod 0666 /sys/kernel/tracing/events/mdss/enable
- chmod 0666 /sys/kernel/debug/tracing/events/mdss/enable
-
# Tracing disabled by default
write /sys/kernel/debug/tracing/tracing_on 0
write /sys/kernel/tracing/tracing_on 0
diff --git a/cmds/bugreport/OWNERS b/cmds/bugreport/OWNERS
new file mode 100644
index 0000000..1ba7cff
--- /dev/null
+++ b/cmds/bugreport/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+felipeal@google.com
+nandana@google.com
+jsharkey@android.com
+enh@google.com
diff --git a/cmds/bugreportz/OWNERS b/cmds/bugreportz/OWNERS
new file mode 100644
index 0000000..1ba7cff
--- /dev/null
+++ b/cmds/bugreportz/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+felipeal@google.com
+nandana@google.com
+jsharkey@android.com
+enh@google.com
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index c852df1..5acc09d 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -133,6 +133,7 @@
name: "dumpstate_test",
defaults: ["dumpstate_defaults"],
srcs: [
+ "dumpstate.cpp",
"tests/dumpstate_test.cpp",
],
static_libs: ["libgmock"],
diff --git a/cmds/dumpstate/DumpstateUtil.cpp b/cmds/dumpstate/DumpstateUtil.cpp
index 85eb464..600a500 100644
--- a/cmds/dumpstate/DumpstateUtil.cpp
+++ b/cmds/dumpstate/DumpstateUtil.cpp
@@ -56,11 +56,11 @@
timespec ts;
ts.tv_sec = MSEC_TO_SEC(timeout_ms);
ts.tv_nsec = (timeout_ms % 1000) * 1000000;
- int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
+ int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
int saved_errno = errno;
// Set the signals back the way they were.
- if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
+ if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
printf("*** sigprocmask failed: %s\n", strerror(errno));
if (ret == 0) {
return false;
@@ -310,7 +310,7 @@
struct sigaction sigact;
memset(&sigact, 0, sizeof(sigact));
sigact.sa_handler = SIG_IGN;
- sigaction(SIGPIPE, &sigact, NULL);
+ sigaction(SIGPIPE, &sigact, nullptr);
execvp(path, (char**)args.data());
// execvp's result will be handled after waitpid_with_timeout() below, but
diff --git a/cmds/dumpstate/OWNERS b/cmds/dumpstate/OWNERS
new file mode 100644
index 0000000..1ba7cff
--- /dev/null
+++ b/cmds/dumpstate/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+felipeal@google.com
+nandana@google.com
+jsharkey@android.com
+enh@google.com
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 6cfdb2b..53bd851 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -101,7 +101,6 @@
#define ALT_PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops-0"
#define BLK_DEV_SYS_DIR "/sys/block"
-#define RAFT_DIR "/data/misc/raft"
#define RECOVERY_DIR "/cache/recovery"
#define RECOVERY_DATA_DIR "/data/misc/recovery"
#define UPDATE_ENGINE_LOG_DIR "/data/misc/update_engine_log"
@@ -455,40 +454,6 @@
}
}
-static void dump_raft() {
- if (PropertiesHelper::IsUserBuild()) {
- return;
- }
-
- std::string raft_path = ds.GetPath("-raft_log.txt");
- if (raft_path.empty()) {
- MYLOGD("raft_path is empty\n");
- return;
- }
-
- struct stat s;
- if (stat(RAFT_DIR, &s) != 0 || !S_ISDIR(s.st_mode)) {
- MYLOGD("%s does not exist or is not a directory\n", RAFT_DIR);
- return;
- }
-
- CommandOptions options = CommandOptions::WithTimeout(600).Build();
- if (!ds.IsZipping()) {
- // Write compressed and encoded raft logs to stdout if it's not a zipped bugreport.
- RunCommand("RAFT LOGS", {"logcompressor", "-r", RAFT_DIR}, options);
- return;
- }
-
- RunCommand("RAFT LOGS", {"logcompressor", "-n", "-r", RAFT_DIR, "-o", raft_path}, options);
- if (!ds.AddZipEntry("raft_log.txt", raft_path)) {
- MYLOGE("Unable to add raft log %s to zip file\n", raft_path.c_str());
- } else {
- if (remove(raft_path.c_str())) {
- MYLOGE("Error removing raft file %s: %s\n", raft_path.c_str(), strerror(errno));
- }
- }
-}
-
static bool skip_not_stat(const char *path) {
static const char stat[] = "/stat";
size_t len = strlen(path);
@@ -1411,6 +1376,54 @@
printf("========================================================\n");
}
+/* Dumps state for the default case. Returns true if everything went fine. */
+static bool DumpstateDefault() {
+ // Dumps systrace right away, otherwise it will be filled with unnecessary events.
+ // First try to dump anrd trace if the daemon is running. Otherwise, dump
+ // the raw trace.
+ if (!dump_anrd_trace()) {
+ dump_systrace();
+ }
+
+ // Invoking the following dumpsys calls before dump_traces() to try and
+ // keep the system stats as close to its initial state as possible.
+ RunDumpsysCritical();
+
+ /* collect stack traces from Dalvik and native processes (needs root) */
+ dump_traces_path = dump_traces();
+
+ /* Run some operations that require root. */
+ ds.tombstone_data_ = GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX, !ds.IsZipping());
+ ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX, !ds.IsZipping());
+
+ ds.AddDir(RECOVERY_DIR, true);
+ ds.AddDir(RECOVERY_DATA_DIR, true);
+ ds.AddDir(UPDATE_ENGINE_LOG_DIR, true);
+ ds.AddDir(LOGPERSIST_DATA_DIR, false);
+ if (!PropertiesHelper::IsUserBuild()) {
+ ds.AddDir(PROFILE_DATA_DIR_CUR, true);
+ ds.AddDir(PROFILE_DATA_DIR_REF, true);
+ }
+ add_mountinfo();
+ DumpIpTablesAsRoot();
+
+ // Capture any IPSec policies in play. No keys are exposed here.
+ RunCommand("IP XFRM POLICY", {"ip", "xfrm", "policy"}, CommandOptions::WithTimeout(10).Build());
+
+ // Run ss as root so we can see socket marks.
+ RunCommand("DETAILED SOCKET STATE", {"ss", "-eionptu"}, CommandOptions::WithTimeout(10).Build());
+
+ // Run iotop as root to show top 100 IO threads
+ RunCommand("IOTOP", {"iotop", "-n", "1", "-m", "100"});
+
+ if (!DropRootUser()) {
+ return false;
+ }
+
+ dumpstate();
+ return true;
+}
+
// This method collects common dumpsys for telephony and wifi
static void DumpstateRadioCommon() {
DumpIpTablesAsRoot();
@@ -1466,6 +1479,12 @@
DUMPSYS_COMPONENTS_OPTIONS);
printf("========================================================\n");
+ printf("== Checkins\n");
+ printf("========================================================\n");
+
+ RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"});
+
+ printf("========================================================\n");
printf("== dumpstate: done (id %d)\n", ds.id_);
printf("========================================================\n");
}
@@ -1745,24 +1764,290 @@
// clang-format on
}
-/** Main entry point for dumpstate. */
-int run_main(int argc, char* argv[]) {
- int do_add_date = 0;
- int do_zip_file = 0;
- int do_vibrate = 1;
- char* use_outfile = nullptr;
- int use_socket = 0;
- int use_control_socket = 0;
- int do_fb = 0;
- int do_broadcast = 0;
- int is_remote_mode = 0;
- bool show_header_only = false;
- bool do_start_service = false;
- bool telephony_only = false;
- bool wifi_only = false;
- int dup_stdout_fd;
- int dup_stderr_fd;
+/*
+ * Prepares state like filename, screenshot path, etc in Dumpstate. Also initializes ZipWriter
+ * if we are writing zip files and adds the version file.
+ */
+static void PrepareToWriteToFile() {
+ const Dumpstate::DumpOptions& options = ds.options_;
+ ds.bugreport_dir_ = dirname(options.use_outfile.c_str());
+ std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD");
+ std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE");
+ ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(options.use_outfile.c_str()),
+ device_name.c_str(), build_id.c_str());
+ if (options.do_add_date) {
+ char date[80];
+ strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
+ ds.name_ = date;
+ } else {
+ ds.name_ = "undated";
+ }
+ if (options.telephony_only) {
+ ds.base_name_ += "-telephony";
+ } else if (options.wifi_only) {
+ ds.base_name_ += "-wifi";
+ }
+
+ if (options.do_fb) {
+ ds.screenshot_path_ = ds.GetPath(".png");
+ }
+ ds.tmp_path_ = ds.GetPath(".tmp");
+ ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt");
+
+ MYLOGD(
+ "Bugreport dir: %s\n"
+ "Base name: %s\n"
+ "Suffix: %s\n"
+ "Log path: %s\n"
+ "Temporary path: %s\n"
+ "Screenshot path: %s\n",
+ ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.log_path_.c_str(),
+ ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());
+
+ if (options.do_zip_file) {
+ ds.path_ = ds.GetPath(".zip");
+ MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
+ create_parent_dirs(ds.path_.c_str());
+ ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
+ if (ds.zip_file == nullptr) {
+ MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
+ } else {
+ ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
+ }
+ ds.AddTextZipEntry("version.txt", ds.version_);
+ }
+}
+
+/*
+ * Finalizes writing to the file by renaming or zipping the tmp file to the final location,
+ * printing zipped file status, etc.
+ */
+static void FinalizeFile() {
+ const Dumpstate::DumpOptions& options = ds.options_;
+ /* check if user changed the suffix using system properties */
+ std::string name =
+ android::base::GetProperty(android::base::StringPrintf("dumpstate.%d.name", ds.pid_), "");
+ bool change_suffix = false;
+ if (!name.empty()) {
+ /* must whitelist which characters are allowed, otherwise it could cross directories */
+ std::regex valid_regex("^[-_a-zA-Z0-9]+$");
+ if (std::regex_match(name.c_str(), valid_regex)) {
+ change_suffix = true;
+ } else {
+ MYLOGE("invalid suffix provided by user: %s\n", name.c_str());
+ }
+ }
+ if (change_suffix) {
+ MYLOGI("changing suffix from %s to %s\n", ds.name_.c_str(), name.c_str());
+ ds.name_ = name;
+ if (!ds.screenshot_path_.empty()) {
+ std::string new_screenshot_path = ds.GetPath(".png");
+ if (rename(ds.screenshot_path_.c_str(), new_screenshot_path.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", ds.screenshot_path_.c_str(),
+ new_screenshot_path.c_str(), strerror(errno));
+ } else {
+ ds.screenshot_path_ = new_screenshot_path;
+ }
+ }
+ }
+
+ bool do_text_file = true;
+ if (options.do_zip_file) {
+ if (!ds.FinishZipFile()) {
+ MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
+ do_text_file = true;
+ } else {
+ do_text_file = false;
+ // Since zip file is already created, it needs to be renamed.
+ std::string new_path = ds.GetPath(".zip");
+ if (ds.path_ != new_path) {
+ MYLOGD("Renaming zip file from %s to %s\n", ds.path_.c_str(), new_path.c_str());
+ if (rename(ds.path_.c_str(), new_path.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", ds.path_.c_str(), new_path.c_str(),
+ strerror(errno));
+ } else {
+ ds.path_ = new_path;
+ }
+ }
+ }
+ }
+ if (do_text_file) {
+ ds.path_ = ds.GetPath(".txt");
+ MYLOGD("Generating .txt bugreport at %s from %s\n", ds.path_.c_str(), ds.tmp_path_.c_str());
+ if (rename(ds.tmp_path_.c_str(), ds.path_.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", ds.tmp_path_.c_str(), ds.path_.c_str(), strerror(errno));
+ ds.path_.clear();
+ }
+ }
+ if (options.use_control_socket) {
+ if (do_text_file) {
+ dprintf(ds.control_socket_fd_,
+ "FAIL:could not create zip file, check %s "
+ "for more details\n",
+ ds.log_path_.c_str());
+ } else {
+ dprintf(ds.control_socket_fd_, "OK:%s\n", ds.path_.c_str());
+ }
+ }
+}
+
+/* Broadcasts that we are done with the bugreport */
+static void SendBugreportFinishedBroadcast() {
+ const Dumpstate::DumpOptions& options = ds.options_;
+ if (!ds.path_.empty()) {
+ MYLOGI("Final bugreport path: %s\n", ds.path_.c_str());
+ // clang-format off
+
+ std::vector<std::string> am_args = {
+ "--receiver-permission", "android.permission.DUMP",
+ "--ei", "android.intent.extra.ID", std::to_string(ds.id_),
+ "--ei", "android.intent.extra.PID", std::to_string(ds.pid_),
+ "--ei", "android.intent.extra.MAX", std::to_string(ds.progress_->GetMax()),
+ "--es", "android.intent.extra.BUGREPORT", ds.path_,
+ "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_
+ };
+ // clang-format on
+ if (options.do_fb) {
+ am_args.push_back("--es");
+ am_args.push_back("android.intent.extra.SCREENSHOT");
+ am_args.push_back(ds.screenshot_path_);
+ }
+ if (!ds.notification_title.empty()) {
+ am_args.push_back("--es");
+ am_args.push_back("android.intent.extra.TITLE");
+ am_args.push_back(ds.notification_title);
+ if (!ds.notification_description.empty()) {
+ am_args.push_back("--es");
+ am_args.push_back("android.intent.extra.DESCRIPTION");
+ am_args.push_back(ds.notification_description);
+ }
+ }
+ if (options.is_remote_mode) {
+ am_args.push_back("--es");
+ am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH");
+ am_args.push_back(SHA256_file_hash(ds.path_));
+ SendBroadcast("com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED", am_args);
+ } else {
+ SendBroadcast("com.android.internal.intent.action.BUGREPORT_FINISHED", am_args);
+ }
+ } else {
+ MYLOGE("Skipping finished broadcast because bugreport could not be generated\n");
+ }
+}
+
+int Dumpstate::ParseCommandlineOptions(int argc, char* argv[]) {
+ int ret = -1; // success
+ int c;
+ while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) {
+ switch (c) {
+ // clang-format off
+ case 'd': options_.do_add_date = true; break;
+ case 'z': options_.do_zip_file = true; break;
+ case 'o': options_.use_outfile = optarg; break;
+ case 's': options_.use_socket = true; break;
+ case 'S': options_.use_control_socket = true; break;
+ case 'v': options_.show_header_only = true; break;
+ case 'q': options_.do_vibrate = false; break;
+ case 'p': options_.do_fb = true; break;
+ case 'P': update_progress_ = true; break;
+ case 'R': options_.is_remote_mode = true; break;
+ case 'B': options_.do_broadcast = true; break;
+ case 'V': break; // compatibility no-op
+ case 'h':
+ ret = 0;
+ break;
+ default:
+ fprintf(stderr, "Invalid option: %c\n", c);
+ ret = 1;
+ break;
+ // clang-format on
+ }
+ }
+
+ // TODO: use helper function to convert argv into a string
+ for (int i = 0; i < argc; i++) {
+ args_ += argv[i];
+ if (i < argc - 1) {
+ args_ += " ";
+ }
+ }
+
+ // Reset next index used by getopt so this can be called multiple times, for eg, in tests.
+ optind = 1;
+ return ret;
+}
+
+// TODO: Move away from system properties when we have binder.
+void Dumpstate::SetOptionsFromProperties() {
+ extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, "");
+ if (!extra_options_.empty()) {
+ // Framework uses a system property to override some command-line args.
+ // Currently, it contains the type of the requested bugreport.
+ if (extra_options_ == "bugreportplus") {
+ // Currently, the dumpstate binder is only used by Shell to update progress.
+ options_.do_start_service = true;
+ update_progress_ = true;
+ options_.do_fb = false;
+ } else if (extra_options_ == "bugreportremote") {
+ options_.do_vibrate = false;
+ options_.is_remote_mode = true;
+ options_.do_fb = false;
+ } else if (extra_options_ == "bugreportwear") {
+ options_.do_start_service = true;
+ update_progress_ = true;
+ options_.do_zip_file = true;
+ } else if (extra_options_ == "bugreporttelephony") {
+ options_.telephony_only = true;
+ } else if (extra_options_ == "bugreportwifi") {
+ options_.wifi_only = true;
+ options_.do_zip_file = true;
+ } else {
+ MYLOGE("Unknown extra option: %s\n", extra_options_.c_str());
+ }
+ // Reset the property
+ android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, "");
+ }
+
+ notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, "");
+ if (!notification_title.empty()) {
+ // Reset the property
+ android::base::SetProperty(PROPERTY_EXTRA_TITLE, "");
+
+ notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
+ if (!notification_description.empty()) {
+ // Reset the property
+ android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
+ }
+ MYLOGD("notification (title: %s, description: %s)\n", notification_title.c_str(),
+ notification_description.c_str());
+ }
+}
+
+bool Dumpstate::ValidateOptions() {
+ if ((options_.do_zip_file || options_.do_add_date || ds.update_progress_ ||
+ options_.do_broadcast) &&
+ options_.use_outfile.empty()) {
+ return false;
+ }
+
+ if (options_.use_control_socket && !options_.do_zip_file) {
+ return false;
+ }
+
+ if (ds.update_progress_ && !options_.do_broadcast) {
+ return false;
+ }
+
+ if (options_.is_remote_mode && (ds.update_progress_ || !options_.do_broadcast ||
+ !options_.do_zip_file || !options_.do_add_date)) {
+ return false;
+ }
+ return true;
+}
+
+/* Main entry point for dumpstate. */
+int run_main(int argc, char* argv[]) {
/* set as high priority, and protect from OOM killer */
setpriority(PRIO_PROCESS, 0, -20);
@@ -1779,97 +2064,12 @@
}
}
- /* parse arguments */
- int c;
- while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) {
- switch (c) {
- // clang-format off
- case 'd': do_add_date = 1; break;
- case 'z': do_zip_file = 1; break;
- case 'o': use_outfile = optarg; break;
- case 's': use_socket = 1; break;
- case 'S': use_control_socket = 1; break;
- case 'v': show_header_only = true; break;
- case 'q': do_vibrate = 0; break;
- case 'p': do_fb = 1; break;
- case 'P': ds.update_progress_ = true; break;
- case 'R': is_remote_mode = 1; break;
- case 'B': do_broadcast = 1; break;
- case 'V': break; // compatibility no-op
- case 'h':
- ShowUsageAndExit(0);
- break;
- default:
- fprintf(stderr, "Invalid option: %c\n", c);
- ShowUsageAndExit();
- // clang-format on
- }
+ int status = ds.ParseCommandlineOptions(argc, argv);
+ if (status != -1) {
+ ShowUsageAndExit(status);
}
-
- // TODO: use helper function to convert argv into a string
- for (int i = 0; i < argc; i++) {
- ds.args_ += argv[i];
- if (i < argc - 1) {
- ds.args_ += " ";
- }
- }
-
- ds.extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, "");
- if (!ds.extra_options_.empty()) {
- // Framework uses a system property to override some command-line args.
- // Currently, it contains the type of the requested bugreport.
- if (ds.extra_options_ == "bugreportplus") {
- // Currently, the dumpstate binder is only used by Shell to update progress.
- do_start_service = true;
- ds.update_progress_ = true;
- do_fb = 0;
- } else if (ds.extra_options_ == "bugreportremote") {
- do_vibrate = 0;
- is_remote_mode = 1;
- do_fb = 0;
- } else if (ds.extra_options_ == "bugreportwear") {
- do_start_service = true;
- ds.update_progress_ = true;
- do_zip_file = 1;
- } else if (ds.extra_options_ == "bugreporttelephony") {
- telephony_only = true;
- } else if (ds.extra_options_ == "bugreportwifi") {
- wifi_only = true;
- do_zip_file = 1;
- } else {
- MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str());
- }
- // Reset the property
- android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, "");
- }
-
- ds.notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, "");
- if (!ds.notification_title.empty()) {
- // Reset the property
- android::base::SetProperty(PROPERTY_EXTRA_TITLE, "");
-
- ds.notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
- if (!ds.notification_description.empty()) {
- // Reset the property
- android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
- }
- MYLOGD("notification (title: %s, description: %s)\n",
- ds.notification_title.c_str(), ds.notification_description.c_str());
- }
-
- if ((do_zip_file || do_add_date || ds.update_progress_ || do_broadcast) && !use_outfile) {
- ExitOnInvalidArgs();
- }
-
- if (use_control_socket && !do_zip_file) {
- ExitOnInvalidArgs();
- }
-
- if (ds.update_progress_ && !do_broadcast) {
- ExitOnInvalidArgs();
- }
-
- if (is_remote_mode && (ds.update_progress_ || !do_broadcast || !do_zip_file || !do_add_date)) {
+ ds.SetOptionsFromProperties();
+ if (!ds.ValidateOptions()) {
ExitOnInvalidArgs();
}
@@ -1884,18 +2084,20 @@
exit(1);
}
- if (show_header_only) {
+ const Dumpstate::DumpOptions& options = ds.options_;
+ if (options.show_header_only) {
ds.PrintHeader();
exit(0);
}
- /* redirect output if needed */
- bool is_redirecting = !use_socket && use_outfile;
+ // Redirect output if needed
+ bool is_redirecting = !options.use_socket && !options.use_outfile.empty();
// TODO: temporarily set progress until it's part of the Dumpstate constructor
- std::string stats_path =
- is_redirecting ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(use_outfile))
- : "";
+ std::string stats_path = is_redirecting
+ ? android::base::StringPrintf("%s/dumpstate-stats.txt",
+ dirname(options.use_outfile.c_str()))
+ : "";
ds.progress_.reset(new Progress(stats_path));
/* gets the sequential id */
@@ -1907,7 +2109,7 @@
register_sig_handler();
- if (do_start_service) {
+ if (options.do_start_service) {
MYLOGI("Starting 'dumpstate' service\n");
android::status_t ret;
if ((ret = android::os::DumpstateService::Start()) != android::OK) {
@@ -1928,70 +2130,22 @@
// If we are going to use a socket, do it as early as possible
// to avoid timeouts from bugreport.
- if (use_socket) {
+ if (options.use_socket) {
redirect_to_socket(stdout, "dumpstate");
}
- if (use_control_socket) {
+ if (options.use_control_socket) {
MYLOGD("Opening control socket\n");
ds.control_socket_fd_ = open_socket("dumpstate");
ds.update_progress_ = 1;
}
if (is_redirecting) {
- ds.bugreport_dir_ = dirname(use_outfile);
- std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD");
- std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE");
- ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(use_outfile),
- device_name.c_str(), build_id.c_str());
- if (do_add_date) {
- char date[80];
- strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
- ds.name_ = date;
- } else {
- ds.name_ = "undated";
- }
-
- if (telephony_only) {
- ds.base_name_ += "-telephony";
- } else if (wifi_only) {
- ds.base_name_ += "-wifi";
- }
-
- if (do_fb) {
- ds.screenshot_path_ = ds.GetPath(".png");
- }
- ds.tmp_path_ = ds.GetPath(".tmp");
- ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt");
-
- MYLOGD(
- "Bugreport dir: %s\n"
- "Base name: %s\n"
- "Suffix: %s\n"
- "Log path: %s\n"
- "Temporary path: %s\n"
- "Screenshot path: %s\n",
- ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(),
- ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());
-
- if (do_zip_file) {
- ds.path_ = ds.GetPath(".zip");
- MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
- create_parent_dirs(ds.path_.c_str());
- ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
- if (ds.zip_file == nullptr) {
- MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
- do_zip_file = 0;
- } else {
- ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
- }
- ds.AddTextZipEntry("version.txt", ds.version_);
- }
+ PrepareToWriteToFile();
if (ds.update_progress_) {
- if (do_broadcast) {
+ if (options.do_broadcast) {
// clang-format off
-
std::vector<std::string> am_args = {
"--receiver-permission", "android.permission.DUMP",
"--es", "android.intent.extra.NAME", ds.name_,
@@ -2002,7 +2156,7 @@
// clang-format on
SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
}
- if (use_control_socket) {
+ if (options.use_control_socket) {
dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str());
}
}
@@ -2015,11 +2169,11 @@
fclose(cmdline);
}
- if (do_vibrate) {
+ if (options.do_vibrate) {
Vibrate(150);
}
- if (do_fb && ds.do_early_screenshot_) {
+ if (options.do_fb && ds.do_early_screenshot_) {
if (ds.screenshot_path_.empty()) {
// should not have happened
MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n");
@@ -2029,13 +2183,15 @@
}
}
- if (do_zip_file) {
+ if (options.do_zip_file && ds.zip_file != nullptr) {
if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) {
MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(),
strerror(errno));
}
}
+ int dup_stdout_fd;
+ int dup_stderr_fd;
if (is_redirecting) {
TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr)));
redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
@@ -2062,60 +2218,17 @@
// duration is logged into MYLOG instead.
ds.PrintHeader();
- if (telephony_only) {
+ if (options.telephony_only) {
DumpstateTelephonyOnly();
ds.DumpstateBoard();
- } else if (wifi_only) {
+ } else if (options.wifi_only) {
DumpstateWifiOnly();
} else {
- // Dumps systrace right away, otherwise it will be filled with unnecessary events.
- // First try to dump anrd trace if the daemon is running. Otherwise, dump
- // the raw trace.
- if (!dump_anrd_trace()) {
- dump_systrace();
- }
-
- // Invoking the following dumpsys calls before dump_traces() to try and
- // keep the system stats as close to its initial state as possible.
- RunDumpsysCritical();
-
- // TODO: Drop root user and move into dumpstate() once b/28633932 is fixed.
- dump_raft();
-
- /* collect stack traces from Dalvik and native processes (needs root) */
- dump_traces_path = dump_traces();
-
- /* Run some operations that require root. */
- ds.tombstone_data_ = GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX, !ds.IsZipping());
- ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX, !ds.IsZipping());
-
- ds.AddDir(RECOVERY_DIR, true);
- ds.AddDir(RECOVERY_DATA_DIR, true);
- ds.AddDir(UPDATE_ENGINE_LOG_DIR, true);
- ds.AddDir(LOGPERSIST_DATA_DIR, false);
- if (!PropertiesHelper::IsUserBuild()) {
- ds.AddDir(PROFILE_DATA_DIR_CUR, true);
- ds.AddDir(PROFILE_DATA_DIR_REF, true);
- }
- add_mountinfo();
- DumpIpTablesAsRoot();
-
- // Capture any IPSec policies in play. No keys are exposed here.
- RunCommand("IP XFRM POLICY", {"ip", "xfrm", "policy"},
- CommandOptions::WithTimeout(10).Build());
-
- // Run ss as root so we can see socket marks.
- RunCommand("DETAILED SOCKET STATE", {"ss", "-eionptu"},
- CommandOptions::WithTimeout(10).Build());
-
- // Run iotop as root to show top 100 IO threads
- RunCommand("IOTOP", {"iotop", "-n", "1", "-m", "100"});
-
- if (!DropRootUser()) {
+ // Dump state for the default case. This also drops root.
+ if (!DumpstateDefault()) {
+ // Something went wrong.
return -1;
}
-
- dumpstate();
}
/* close output if needed */
@@ -2124,79 +2237,12 @@
}
/* rename or zip the (now complete) .tmp file to its final location */
- if (use_outfile) {
-
- /* check if user changed the suffix using system properties */
- std::string name = android::base::GetProperty(
- android::base::StringPrintf("dumpstate.%d.name", ds.pid_), "");
- bool change_suffix= false;
- if (!name.empty()) {
- /* must whitelist which characters are allowed, otherwise it could cross directories */
- std::regex valid_regex("^[-_a-zA-Z0-9]+$");
- if (std::regex_match(name.c_str(), valid_regex)) {
- change_suffix = true;
- } else {
- MYLOGE("invalid suffix provided by user: %s\n", name.c_str());
- }
- }
- if (change_suffix) {
- MYLOGI("changing suffix from %s to %s\n", ds.name_.c_str(), name.c_str());
- ds.name_ = name;
- if (!ds.screenshot_path_.empty()) {
- std::string new_screenshot_path = ds.GetPath(".png");
- if (rename(ds.screenshot_path_.c_str(), new_screenshot_path.c_str())) {
- MYLOGE("rename(%s, %s): %s\n", ds.screenshot_path_.c_str(),
- new_screenshot_path.c_str(), strerror(errno));
- } else {
- ds.screenshot_path_ = new_screenshot_path;
- }
- }
- }
-
- bool do_text_file = true;
- if (do_zip_file) {
- if (!ds.FinishZipFile()) {
- MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
- do_text_file = true;
- } else {
- do_text_file = false;
- // Since zip file is already created, it needs to be renamed.
- std::string new_path = ds.GetPath(".zip");
- if (ds.path_ != new_path) {
- MYLOGD("Renaming zip file from %s to %s\n", ds.path_.c_str(), new_path.c_str());
- if (rename(ds.path_.c_str(), new_path.c_str())) {
- MYLOGE("rename(%s, %s): %s\n", ds.path_.c_str(), new_path.c_str(),
- strerror(errno));
- } else {
- ds.path_ = new_path;
- }
- }
- }
- }
- if (do_text_file) {
- ds.path_ = ds.GetPath(".txt");
- MYLOGD("Generating .txt bugreport at %s from %s\n", ds.path_.c_str(),
- ds.tmp_path_.c_str());
- if (rename(ds.tmp_path_.c_str(), ds.path_.c_str())) {
- MYLOGE("rename(%s, %s): %s\n", ds.tmp_path_.c_str(), ds.path_.c_str(),
- strerror(errno));
- ds.path_.clear();
- }
- }
- if (use_control_socket) {
- if (do_text_file) {
- dprintf(ds.control_socket_fd_,
- "FAIL:could not create zip file, check %s "
- "for more details\n",
- ds.log_path_.c_str());
- } else {
- dprintf(ds.control_socket_fd_, "OK:%s\n", ds.path_.c_str());
- }
- }
+ if (!options.use_outfile.empty()) {
+ FinalizeFile();
}
/* vibrate a few but shortly times to let user know it's finished */
- if (do_vibrate) {
+ if (options.do_vibrate) {
for (int i = 0; i < 3; i++) {
Vibrate(75);
usleep((75 + 50) * 1000);
@@ -2204,47 +2250,8 @@
}
/* tell activity manager we're done */
- if (do_broadcast) {
- if (!ds.path_.empty()) {
- MYLOGI("Final bugreport path: %s\n", ds.path_.c_str());
- // clang-format off
-
- std::vector<std::string> am_args = {
- "--receiver-permission", "android.permission.DUMP",
- "--ei", "android.intent.extra.ID", std::to_string(ds.id_),
- "--ei", "android.intent.extra.PID", std::to_string(ds.pid_),
- "--ei", "android.intent.extra.MAX", std::to_string(ds.progress_->GetMax()),
- "--es", "android.intent.extra.BUGREPORT", ds.path_,
- "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_
- };
- // clang-format on
- if (do_fb) {
- am_args.push_back("--es");
- am_args.push_back("android.intent.extra.SCREENSHOT");
- am_args.push_back(ds.screenshot_path_);
- }
- if (!ds.notification_title.empty()) {
- am_args.push_back("--es");
- am_args.push_back("android.intent.extra.TITLE");
- am_args.push_back(ds.notification_title);
- if (!ds.notification_description.empty()) {
- am_args.push_back("--es");
- am_args.push_back("android.intent.extra.DESCRIPTION");
- am_args.push_back(ds.notification_description);
- }
- }
- if (is_remote_mode) {
- am_args.push_back("--es");
- am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH");
- am_args.push_back(SHA256_file_hash(ds.path_));
- SendBroadcast("com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED",
- am_args);
- } else {
- SendBroadcast("com.android.internal.intent.action.BUGREPORT_FINISHED", am_args);
- }
- } else {
- MYLOGE("Skipping finished broadcast because bugreport could not be generated\n");
- }
+ if (options.do_broadcast) {
+ SendBugreportFinishedBroadcast();
}
MYLOGD("Final progress: %d/%d (estimated %d)\n", ds.progress_->Get(), ds.progress_->GetMax(),
@@ -2256,7 +2263,7 @@
TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr)));
}
- if (use_control_socket && ds.control_socket_fd_ != -1) {
+ if (options.use_control_socket && ds.control_socket_fd_ != -1) {
MYLOGD("Closing control socket\n");
close(ds.control_socket_fd_);
}
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index b220013..389cc2e 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -290,14 +290,51 @@
/* Returns true if the current version supports priority dump feature. */
bool CurrentVersionSupportsPriorityDumps() const;
- // TODO: initialize fields on constructor
+ // TODO: revisit the return values later.
+ /*
+ * Parses commandline arguments and sets runtime options accordingly.
+ *
+ * Returns 0 or positive number if the caller should exit with returned value as
+ * exit code, or returns -1 if caller should proceed with execution.
+ */
+ int ParseCommandlineOptions(int argc, char* argv[]);
+ /* Sets runtime options from the system properties. */
+ void SetOptionsFromProperties();
+
+ /* Returns true if the options set so far are consistent. */
+ bool ValidateOptions();
+
+ // TODO: add update_progress_ & other options from DumpState.
+ /*
+ * Structure to hold options that determine the behavior of dumpstate.
+ */
+ struct DumpOptions {
+ bool do_add_date = false;
+ bool do_zip_file = false;
+ bool do_vibrate = true;
+ bool use_socket = false;
+ bool use_control_socket = false;
+ bool do_fb = false;
+ bool do_broadcast = false;
+ bool is_remote_mode = false;
+ bool show_header_only = false;
+ bool do_start_service = false;
+ bool telephony_only = false;
+ bool wifi_only = false;
+ std::string use_outfile;
+ };
+
+ // TODO: initialize fields on constructor
// dumpstate id - unique after each device reboot.
uint32_t id_;
// dumpstate pid
pid_t pid_;
+ // Runtime options.
+ DumpOptions options_;
+
// Whether progress updates should be published.
bool update_progress_ = false;
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 838b385..c57535a 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -54,6 +54,8 @@
using ::testing::internal::GetCapturedStderr;
using ::testing::internal::GetCapturedStdout;
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
class DumpstateListenerMock : public IDumpstateListener {
public:
MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress));
@@ -144,6 +146,7 @@
ds.progress_.reset(new Progress());
ds.update_progress_ = false;
ds.update_progress_threshold_ = 0;
+ ds.options_ = Dumpstate::DumpOptions();
}
// Runs a command and capture `stdout` and `stderr`.
@@ -201,6 +204,157 @@
Dumpstate& ds = Dumpstate::GetInstance();
};
+TEST_F(DumpstateTest, ParseCommandlineOptionsNone) {
+ // clang-format off
+ char* argv[] = {
+ const_cast<char*>("dumpstate")
+ };
+ // clang-format on
+
+ int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
+ EXPECT_EQ(-1, ret);
+ EXPECT_FALSE(ds.options_.do_add_date);
+ EXPECT_FALSE(ds.options_.do_zip_file);
+ EXPECT_EQ("", ds.options_.use_outfile);
+ EXPECT_FALSE(ds.options_.use_socket);
+ EXPECT_FALSE(ds.options_.use_control_socket);
+ EXPECT_FALSE(ds.options_.show_header_only);
+ EXPECT_TRUE(ds.options_.do_vibrate);
+ EXPECT_FALSE(ds.options_.do_fb);
+ EXPECT_FALSE(ds.update_progress_);
+ EXPECT_FALSE(ds.options_.is_remote_mode);
+ EXPECT_FALSE(ds.options_.do_broadcast);
+}
+
+TEST_F(DumpstateTest, ParseCommandlineOptionsPartial1) {
+ // clang-format off
+ char* argv[] = {
+ const_cast<char*>("dumpstate"),
+ const_cast<char*>("-d"),
+ const_cast<char*>("-z"),
+ const_cast<char*>("-o abc"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-S"),
+
+ };
+ // clang-format on
+ int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
+ EXPECT_EQ(-1, ret);
+ EXPECT_TRUE(ds.options_.do_add_date);
+ EXPECT_TRUE(ds.options_.do_zip_file);
+ // TODO: Maybe we should trim the filename
+ EXPECT_EQ(" abc", std::string(ds.options_.use_outfile));
+ EXPECT_TRUE(ds.options_.use_socket);
+ EXPECT_TRUE(ds.options_.use_control_socket);
+
+ // Other options retain default values
+ EXPECT_FALSE(ds.options_.show_header_only);
+ EXPECT_TRUE(ds.options_.do_vibrate);
+ EXPECT_FALSE(ds.options_.do_fb);
+ EXPECT_FALSE(ds.update_progress_);
+ EXPECT_FALSE(ds.options_.is_remote_mode);
+ EXPECT_FALSE(ds.options_.do_broadcast);
+}
+
+TEST_F(DumpstateTest, ParseCommandlineOptionsPartial2) {
+ // clang-format off
+ char* argv[] = {
+ const_cast<char*>("dumpstate"),
+ const_cast<char*>("-v"),
+ const_cast<char*>("-q"),
+ const_cast<char*>("-p"),
+ const_cast<char*>("-P"),
+ const_cast<char*>("-R"),
+ const_cast<char*>("-B"),
+ };
+ // clang-format on
+ int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
+ EXPECT_EQ(-1, ret);
+ EXPECT_TRUE(ds.options_.show_header_only);
+ EXPECT_FALSE(ds.options_.do_vibrate);
+ EXPECT_TRUE(ds.options_.do_fb);
+ EXPECT_TRUE(ds.update_progress_);
+ EXPECT_TRUE(ds.options_.is_remote_mode);
+ EXPECT_TRUE(ds.options_.do_broadcast);
+
+ // Other options retain default values
+ EXPECT_FALSE(ds.options_.do_add_date);
+ EXPECT_FALSE(ds.options_.do_zip_file);
+ EXPECT_EQ("", ds.options_.use_outfile);
+ EXPECT_FALSE(ds.options_.use_socket);
+ EXPECT_FALSE(ds.options_.use_control_socket);
+}
+
+TEST_F(DumpstateTest, ParseCommandlineOptionsHelp) {
+ // clang-format off
+ char* argv[] = {
+ const_cast<char*>("dumpstate"),
+ const_cast<char*>("-h")
+ };
+ // clang-format on
+ int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
+
+ // -h is for help. Caller exit with code = 0 after printing usage, so expect return = 0.
+ EXPECT_EQ(0, ret);
+}
+
+TEST_F(DumpstateTest, ParseCommandlineOptionsUnknown) {
+ // clang-format off
+ char* argv[] = {
+ const_cast<char*>("dumpstate"),
+ const_cast<char*>("-u") // unknown flag
+ };
+ // clang-format on
+ int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
+
+ // -u is unknown. Caller exit with code = 1 to show execution failure, after printing usage,
+ // so expect return = 1.
+ EXPECT_EQ(1, ret);
+}
+
+TEST_F(DumpstateTest, ValidateOptionsNeedOutfile1) {
+ ds.options_.do_zip_file = true;
+ EXPECT_FALSE(ds.ValidateOptions());
+ ds.options_.use_outfile = "a/b/c";
+ EXPECT_TRUE(ds.ValidateOptions());
+}
+
+TEST_F(DumpstateTest, ValidateOptionsNeedOutfile2) {
+ ds.options_.do_broadcast = true;
+ EXPECT_FALSE(ds.ValidateOptions());
+ ds.options_.use_outfile = "a/b/c";
+ EXPECT_TRUE(ds.ValidateOptions());
+}
+
+TEST_F(DumpstateTest, ValidateOptionsNeedZipfile) {
+ ds.options_.use_control_socket = true;
+ EXPECT_FALSE(ds.ValidateOptions());
+
+ ds.options_.do_zip_file = true;
+ ds.options_.use_outfile = "a/b/c"; // do_zip_file needs outfile
+ EXPECT_TRUE(ds.ValidateOptions());
+}
+
+TEST_F(DumpstateTest, ValidateOptionsUpdateProgressNeedsBroadcast) {
+ ds.update_progress_ = true;
+ ds.options_.use_outfile = "a/b/c"; // update_progress_ needs outfile
+ EXPECT_FALSE(ds.ValidateOptions());
+
+ ds.options_.do_broadcast = true;
+ EXPECT_TRUE(ds.ValidateOptions());
+}
+
+TEST_F(DumpstateTest, ValidateOptionsRemoteMode) {
+ ds.options_.is_remote_mode = true;
+ EXPECT_FALSE(ds.ValidateOptions());
+
+ ds.options_.do_broadcast = true;
+ ds.options_.do_zip_file = true;
+ ds.options_.do_add_date = true;
+ ds.options_.use_outfile = "a/b/c"; // do_broadcast needs outfile
+ EXPECT_TRUE(ds.ValidateOptions());
+}
+
TEST_F(DumpstateTest, RunCommandNoArgs) {
EXPECT_EQ(-1, RunCommand("", {}));
}
diff --git a/cmds/dumpsys/OWNERS b/cmds/dumpsys/OWNERS
new file mode 100644
index 0000000..1ba7cff
--- /dev/null
+++ b/cmds/dumpsys/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+felipeal@google.com
+nandana@google.com
+jsharkey@android.com
+enh@google.com
diff --git a/cmds/installd/MatchExtensionGen.h b/cmds/installd/MatchExtensionGen.h
index fded6b7..35c3889 100644
--- a/cmds/installd/MatchExtensionGen.h
+++ b/cmds/installd/MatchExtensionGen.h
@@ -31,6 +31,7 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'p': case 'P':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
@@ -41,10 +42,15 @@
switch (ext[5]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
}
+ break;
}
+ break;
case 'a': case 'A':
switch (ext[1]) {
case 'a': case 'A':
@@ -53,7 +59,9 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'i': case 'I':
switch (ext[2]) {
case 'f': case 'F':
@@ -63,56 +71,73 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'f': case 'F':
switch (ext[4]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
}
+ break;
case 'm': case 'M':
switch (ext[2]) {
case 'r': case 'R':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'r': case 'R':
switch (ext[2]) {
case 't': case 'T':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
case 'w': case 'W':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 's': case 'S':
switch (ext[2]) {
case 'f': case 'F':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'x': case 'X':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'v': case 'V':
switch (ext[2]) {
case 'i': case 'I':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'w': case 'W':
switch (ext[2]) {
case 'b': case 'B':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
}
+ break;
case 'b': case 'B':
switch (ext[1]) {
case 'm': case 'M':
@@ -121,8 +146,11 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'c': case 'C':
switch (ext[1]) {
case 'r': case 'R':
@@ -131,8 +159,11 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'd': case 'D':
switch (ext[1]) {
case 'i': case 'I':
@@ -141,23 +172,30 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'l': case 'L':
switch (ext[2]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'n': case 'N':
switch (ext[2]) {
case 'g': case 'G':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'v': case 'V':
switch (ext[2]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'f': case 'F':
switch (ext[1]) {
case 'l': case 'L':
@@ -168,13 +206,18 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'i': case 'I':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
case 'g': case 'G':
switch (ext[1]) {
case 'i': case 'I':
@@ -183,15 +226,20 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 's': case 'S':
switch (ext[2]) {
case 'm': case 'M':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
}
+ break;
case 'j': case 'J':
switch (ext[1]) {
case 'n': case 'N':
@@ -200,7 +248,9 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'p': case 'P':
switch (ext[2]) {
case 'e': case 'E':
@@ -210,13 +260,18 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'g': case 'G':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'l': case 'L':
switch (ext[1]) {
case 's': case 'S':
@@ -225,12 +280,16 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'x': case 'X':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
case 'm': case 'M':
switch (ext[1]) {
case '3':
@@ -239,36 +298,46 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case '4':
switch (ext[2]) {
case 'a': case 'A':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'v': case 'V':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'k': case 'K':
switch (ext[2]) {
case 'a': case 'A':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'v': case 'V':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'n': case 'N':
switch (ext[2]) {
case 'g': case 'G':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'o': case 'O':
switch (ext[2]) {
case 'v': case 'V':
@@ -280,23 +349,30 @@
switch (ext[5]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
}
+ break;
case 'p': case 'P':
switch (ext[2]) {
case '2':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case '3':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case '4':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'e': case 'E':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
@@ -307,8 +383,11 @@
switch (ext[5]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
}
+ break;
case 'g': case 'G':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
@@ -316,16 +395,22 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
}
+ break;
case 'x': case 'X':
switch (ext[2]) {
case 'u': case 'U':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
case 'n': case 'N':
switch (ext[1]) {
case 'e': case 'E':
@@ -334,15 +419,20 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'r': case 'R':
switch (ext[2]) {
case 'w': case 'W':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'o': case 'O':
switch (ext[1]) {
case 'g': case 'G':
@@ -351,19 +441,25 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'g': case 'G':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'r': case 'R':
switch (ext[2]) {
case 'f': case 'F':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'p': case 'P':
switch (ext[1]) {
case 'b': case 'B':
@@ -372,68 +468,88 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'c': case 'C':
switch (ext[2]) {
case 'x': case 'X':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'e': case 'E':
switch (ext[2]) {
case 'f': case 'F':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'g': case 'G':
switch (ext[2]) {
case 'm': case 'M':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'l': case 'L':
switch (ext[2]) {
case 's': case 'S':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'n': case 'N':
switch (ext[2]) {
case 'g': case 'G':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
case 'm': case 'M':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'p': case 'P':
switch (ext[2]) {
case 'm': case 'M':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 's': case 'S':
switch (ext[2]) {
case 'd': case 'D':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'q': case 'Q':
switch (ext[1]) {
case 't': case 'T':
switch (ext[2]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'r': case 'R':
switch (ext[1]) {
case 'a': case 'A':
@@ -443,30 +559,39 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 's': case 'S':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'g': case 'G':
switch (ext[2]) {
case 'b': case 'B':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'm': case 'M':
switch (ext[2]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'w': case 'W':
switch (ext[2]) {
case '2':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 's': case 'S':
switch (ext[1]) {
case 'd': case 'D':
@@ -475,21 +600,27 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'n': case 'N':
switch (ext[2]) {
case 'd': case 'D':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'r': case 'R':
switch (ext[2]) {
case 'w': case 'W':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'v': case 'V':
switch (ext[2]) {
case 'g': case 'G':
@@ -499,9 +630,13 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
}
+ break;
case 't': case 'T':
switch (ext[1]) {
case 'i': case 'I':
@@ -513,13 +648,18 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 's': case 'S':
switch (ext[2]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'v': case 'V':
switch (ext[1]) {
case 'o': case 'O':
@@ -528,8 +668,11 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
case 'w': case 'W':
switch (ext[1]) {
case 'a': case 'A':
@@ -538,11 +681,14 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'x': case 'X':
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
}
+ break;
case 'b': case 'B':
switch (ext[2]) {
case 'm': case 'M':
@@ -551,8 +697,11 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'e': case 'E':
switch (ext[2]) {
case 'b': case 'B':
@@ -561,12 +710,16 @@
switch (ext[4]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'p': case 'P':
switch (ext[4]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
case 'm': case 'M':
switch (ext[2]) {
case '\0': return AID_MEDIA_VIDEO;
@@ -574,30 +727,39 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_AUDIO;
}
+ break;
case 'v': case 'V':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
case 'x': case 'X':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'r': case 'R':
switch (ext[2]) {
case 'f': case 'F':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
case 'v': case 'V':
switch (ext[2]) {
case 'x': case 'X':
switch (ext[3]) {
case '\0': return AID_MEDIA_VIDEO;
}
+ break;
}
+ break;
}
+ break;
case 'x': case 'X':
switch (ext[1]) {
case 'b': case 'B':
@@ -606,22 +768,29 @@
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'p': case 'P':
switch (ext[2]) {
case 'm': case 'M':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
case 'w': case 'W':
switch (ext[2]) {
case 'd': case 'D':
switch (ext[3]) {
case '\0': return AID_MEDIA_IMAGE;
}
+ break;
}
+ break;
}
+ break;
}
return 0;
diff --git a/cmds/installd/matchgen.py b/cmds/installd/matchgen.py
index 131487d..42ce82b 100644
--- a/cmds/installd/matchgen.py
+++ b/cmds/installd/matchgen.py
@@ -84,6 +84,8 @@
print "%scase '%s':" % (prefix, k)
dump(target[k], index + 1)
print "%s}" % (prefix)
+ if index > 0:
+ print "%sbreak;" % (prefix)
dump(trie, 0)
diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp
index bcdd03e..ad7a5ae 100644
--- a/cmds/installd/tests/installd_utils_test.cpp
+++ b/cmds/installd/tests/installd_utils_test.cpp
@@ -21,6 +21,7 @@
#include <gtest/gtest.h>
#include "InstalldNativeService.h"
+#include "MatchExtensionGen.h"
#include "globals.h"
#include "utils.h"
@@ -529,5 +530,19 @@
EXPECT_NE(0, validate_apk_path_subdirs("/data/app/com.example/dir/dir/dir//file"));
}
+TEST_F(UtilsTest, MatchExtension_Valid) {
+ EXPECT_EQ(AID_MEDIA_VIDEO, MatchExtension("mpg"));
+ EXPECT_EQ(AID_MEDIA_VIDEO, MatchExtension("mpeg"));
+ EXPECT_EQ(AID_MEDIA_VIDEO, MatchExtension("mPeG"));
+ EXPECT_EQ(AID_MEDIA_VIDEO, MatchExtension("MPEG"));
+}
+
+TEST_F(UtilsTest, MatchExtension_Invalid) {
+ EXPECT_EQ(0, MatchExtension("log"));
+ EXPECT_EQ(0, MatchExtension("3amp"));
+ EXPECT_EQ(0, MatchExtension("fpe"));
+ EXPECT_EQ(0, MatchExtension("docx"));
+}
+
} // namespace installd
} // namespace android
diff --git a/cmds/lshal/PipeRelay.cpp b/cmds/lshal/PipeRelay.cpp
index 3a17e03..820679f 100644
--- a/cmds/lshal/PipeRelay.cpp
+++ b/cmds/lshal/PipeRelay.cpp
@@ -16,33 +16,75 @@
#include "PipeRelay.h"
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <atomic>
+
+#include <android-base/logging.h>
#include <utils/Thread.h>
namespace android {
namespace lshal {
+static constexpr struct timeval READ_TIMEOUT { .tv_sec = 1, .tv_usec = 0 };
+
struct PipeRelay::RelayThread : public Thread {
explicit RelayThread(int fd, std::ostream &os);
bool threadLoop() override;
+ void setFinished();
private:
int mFd;
std::ostream &mOutStream;
+ // If we were to use requestExit() and exitPending() instead, threadLoop()
+ // may not run at all by the time ~PipeRelay is called (i.e. debug() has
+ // returned from HAL). By using our own flag, we ensure that select() and
+ // read() are executed until data are drained.
+ std::atomic_bool mFinished;
+
DISALLOW_COPY_AND_ASSIGN(RelayThread);
};
////////////////////////////////////////////////////////////////////////////////
PipeRelay::RelayThread::RelayThread(int fd, std::ostream &os)
- : mFd(fd),
- mOutStream(os) {
-}
+ : mFd(fd), mOutStream(os), mFinished(false) {}
bool PipeRelay::RelayThread::threadLoop() {
char buffer[1024];
- ssize_t n = read(mFd, buffer, sizeof(buffer));
+
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(mFd, &set);
+
+ struct timeval timeout = READ_TIMEOUT;
+
+ int res = TEMP_FAILURE_RETRY(select(mFd + 1, &set, nullptr, nullptr, &timeout));
+ if (res < 0) {
+ PLOG(INFO) << "select() failed";
+ return false;
+ }
+
+ if (res == 0 || !FD_ISSET(mFd, &set)) {
+ if (mFinished) {
+ LOG(WARNING) << "debug: timeout reading from pipe, output may be truncated.";
+ return false;
+ }
+ // timeout, but debug() has not returned, so wait for HAL to finish.
+ return true;
+ }
+
+ // FD_ISSET(mFd, &set) == true. Data available, start reading
+ ssize_t n = TEMP_FAILURE_RETRY(read(mFd, buffer, sizeof(buffer)));
+
+ if (n < 0) {
+ PLOG(ERROR) << "read() failed";
+ }
if (n <= 0) {
return false;
@@ -53,6 +95,10 @@
return true;
}
+void PipeRelay::RelayThread::setFinished() {
+ mFinished = true;
+}
+
////////////////////////////////////////////////////////////////////////////////
PipeRelay::PipeRelay(std::ostream &os)
@@ -77,12 +123,14 @@
PipeRelay::~PipeRelay() {
CloseFd(&mFds[1]);
- CloseFd(&mFds[0]);
if (mThread != nullptr) {
+ mThread->setFinished();
mThread->join();
mThread.clear();
}
+
+ CloseFd(&mFds[0]);
}
status_t PipeRelay::initCheck() const {
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index aca9521..f06119f 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -1,7 +1,6 @@
// Build the unit tests.
cc_test {
name: "libinput_tests",
- test_per_src: true,
srcs: [
"InputChannel_test.cpp",
"InputEvent_test.cpp",
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 43b6012..9da2e2a 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "VelocityTracker_test"
+#include <array>
#include <math.h>
#include <android-base/stringprintf.h>
@@ -32,29 +33,35 @@
// here EV = expected value, tol = VELOCITY_TOLERANCE
constexpr float VELOCITY_TOLERANCE = 0.2;
+// estimate coefficients must be within 0.001% of the target value
+constexpr float COEFFICIENT_TOLERANCE = 0.00001;
+
// --- VelocityTrackerTest ---
class VelocityTrackerTest : public testing::Test { };
-static void checkVelocity(float Vactual, float Vtarget) {
- // Compare directions
- if ((Vactual > 0 && Vtarget <= 0) || (Vactual < 0 && Vtarget >= 0)) {
- FAIL() << StringPrintf("Velocity %f does not have the same direction"
- " as the target velocity %f", Vactual, Vtarget);
- }
+/*
+ * Similar to EXPECT_NEAR, but ensures that the difference between the two float values
+ * is at most a certain fraction of the target value.
+ * If fraction is zero, require exact match.
+ */
+static void EXPECT_NEAR_BY_FRACTION(float actual, float target, float fraction) {
+ float tolerance = fabsf(target * fraction);
- // Compare magnitudes
- const float Vlower = fabsf(Vtarget * (1 - VELOCITY_TOLERANCE));
- const float Vupper = fabsf(Vtarget * (1 + VELOCITY_TOLERANCE));
- if (fabsf(Vactual) < Vlower) {
- FAIL() << StringPrintf("Velocity %f is more than %.0f%% below target velocity %f",
- Vactual, VELOCITY_TOLERANCE * 100, Vtarget);
+ if (target == 0 && fraction != 0) {
+ // If target is zero, this would force actual == target, which is too harsh.
+ // Relax this requirement a little. The value is determined empirically from the
+ // coefficients computed by the quadratic least squares algorithms.
+ tolerance = 1E-6;
}
- if (fabsf(Vactual) > Vupper) {
- FAIL() << StringPrintf("Velocity %f is more than %.0f%% above target velocity %f",
- Vactual, VELOCITY_TOLERANCE * 100, Vtarget);
- }
- SUCCEED() << StringPrintf("Velocity %f within %.0f%% of target %f)",
- Vactual, VELOCITY_TOLERANCE * 100, Vtarget);
+ EXPECT_NEAR(actual, target, tolerance);
+}
+
+static void checkVelocity(float Vactual, float Vtarget) {
+ EXPECT_NEAR_BY_FRACTION(Vactual, Vtarget, VELOCITY_TOLERANCE);
+}
+
+static void checkCoefficient(float actual, float target) {
+ EXPECT_NEAR_BY_FRACTION(actual, target, COEFFICIENT_TOLERANCE);
}
void failWithMessage(std::string message) {
@@ -123,6 +130,19 @@
delete event;
}
+static void computeAndCheckQuadraticEstimate(const Position* positions, size_t numSamples,
+ const std::array<float, 3>& coefficients) {
+ VelocityTracker vt("lsq2");
+ MotionEvent* event = createSimpleMotionEvent(positions, numSamples);
+ vt.addMovement(event);
+ VelocityTracker::Estimator estimator;
+ EXPECT_TRUE(vt.getEstimator(0, &estimator));
+ for (size_t i = 0; i< coefficients.size(); i++) {
+ checkCoefficient(estimator.xCoeff[i], coefficients[i]);
+ checkCoefficient(estimator.yCoeff[i], coefficients[i]);
+ }
+}
+
/*
* ================== VelocityTracker tests generated manually =====================================
*/
@@ -660,5 +680,114 @@
computeAndCheckVelocity(values, count, AMOTION_EVENT_AXIS_Y, 28354.796875); // lsq2
}
+/*
+ * Special care must be taken when constructing tests for LeastSquaresVelocityTrackerStrategy
+ * getEstimator function. In particular:
+ * - inside the function, time gets converted from nanoseconds to seconds
+ * before being used in the fit.
+ * - any values that are older than 100 ms are being discarded.
+ * - the newest time gets subtracted from all of the other times before being used in the fit.
+ * So these tests have to be designed with those limitations in mind.
+ *
+ * General approach for the tests below:
+ * We only used timestamps in milliseconds, 0 ms, 1 ms, and 2 ms, to be sure that
+ * we are well within the HORIZON range.
+ * When specifying the expected values of the coefficients, we treat the x values as if
+ * they were in ms. Then, to adjust for the time units, the coefficients get progressively
+ * multiplied by powers of 1E3.
+ * For example:
+ * data: t(ms), x
+ * 1 ms, 1
+ * 2 ms, 4
+ * 3 ms, 9
+ * The coefficients are (0, 0, 1).
+ * In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2).
+ */
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) {
+ Position values[] = {
+ { 0000000, 1, 1 }, // 0 s
+ { 1000000, 1, 1 }, // 0.001 s
+ { 2000000, 1, 1 }, // 0.002 s
+ };
+ // The data used for the fit will be as follows:
+ // time(s), position
+ // -0.002, 1
+ // -0.001, 1
+ // -0.000, 1
+ size_t count = sizeof(values) / sizeof(Position);
+ computeAndCheckQuadraticEstimate(values, count, std::array<float, 3>({1, 0, 0}));
+}
+
+/*
+ * Straight line y = x :: the constant and quadratic coefficients are zero.
+ */
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) {
+ Position values[] = {
+ { 0000000, -2, -2 },
+ { 1000000, -1, -1 },
+ { 2000000, -0, -0 },
+ };
+ // The data used for the fit will be as follows:
+ // time(s), position
+ // -0.002, -2
+ // -0.001, -1
+ // -0.000, 0
+ size_t count = sizeof(values) / sizeof(Position);
+ computeAndCheckQuadraticEstimate(values, count, std::array<float, 3>({0, 1E3, 0}));
+}
+
+/*
+ * Parabola
+ */
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) {
+ Position values[] = {
+ { 0000000, 1, 1 },
+ { 1000000, 4, 4 },
+ { 2000000, 8, 8 },
+ };
+ // The data used for the fit will be as follows:
+ // time(s), position
+ // -0.002, 1
+ // -0.001, 4
+ // -0.000, 8
+ size_t count = sizeof(values) / sizeof(Position);
+ computeAndCheckQuadraticEstimate(values, count, std::array<float, 3>({8, 4.5E3, 0.5E6}));
+}
+
+/*
+ * Parabola
+ */
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) {
+ Position values[] = {
+ { 0000000, 1, 1 },
+ { 1000000, 4, 4 },
+ { 2000000, 9, 9 },
+ };
+ // The data used for the fit will be as follows:
+ // time(s), position
+ // -0.002, 1
+ // -0.001, 4
+ // -0.000, 9
+ size_t count = sizeof(values) / sizeof(Position);
+ computeAndCheckQuadraticEstimate(values, count, std::array<float, 3>({9, 6E3, 1E6}));
+}
+
+/*
+ * Parabola :: y = x^2 :: the constant and linear coefficients are zero.
+ */
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) {
+ Position values[] = {
+ { 0000000, 4, 4 },
+ { 1000000, 1, 1 },
+ { 2000000, 0, 0 },
+ };
+ // The data used for the fit will be as follows:
+ // time(s), position
+ // -0.002, 4
+ // -0.001, 1
+ // -0.000, 0
+ size_t count = sizeof(values) / sizeof(Position);
+ computeAndCheckQuadraticEstimate(values, count, std::array<float, 3>({0, 0E3, 1E6}));
+}
} // namespace android
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index dd19800..afaf139 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -6,7 +6,6 @@
"InputReader_test.cpp",
"InputDispatcher_test.cpp",
],
- test_per_src: true,
cflags: [
"-Wall",
"-Werror",