auto import from //depot/cupcake/@135843
diff --git a/liblog/Android.mk b/liblog/Android.mk
new file mode 100644
index 0000000..0eec87f
--- /dev/null
+++ b/liblog/Android.mk
@@ -0,0 +1,72 @@
+#
+# Copyright (C) 2008 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.
+#
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+liblog_sources := logd_write.c
+
+# some files must not be compiled when building against Mingw
+# they correspond to features not used by our host development tools
+# which are also hard or even impossible to port to native Win32
+WITH_MINGW :=
+ifeq ($(HOST_OS),windows)
+    ifeq ($(strip $(USE_CYGWIN)),)
+        WITH_MINGW := true
+    endif
+endif
+# USE_MINGW is defined when we build against Mingw on Linux
+ifneq ($(strip $(USE_MINGW)),)
+    WITH_MINGW := true
+endif
+
+ifndef WITH_MINGW
+    liblog_sources += \
+        logprint.c \
+        event_tag_map.c
+endif
+
+liblog_host_sources := $(liblog_sources) fake_log_device.c
+
+# Static library for host
+# ========================================================
+LOCAL_MODULE := liblog
+LOCAL_SRC_FILES := $(liblog_host_sources)
+LOCAL_LDLIBS := -lpthread
+LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+ifeq ($(TARGET_SIMULATOR),true)
+  # Shared library for simulator
+  # ========================================================
+  include $(CLEAR_VARS)
+  LOCAL_MODULE := liblog
+  LOCAL_SRC_FILES := $(liblog_host_sources)
+  LOCAL_LDLIBS := -lpthread
+  LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1
+  include $(BUILD_SHARED_LIBRARY)
+else # !sim
+  # Shared and static library for target
+  # ========================================================
+  include $(CLEAR_VARS)
+  LOCAL_MODULE := liblog
+  LOCAL_SRC_FILES := $(liblog_sources)
+  include $(BUILD_STATIC_LIBRARY)
+
+  include $(CLEAR_VARS)
+  LOCAL_MODULE := liblog
+  LOCAL_WHOLE_STATIC_LIBRARIES := liblog
+  include $(BUILD_SHARED_LIBRARY)
+endif # !sim
diff --git a/liblog/event_tag_map.c b/liblog/event_tag_map.c
new file mode 100644
index 0000000..e70754e
--- /dev/null
+++ b/liblog/event_tag_map.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+#include "cutils/event_tag_map.h"
+#include "cutils/log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <assert.h>
+
+#define OUT_TAG "EventTagMap"
+
+/*
+ * Single entry.
+ */
+typedef struct EventTag {
+    unsigned int    tagIndex;
+    const char*     tagStr;
+} EventTag;
+
+/*
+ * Map.
+ */
+struct EventTagMap {
+    /* memory-mapped source file; we get strings from here */
+    void*           mapAddr;
+    size_t          mapLen;
+
+    /* array of event tags, sorted numerically by tag index */
+    EventTag*       tagArray;
+    int             numTags;
+};
+
+/* fwd */
+static int processFile(EventTagMap* map);
+static int countMapLines(const EventTagMap* map);
+static int parseMapLines(EventTagMap* map);
+static int scanTagLine(char** pData, EventTag* tag, int lineNum);
+static int sortTags(EventTagMap* map);
+static void dumpTags(const EventTagMap* map);
+
+
+/*
+ * Open the map file and allocate a structure to manage it.
+ *
+ * We create a private mapping because we want to terminate the log tag
+ * strings with '\0'.
+ */
+EventTagMap* android_openEventTagMap(const char* fileName)
+{
+    EventTagMap* newTagMap;
+    off_t end;
+    int fd = -1;
+
+    newTagMap = calloc(1, sizeof(EventTagMap));
+    if (newTagMap == NULL)
+        return NULL;
+
+    fd = open(fileName, O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "%s: unable to open map '%s': %s\n",
+            OUT_TAG, fileName, strerror(errno));
+        goto fail;
+    }
+
+    end = lseek(fd, 0L, SEEK_END);
+    (void) lseek(fd, 0L, SEEK_SET);
+    if (end < 0) {
+        fprintf(stderr, "%s: unable to seek map '%s'\n", OUT_TAG, fileName);
+        goto fail;
+    }
+
+    newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE,
+                                fd, 0);
+    if (newTagMap->mapAddr == MAP_FAILED) {
+        fprintf(stderr, "%s: mmap(%s) failed: %s\n",
+            OUT_TAG, fileName, strerror(errno));
+        goto fail;
+    }
+    newTagMap->mapLen = end;
+
+    if (processFile(newTagMap) != 0)
+        goto fail;
+
+    return newTagMap;
+
+fail:
+    android_closeEventTagMap(newTagMap);
+    if (fd >= 0)
+        close(fd);
+    return NULL;
+}
+
+/*
+ * Close the map.
+ */
+void android_closeEventTagMap(EventTagMap* map)
+{
+    if (map == NULL)
+        return;
+
+    munmap(map->mapAddr, map->mapLen);
+    free(map);
+}
+
+/*
+ * Look up an entry in the map.
+ *
+ * The entries are sorted by tag number, so we can do a binary search.
+ */
+const char* android_lookupEventTag(const EventTagMap* map, int tag)
+{
+    int hi, lo, mid;
+
+    lo = 0;
+    hi = map->numTags-1;
+
+    while (lo <= hi) {
+        int cmp;
+
+        mid = (lo+hi)/2;
+        cmp = map->tagArray[mid].tagIndex - tag;
+        if (cmp < 0) {
+            /* tag is bigger */
+            lo = mid + 1;
+        } else if (cmp > 0) {
+            /* tag is smaller */
+            hi = mid - 1;
+        } else {
+            /* found */
+            return map->tagArray[mid].tagStr;
+        }
+    }
+
+    return NULL;
+}
+
+
+
+/*
+ * Determine whether "c" is a whitespace char.
+ */
+static inline int isCharWhitespace(char c)
+{
+    return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
+}
+
+/*
+ * Determine whether "c" is a valid tag char.
+ */
+static inline int isCharValidTag(char c)
+{
+    return ((c >= 'A' && c <= 'Z') ||
+            (c >= 'a' && c <= 'z') ||
+            (c >= '0' && c <= '9') ||
+            (c == '_'));
+}
+
+/*
+ * Determine whether "c" is a valid decimal digit.
+ */
+static inline int isCharDigit(char c)
+{
+    return (c >= '0' && c <= '9');
+}
+
+
+/*
+ * Crunch through the file, parsing the contents and creating a tag index.
+ */
+static int processFile(EventTagMap* map)
+{
+    EventTag* tagArray = NULL;
+
+    /* get a tag count */
+    map->numTags = countMapLines(map);
+    if (map->numTags < 0)
+        return -1;
+
+    //printf("+++ found %d tags\n", map->numTags);
+
+    /* allocate storage for the tag index array */
+    map->tagArray = calloc(1, sizeof(EventTag) * map->numTags);
+    if (map->tagArray == NULL)
+        return -1;
+
+    /* parse the file, null-terminating tag strings */
+    if (parseMapLines(map) != 0) {
+        fprintf(stderr, "%s: file parse failed\n", OUT_TAG);
+        return -1;
+    }
+
+    /* sort the tags and check for duplicates */
+    if (sortTags(map) != 0)
+        return -1;
+
+    return 0;
+}
+
+/*
+ * Run through all lines in the file, determining whether they're blank,
+ * comments, or possibly have a tag entry.
+ *
+ * This is a very "loose" scan.  We don't try to detect syntax errors here.
+ * The later pass is more careful, but the number of tags found there must
+ * match the number of tags found here.
+ *
+ * Returns the number of potential tag entries found.
+ */
+static int countMapLines(const EventTagMap* map)
+{
+    int numTags, unknown;
+    const char* cp;
+    const char* endp;
+
+    cp = (const char*) map->mapAddr;
+    endp = cp + map->mapLen;
+
+    numTags = 0;
+    unknown = 1;
+    while (cp < endp) {
+        if (*cp == '\n') {
+            unknown = 1;
+        } else if (unknown) {
+            if (isCharDigit(*cp)) {
+                /* looks like a tag to me */
+                numTags++;
+                unknown = 0;
+            } else if (isCharWhitespace(*cp)) {
+                /* might be leading whitespace before tag num, keep going */
+            } else {
+                /* assume comment; second pass can complain in detail */
+                unknown = 0;
+            }
+        } else {
+            /* we've made up our mind; just scan to end of line */
+        }
+        cp++;
+    }
+
+    return numTags;
+}
+
+/*
+ * Parse the tags out of the file.
+ */
+static int parseMapLines(EventTagMap* map)
+{
+    int tagNum, lineStart, lineNum;
+    char* cp;
+    char* endp;
+
+    cp = (char*) map->mapAddr;
+    endp = cp + map->mapLen;
+
+    /* insist on EOL at EOF; simplifies parsing and null-termination */
+    if (*(endp-1) != '\n') {
+        fprintf(stderr, "%s: map file missing EOL on last line\n", OUT_TAG);
+        return -1;
+    }
+
+    tagNum = 0;
+    lineStart = 1;
+    lineNum = 1;
+    while (cp < endp) {
+        //printf("{%02x}", *cp); fflush(stdout);
+        if (*cp == '\n') {
+            lineStart = 1;
+            lineNum++;
+        } else if (lineStart) {
+            if (*cp == '#') {
+                /* comment; just scan to end */
+                lineStart = 0;
+            } else if (isCharDigit(*cp)) {
+                /* looks like a tag; scan it out */
+                if (tagNum >= map->numTags) {
+                    fprintf(stderr,
+                        "%s: more tags than expected (%d)\n", OUT_TAG, tagNum);
+                    return -1;
+                }
+                if (scanTagLine(&cp, &map->tagArray[tagNum], lineNum) != 0)
+                    return -1;
+                tagNum++;
+                lineNum++;      // we eat the '\n'
+                /* leave lineStart==1 */
+            } else if (isCharWhitespace(*cp)) {
+                /* looks like leading whitespace; keep scanning */
+            } else {
+                fprintf(stderr,
+                    "%s: unexpected chars (0x%02x) in tag number on line %d\n",
+                    OUT_TAG, *cp, lineNum);
+                return -1;
+            }
+        } else {
+            /* this is a blank or comment line */
+        }
+        cp++;
+    }
+
+    if (tagNum != map->numTags) {
+        fprintf(stderr, "%s: parsed %d tags, expected %d\n",
+            OUT_TAG, tagNum, map->numTags);
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Scan one tag line.
+ *
+ * "*pData" should be pointing to the first digit in the tag number.  On
+ * successful return, it will be pointing to the last character in the
+ * tag line (i.e. the character before the start of the next line).
+ *
+ * Returns 0 on success, nonzero on failure.
+ */
+static int scanTagLine(char** pData, EventTag* tag, int lineNum)
+{
+    char* cp = *pData;
+    char* startp;
+    char* endp;
+    unsigned long val;
+
+    startp = cp;
+    while (isCharDigit(*++cp))
+        ;
+    *cp = '\0';
+
+    val = strtoul(startp, &endp, 10);
+    assert(endp == cp);
+    if (endp != cp)
+        fprintf(stderr, "ARRRRGH\n");
+
+    tag->tagIndex = val;
+
+    while (*++cp != '\n' && isCharWhitespace(*cp))
+        ;
+
+    if (*cp == '\n') {
+        fprintf(stderr,
+            "%s: missing tag string on line %d\n", OUT_TAG, lineNum);
+        return -1;
+    }
+
+    tag->tagStr = cp;
+
+    while (isCharValidTag(*++cp))
+        ;
+
+    if (*cp == '\n') {
+        /* null terminate and return */
+        *cp = '\0';
+    } else if (isCharWhitespace(*cp)) {
+        /* CRLF or trailin spaces; zap this char, then scan for the '\n' */
+        *cp = '\0';
+
+        /* just ignore the rest of the line till \n
+        TODO: read the tag description that follows the tag name
+        */
+        while (*++cp != '\n') {
+        }
+    } else {
+        fprintf(stderr,
+            "%s: invalid tag chars on line %d\n", OUT_TAG, lineNum);
+        return -1;
+    }
+
+    *pData = cp;
+
+    //printf("+++ Line %d: got %d '%s'\n", lineNum, tag->tagIndex, tag->tagStr);
+    return 0;
+}
+
+/*
+ * Compare two EventTags.
+ */
+static int compareEventTags(const void* v1, const void* v2)
+{
+    const EventTag* tag1 = (const EventTag*) v1;
+    const EventTag* tag2 = (const EventTag*) v2;
+
+    return tag1->tagIndex - tag2->tagIndex;
+}
+
+/*
+ * Sort the EventTag array so we can do fast lookups by tag index.  After
+ * the sort we do a quick check for duplicate tag indices.
+ *
+ * Returns 0 on success.
+ */
+static int sortTags(EventTagMap* map)
+{
+    int i;
+
+    qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
+
+    for (i = 1; i < map->numTags; i++) {
+        if (map->tagArray[i].tagIndex == map->tagArray[i-1].tagIndex) {
+            fprintf(stderr, "%s: duplicate tag entries (%d:%s and %d:%s)\n",
+                OUT_TAG,
+                map->tagArray[i].tagIndex, map->tagArray[i].tagStr,
+                map->tagArray[i-1].tagIndex, map->tagArray[i-1].tagStr);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Dump the tag array for debugging.
+ */
+static void dumpTags(const EventTagMap* map)
+{
+    int i;
+
+    for (i = 0; i < map->numTags; i++) {
+        const EventTag* tag = &map->tagArray[i];
+        printf("  %3d: %6d '%s'\n", i, tag->tagIndex, tag->tagStr);
+    }
+}
+
diff --git a/liblog/fake_log_device.c b/liblog/fake_log_device.c
new file mode 100644
index 0000000..ed9d699
--- /dev/null
+++ b/liblog/fake_log_device.c
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+/*
+ * Intercepts log messages intended for the Android log device.
+ * When running in the context of the simulator, the messages are
+ * passed on to the underlying (fake) log device.  When not in the
+ * simulator, messages are printed to stderr.
+ */
+#include "cutils/logd.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef HAVE_PTHREADS
+#include <pthread.h>
+#endif
+
+#define kMaxTagLen  16      /* from the long-dead utils/Log.cpp */
+
+#define kTagSetSize 16      /* arbitrary */
+
+#if 0
+#define TRACE(...) printf("fake_log_device: " __VA_ARGS__)
+#else
+#define TRACE(...) ((void)0)
+#endif
+
+/* from the long-dead utils/Log.cpp */
+typedef enum {
+    FORMAT_OFF = 0,
+    FORMAT_BRIEF,
+    FORMAT_PROCESS,
+    FORMAT_TAG,
+    FORMAT_THREAD,
+    FORMAT_RAW,
+    FORMAT_TIME,
+    FORMAT_THREADTIME,
+    FORMAT_LONG
+} LogFormat;
+
+
+/*
+ * Log driver state.
+ */
+typedef struct LogState {
+    /* the fake fd that's seen by the user */
+    int     fakeFd;
+
+    /* a printable name for this fake device */
+    char   *debugName;
+
+    /* nonzero if this is a binary log */
+    int     isBinary;
+
+    /* global minimum priority */
+    int     globalMinPriority;
+
+    /* output format */
+    LogFormat outputFormat;
+
+    /* tags and priorities */
+    struct {
+        char    tag[kMaxTagLen];
+        int     minPriority;
+    } tagSet[kTagSetSize];
+} LogState;
+
+
+#ifdef HAVE_PTHREADS
+/*
+ * Locking.  Since we're emulating a device, we need to be prepared
+ * to have multiple callers at the same time.  This lock is used
+ * to both protect the fd list and to prevent LogStates from being
+ * freed out from under a user.
+ */
+static pthread_mutex_t fakeLogDeviceLock = PTHREAD_MUTEX_INITIALIZER;
+
+static void lock()
+{
+    pthread_mutex_lock(&fakeLogDeviceLock);
+}
+
+static void unlock()
+{
+    pthread_mutex_unlock(&fakeLogDeviceLock);
+}
+#else   // !HAVE_PTHREADS
+#define lock() ((void)0)
+#define unlock() ((void)0)
+#endif  // !HAVE_PTHREADS
+
+
+/*
+ * File descriptor management.
+ */
+#define FAKE_FD_BASE 10000
+#define MAX_OPEN_LOGS 16
+static LogState *openLogTable[MAX_OPEN_LOGS];
+
+/*
+ * Allocate an fd and associate a new LogState with it.
+ * The fd is available via the fakeFd field of the return value.
+ */
+static LogState *createLogState()
+{
+    size_t i;
+
+    for (i = 0; i < sizeof(openLogTable); i++) {
+        if (openLogTable[i] == NULL) {
+            openLogTable[i] = calloc(1, sizeof(LogState));
+            openLogTable[i]->fakeFd = FAKE_FD_BASE + i;
+            return openLogTable[i];
+        }
+    }
+    return NULL;
+}
+
+/*
+ * Translate an fd to a LogState.
+ */
+static LogState *fdToLogState(int fd)
+{
+    if (fd >= FAKE_FD_BASE && fd < FAKE_FD_BASE + MAX_OPEN_LOGS) {
+        return openLogTable[fd - FAKE_FD_BASE];
+    }
+    return NULL;
+}
+
+/*
+ * Unregister the fake fd and free the memory it pointed to.
+ */
+static void deleteFakeFd(int fd)
+{
+    LogState *ls;
+
+    lock();
+
+    ls = fdToLogState(fd);
+    if (ls != NULL) {
+        openLogTable[fd - FAKE_FD_BASE] = NULL;
+        free(ls->debugName);
+        free(ls);
+    }
+
+    unlock();
+}
+
+/*
+ * Configure logging based on ANDROID_LOG_TAGS environment variable.  We
+ * need to parse a string that looks like
+ *
+ *   *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
+ *
+ * The tag (or '*' for the global level) comes first, followed by a colon
+ * and a letter indicating the minimum priority level we're expected to log.
+ * This can be used to reveal or conceal logs with specific tags.
+ *
+ * We also want to check ANDROID_PRINTF_LOG to determine how the output
+ * will look.
+ */
+static void configureInitialState(const char* pathName, LogState* logState)
+{
+    static const int kDevLogLen = sizeof("/dev/log/") - 1;
+
+    logState->debugName = strdup(pathName);
+
+    /* identify binary logs */
+    if (strcmp(pathName + kDevLogLen, "events") == 0) {
+        logState->isBinary = 1;
+    }
+
+    /* global min priority defaults to "info" level */
+    logState->globalMinPriority = ANDROID_LOG_INFO;
+
+    /*
+     * This is based on the the long-dead utils/Log.cpp code.
+     */
+    const char* tags = getenv("ANDROID_LOG_TAGS");
+    TRACE("Found ANDROID_LOG_TAGS='%s'\n", tags);
+    if (tags != NULL) {
+        int entry = 0;
+
+        while (*tags != '\0') {
+            char tagName[kMaxTagLen];
+            int i, minPrio;
+
+            while (isspace(*tags))
+                tags++;
+
+            i = 0;
+            while (*tags != '\0' && !isspace(*tags) && *tags != ':' &&
+                i < kMaxTagLen)
+            {
+                tagName[i++] = *tags++;
+            }
+            if (i == kMaxTagLen) {
+                TRACE("ERROR: env tag too long (%d chars max)\n", kMaxTagLen-1);
+                return;
+            }
+            tagName[i] = '\0';
+
+            /* default priority, if there's no ":" part; also zero out '*' */
+            minPrio = ANDROID_LOG_VERBOSE;
+            if (tagName[0] == '*' && tagName[1] == '\0') {
+                minPrio = ANDROID_LOG_DEBUG;
+                tagName[0] = '\0';
+            }
+
+            if (*tags == ':') {
+                tags++;
+                if (*tags >= '0' && *tags <= '9') {
+                    if (*tags >= ('0' + ANDROID_LOG_SILENT))
+                        minPrio = ANDROID_LOG_VERBOSE;
+                    else
+                        minPrio = *tags - '\0';
+                } else {
+                    switch (*tags) {
+                    case 'v':   minPrio = ANDROID_LOG_VERBOSE;  break;
+                    case 'd':   minPrio = ANDROID_LOG_DEBUG;    break;
+                    case 'i':   minPrio = ANDROID_LOG_INFO;     break;
+                    case 'w':   minPrio = ANDROID_LOG_WARN;     break;
+                    case 'e':   minPrio = ANDROID_LOG_ERROR;    break;
+                    case 'f':   minPrio = ANDROID_LOG_FATAL;    break;
+                    case 's':   minPrio = ANDROID_LOG_SILENT;   break;
+                    default:    minPrio = ANDROID_LOG_DEFAULT;  break;
+                    }
+                }
+
+                tags++;
+                if (*tags != '\0' && !isspace(*tags)) {
+                    TRACE("ERROR: garbage in tag env; expected whitespace\n");
+                    TRACE("       env='%s'\n", tags);
+                    return;
+                }
+            }
+
+            if (tagName[0] == 0) {
+                logState->globalMinPriority = minPrio;
+                TRACE("+++ global min prio %d\n", logState->globalMinPriority);
+            } else {
+                logState->tagSet[entry].minPriority = minPrio;
+                strcpy(logState->tagSet[entry].tag, tagName);
+                TRACE("+++ entry %d: %s:%d\n",
+                    entry,
+                    logState->tagSet[entry].tag,
+                    logState->tagSet[entry].minPriority);
+                entry++;
+            }
+        }
+    }
+
+
+    /*
+     * Taken from the long-dead utils/Log.cpp
+     */
+    const char* fstr = getenv("ANDROID_PRINTF_LOG");
+    LogFormat format;
+    if (fstr == NULL) {
+        format = FORMAT_BRIEF;
+    } else {
+        if (strcmp(fstr, "brief") == 0)
+            format = FORMAT_BRIEF;
+        else if (strcmp(fstr, "process") == 0)
+            format = FORMAT_PROCESS;
+        else if (strcmp(fstr, "tag") == 0)
+            format = FORMAT_PROCESS;
+        else if (strcmp(fstr, "thread") == 0)
+            format = FORMAT_PROCESS;
+        else if (strcmp(fstr, "raw") == 0)
+            format = FORMAT_PROCESS;
+        else if (strcmp(fstr, "time") == 0)
+            format = FORMAT_PROCESS;
+        else if (strcmp(fstr, "long") == 0)
+            format = FORMAT_PROCESS;
+        else
+            format = (LogFormat) atoi(fstr);        // really?!
+    }
+
+    logState->outputFormat = format;
+}
+
+/*
+ * Return a human-readable string for the priority level.  Always returns
+ * a valid string.
+ */
+static const char* getPriorityString(int priority)
+{
+    /* the first character of each string should be unique */
+    static const char* priorityStrings[] = {
+        "Verbose", "Debug", "Info", "Warn", "Error", "Assert"
+    };
+    int idx;
+
+    idx = (int) priority - (int) ANDROID_LOG_VERBOSE;
+    if (idx < 0 ||
+        idx >= (int) (sizeof(priorityStrings) / sizeof(priorityStrings[0])))
+        return "?unknown?";
+    return priorityStrings[idx];
+}
+
+#ifndef HAVE_WRITEV
+/*
+ * Some platforms like WIN32 do not have writev().
+ * Make up something to replace it.
+ */
+static ssize_t fake_writev(int fd, const struct iovec *iov, int iovcnt) {
+    int result = 0;
+    struct iovec* end = iov + iovcnt;
+    for (; iov < end; iov++) {
+        int w = write(fd, iov->iov_base, iov->iov_len);
+        if (w != iov->iov_len) {
+            if (w < 0)
+                return w;
+            return result + w;
+        }
+        result += w;
+    }
+    return result;
+}
+
+#define writev fake_writev
+#endif
+
+
+/*
+ * Write a filtered log message to stderr.
+ *
+ * Log format parsing taken from the long-dead utils/Log.cpp.
+ */
+static void showLog(LogState *state,
+        int logPrio, const char* tag, const char* msg)
+{
+#if defined(HAVE_LOCALTIME_R)
+    struct tm tmBuf;
+#endif
+    struct tm* ptm;
+    char timeBuf[32];
+    char prefixBuf[128], suffixBuf[128];
+    char priChar;
+    time_t when;
+    pid_t pid, tid;
+
+    TRACE("LOG %d: %s %s", logPrio, tag, msg);
+
+    priChar = getPriorityString(logPrio)[0];
+    when = time(NULL);
+    pid = tid = getpid();       // find gettid()?
+
+    /*
+     * Get the current date/time in pretty form
+     *
+     * It's often useful when examining a log with "less" to jump to
+     * a specific point in the file by searching for the date/time stamp.
+     * For this reason it's very annoying to have regexp meta characters
+     * in the time stamp.  Don't use forward slashes, parenthesis,
+     * brackets, asterisks, or other special chars here.
+     */
+#if defined(HAVE_LOCALTIME_R)
+    ptm = localtime_r(&when, &tmBuf);
+#else
+    ptm = localtime(&when);
+#endif
+    //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
+    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
+
+    /*
+     * Construct a buffer containing the log header and log message.
+     */
+    size_t prefixLen, suffixLen;
+
+    switch (state->outputFormat) {
+    case FORMAT_TAG:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "%c/%-8s: ", priChar, tag);
+        strcpy(suffixBuf, "\n"); suffixLen = 1;
+        break;
+    case FORMAT_PROCESS:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "%c(%5d) ", priChar, pid);
+        suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
+            "  (%s)\n", tag);
+        break;
+    case FORMAT_THREAD:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "%c(%5d:%p) ", priChar, pid, (void*)tid);
+        strcpy(suffixBuf, "\n"); suffixLen = 1;
+        break;
+    case FORMAT_RAW:
+        prefixBuf[0] = 0; prefixLen = 0;
+        strcpy(suffixBuf, "\n"); suffixLen = 1;
+        break;
+    case FORMAT_TIME:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "%s %-8s\n\t", timeBuf, tag);
+        strcpy(suffixBuf, "\n"); suffixLen = 1;
+        break;
+    case FORMAT_THREADTIME:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "%s %5d %5d %c %-8s \n\t", timeBuf, pid, tid, priChar, tag);
+        strcpy(suffixBuf, "\n"); suffixLen = 1;
+        break;
+    case FORMAT_LONG:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "[ %s %5d:%p %c/%-8s ]\n",
+            timeBuf, pid, (void*)tid, priChar, tag);
+        strcpy(suffixBuf, "\n\n"); suffixLen = 2;
+        break;
+    default:
+        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+            "%c/%-8s(%5d): ", priChar, tag, pid);
+        strcpy(suffixBuf, "\n"); suffixLen = 1;
+        break;
+     }
+
+    /*
+     * Figure out how many lines there will be.
+     */
+    const char* end = msg + strlen(msg);
+    size_t numLines = 0;
+    const char* p = msg;
+    while (p < end) {
+        if (*p++ == '\n') numLines++;
+    }
+    if (p > msg && *(p-1) != '\n') numLines++;
+
+    /*
+     * Create an array of iovecs large enough to write all of
+     * the lines with a prefix and a suffix.
+     */
+    const size_t INLINE_VECS = 6;
+    const size_t MAX_LINES   = ((size_t)~0)/(3*sizeof(struct iovec*));
+    struct iovec stackVec[INLINE_VECS];
+    struct iovec* vec = stackVec;
+    size_t numVecs;
+
+    if (numLines > MAX_LINES)
+        numLines = MAX_LINES;
+
+    numVecs = numLines*3;  // 3 iovecs per line.
+    if (numVecs > INLINE_VECS) {
+        vec = (struct iovec*)malloc(sizeof(struct iovec)*numLines);
+        if (vec == NULL) {
+            msg = "LOG: write failed, no memory";
+            numVecs = 3;
+            numLines = 1;
+            vec = stackVec;
+        }
+    }
+
+    /*
+     * Fill in the iovec pointers.
+     */
+    p = msg;
+    struct iovec* v = vec;
+    int totalLen = 0;
+    while (numLines > 0 && p < end) {
+        if (prefixLen > 0) {
+            v->iov_base = prefixBuf;
+            v->iov_len = prefixLen;
+            totalLen += prefixLen;
+            v++;
+        }
+        const char* start = p;
+        while (p < end && *p != '\n') p++;
+        if ((p-start) > 0) {
+            v->iov_base = (void*)start;
+            v->iov_len = p-start;
+            totalLen += p-start;
+            v++;
+        }
+        if (*p == '\n') p++;
+        if (suffixLen > 0) {
+            v->iov_base = suffixBuf;
+            v->iov_len = suffixLen;
+            totalLen += suffixLen;
+            v++;
+        }
+        numLines -= 1;
+    }
+    
+    /*
+     * Write the entire message to the log file with a single writev() call.
+     * We need to use this rather than a collection of printf()s on a FILE*
+     * because of multi-threading and multi-process issues.
+     *
+     * If the file was not opened with O_APPEND, this will produce interleaved
+     * output when called on the same file from multiple processes.
+     *
+     * If the file descriptor is actually a network socket, the writev()
+     * call may return with a partial write.  Putting the writev() call in
+     * a loop can result in interleaved data.  This can be alleviated
+     * somewhat by wrapping the writev call in the Mutex.
+     */
+
+    for(;;) {
+        int cc = writev(fileno(stderr), vec, v-vec);
+
+        if (cc == totalLen) break;
+        
+        if (cc < 0) {
+            if(errno == EINTR) continue;
+            
+                /* can't really log the failure; for now, throw out a stderr */
+            fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
+            break;
+        } else {
+                /* shouldn't happen when writing to file or tty */
+            fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", cc, totalLen);
+            break;
+        }
+    }
+
+    /* if we allocated storage for the iovecs, free it */
+    if (vec != stackVec)
+        free(vec);
+}
+
+
+/*
+ * Receive a log message.  We happen to know that "vector" has three parts:
+ *
+ *  priority (1 byte)
+ *  tag (N bytes -- null-terminated ASCII string)
+ *  message (N bytes -- null-terminated ASCII string)
+ */
+static ssize_t logWritev(int fd, const struct iovec* vector, int count)
+{
+    LogState* state;
+
+    /* Make sure that no-one frees the LogState while we're using it.
+     * Also guarantees that only one thread is in showLog() at a given
+     * time (if it matters).
+     */
+    lock();
+
+    state = fdToLogState(fd);
+    if (state == NULL) {
+        errno = EBADF;
+        goto error;
+    }
+
+    if (state->isBinary) {
+        TRACE("%s: ignoring binary log\n", state->debugName);
+        goto bail;
+    }
+
+    if (count != 3) {
+        TRACE("%s: writevLog with count=%d not expected\n",
+            state->debugName, count);
+        goto error;
+    }
+
+    /* pull out the three fields */
+    int logPrio = *(const char*)vector[0].iov_base;
+    const char* tag = (const char*) vector[1].iov_base;
+    const char* msg = (const char*) vector[2].iov_base;
+
+    /* see if this log tag is configured */
+    int i;
+    int minPrio = state->globalMinPriority;
+    for (i = 0; i < kTagSetSize; i++) {
+        if (state->tagSet[i].minPriority == ANDROID_LOG_UNKNOWN)
+            break;      /* reached end of configured values */
+
+        if (strcmp(state->tagSet[i].tag, tag) == 0) {
+            //TRACE("MATCH tag '%s'\n", tag);
+            minPrio = state->tagSet[i].minPriority;
+            break;
+        }
+    }
+
+    if (logPrio >= minPrio) {
+        showLog(state, logPrio, tag, msg);
+    } else {
+        //TRACE("+++ NOLOG(%d): %s %s", logPrio, tag, msg);
+    }
+
+bail:
+    unlock();
+    return vector[0].iov_len + vector[1].iov_len + vector[2].iov_len;
+error:
+    unlock();
+    return -1;
+}
+
+/*
+ * Free up our state and close the fake descriptor.
+ */
+static int logClose(int fd)
+{
+    deleteFakeFd(fd);
+    return 0;
+}
+
+/*
+ * Open a log output device and return a fake fd.
+ */
+static int logOpen(const char* pathName, int flags)
+{
+    LogState *logState;
+    int fd = -1;
+
+    lock();
+
+    logState = createLogState();
+    if (logState != NULL) {
+        configureInitialState(pathName, logState);
+        fd = logState->fakeFd;
+    } else  {
+        errno = ENFILE;
+    }
+
+    unlock();
+
+    return fd;
+}
+
+
+/*
+ * Runtime redirection.  If this binary is running in the simulator,
+ * just pass log messages to the emulated device.  If it's running
+ * outside of the simulator, write the log messages to stderr.
+ */
+
+static int (*redirectOpen)(const char *pathName, int flags) = NULL;
+static int (*redirectClose)(int fd) = NULL;
+static ssize_t (*redirectWritev)(int fd, const struct iovec* vector, int count)
+        = NULL;
+
+static void setRedirects()
+{
+    const char *ws;
+
+    /* Wrapsim sets this environment variable on children that it's
+     * created using its LD_PRELOAD wrapper.
+     */
+    ws = getenv("ANDROID_WRAPSIM");
+    if (ws != NULL && strcmp(ws, "1") == 0) {
+        /* We're running inside wrapsim, so we can just write to the device. */
+        redirectOpen = (int (*)(const char *pathName, int flags))open;
+        redirectClose = close;
+        redirectWritev = writev;
+    } else {
+        /* There's no device to delegate to; handle the logging ourselves. */
+        redirectOpen = logOpen;
+        redirectClose = logClose;
+        redirectWritev = logWritev;
+    }
+}
+
+int fakeLogOpen(const char *pathName, int flags)
+{
+    if (redirectOpen == NULL) {
+        setRedirects();
+    }
+    return redirectOpen(pathName, flags);
+}
+
+int fakeLogClose(int fd)
+{
+    /* Assume that open() was called first. */
+    return redirectClose(fd);
+}
+
+ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count)
+{
+    /* Assume that open() was called first. */
+    return redirectWritev(fd, vector, count);
+}
diff --git a/liblog/logd_write.c b/liblog/logd_write.c
new file mode 100644
index 0000000..80867d1
--- /dev/null
+++ b/liblog/logd_write.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+#include <time.h>
+#include <stdio.h>
+#ifdef HAVE_PTHREADS
+#include <pthread.h>
+#endif
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <cutils/logger.h>
+#include <cutils/logd.h>
+
+#define LOG_BUF_SIZE	1024
+
+#if FAKE_LOG_DEVICE
+// This will be defined when building for the host.
+#define log_open(pathname, flags) fakeLogOpen(pathname, flags)
+#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
+#define log_close(filedes) fakeLogClose(filedes)
+#else
+#define log_open(pathname, flags) open(pathname, flags)
+#define log_writev(filedes, vector, count) writev(filedes, vector, count)
+#define log_close(filedes) close(filedes)
+#endif
+
+typedef enum {
+    LOG_ID_MAIN = 0,
+    LOG_ID_RADIO,
+    LOG_ID_EVENTS,
+    LOG_ID_MAX
+} log_id_t;
+
+static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
+static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) =
+    __write_to_log_init;
+#ifdef HAVE_PTHREADS
+static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1 };
+
+/*
+ * This is used by the C++ code to decide if it should write logs through
+ * the C code.  Basically, if /dev/log/... is available, we're running in
+ * the simulator rather than a desktop tool and want to use the device.
+ */
+static enum {
+    kLogUninitialized, kLogNotAvailable, kLogAvailable 
+} g_log_status = kLogUninitialized;
+int __android_log_dev_available(void)
+{
+    if (g_log_status == kLogUninitialized) {
+        if (access("/dev/"LOGGER_LOG_MAIN, W_OK) == 0)
+            g_log_status = kLogAvailable;
+        else
+            g_log_status = kLogNotAvailable;
+    }
+
+    return (g_log_status == kLogAvailable);
+}
+
+static int __write_to_log_null(log_id_t log_fd, struct iovec *vec, size_t nr)
+{
+    return -1;
+}
+
+static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
+{
+    ssize_t ret;
+    int log_fd;
+
+    if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
+        log_fd = log_fds[(int)log_id];
+    } else {
+        return EBADF;
+    }
+
+    do {
+        ret = log_writev(log_fd, vec, nr);
+    } while (ret < 0 && errno == EINTR);
+
+    return ret;
+}
+
+static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
+{
+#ifdef HAVE_PTHREADS
+    pthread_mutex_lock(&log_init_lock);
+#endif
+
+    if (write_to_log == __write_to_log_init) {
+        log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
+        log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
+        log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
+
+        write_to_log = __write_to_log_kernel;
+
+        if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
+                log_fds[LOG_ID_EVENTS] < 0) {
+            log_close(log_fds[LOG_ID_MAIN]);
+            log_close(log_fds[LOG_ID_RADIO]);
+            log_close(log_fds[LOG_ID_EVENTS]);
+            log_fds[LOG_ID_MAIN] = -1;
+            log_fds[LOG_ID_RADIO] = -1;
+            log_fds[LOG_ID_EVENTS] = -1;
+            write_to_log = __write_to_log_null;
+        }
+    }
+
+#ifdef HAVE_PTHREADS
+    pthread_mutex_unlock(&log_init_lock);
+#endif
+
+    return write_to_log(log_id, vec, nr);
+}
+
+int __android_log_write(int prio, const char *tag, const char *msg)
+{
+    struct iovec vec[3];
+    log_id_t log_id = LOG_ID_MAIN;
+
+    if (!tag)
+        tag = "";
+
+    /* XXX: This needs to go! */
+    if (!strcmp(tag, "HTC_RIL") ||
+        !strcmp(tag, "RILJ") ||
+        !strcmp(tag, "RILC") ||
+        !strcmp(tag, "RILD") ||
+        !strcmp(tag, "RIL") ||
+        !strcmp(tag, "AT") ||
+        !strcmp(tag, "GSM") ||
+        !strcmp(tag, "STK"))
+            log_id = LOG_ID_RADIO;
+
+    vec[0].iov_base   = (unsigned char *) &prio;
+    vec[0].iov_len    = 1;
+    vec[1].iov_base   = (void *) tag;
+    vec[1].iov_len    = strlen(tag) + 1;
+    vec[2].iov_base   = (void *) msg;
+    vec[2].iov_len    = strlen(msg) + 1;
+
+    return write_to_log(log_id, vec, 3);
+}
+
+int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
+{
+    char buf[LOG_BUF_SIZE];    
+
+    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+
+    return __android_log_write(prio, tag, buf);
+}
+
+int __android_log_print(int prio, const char *tag, const char *fmt, ...)
+{
+    va_list ap;
+    char buf[LOG_BUF_SIZE];    
+
+    va_start(ap, fmt);
+    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+    va_end(ap);
+
+    return __android_log_write(prio, tag, buf);
+}
+
+void __android_log_assert(const char *cond, const char *tag,
+			  const char *fmt, ...)
+{
+    va_list ap;
+    char buf[LOG_BUF_SIZE];    
+
+    va_start(ap, fmt);
+    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+    va_end(ap);
+
+    __android_log_write(ANDROID_LOG_FATAL, tag, buf);
+
+    __builtin_trap(); /* trap so we have a chance to debug the situation */
+}
+
+int __android_log_bwrite(int32_t tag, const void *payload, size_t len)
+{
+    struct iovec vec[2];
+
+    vec[0].iov_base = &tag;
+    vec[0].iov_len = sizeof(tag);
+    vec[1].iov_base = (void*)payload;
+    vec[1].iov_len = len;
+
+    return write_to_log(LOG_ID_EVENTS, vec, 2);
+}
+
+/*
+ * Like __android_log_bwrite, but takes the type as well.  Doesn't work
+ * for the general case where we're generating lists of stuff, but very
+ * handy if we just want to dump an integer into the log.
+ */
+int __android_log_btwrite(int32_t tag, char type, const void *payload,
+    size_t len)
+{
+    struct iovec vec[3];
+
+    vec[0].iov_base = &tag;
+    vec[0].iov_len = sizeof(tag);
+    vec[1].iov_base = &type;
+    vec[1].iov_len = sizeof(type);
+    vec[2].iov_base = (void*)payload;
+    vec[2].iov_len = len;
+
+    return write_to_log(LOG_ID_EVENTS, vec, 3);
+}
diff --git a/liblog/logprint.c b/liblog/logprint.c
new file mode 100644
index 0000000..2cf1254
--- /dev/null
+++ b/liblog/logprint.c
@@ -0,0 +1,972 @@
+/* //device/libs/cutils/logprint.c
+**
+** Copyright 2006, 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.
+*/
+
+#define _GNU_SOURCE /* for asprintf */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <alloca.h>
+#include <assert.h>
+#include <arpa/inet.h>
+
+#include <cutils/logd.h>
+#include <cutils/logprint.h>
+
+typedef struct FilterInfo_t {
+    char *mTag;
+    android_LogPriority mPri;
+    struct FilterInfo_t *p_next;
+} FilterInfo;
+
+struct AndroidLogFormat_t {
+    android_LogPriority global_pri;
+    FilterInfo *filters;
+    AndroidLogPrintFormat format;
+};
+
+static FilterInfo * filterinfo_new(const char * tag, android_LogPriority pri)
+{
+    FilterInfo *p_ret;
+
+    p_ret = (FilterInfo *)calloc(1, sizeof(FilterInfo));
+    p_ret->mTag = strdup(tag);
+    p_ret->mPri = pri;
+
+    return p_ret;
+}
+
+static void filterinfo_free(FilterInfo *p_info)
+{
+    if (p_info == NULL) {
+        return;
+    }
+
+    free(p_info->mTag);
+    p_info->mTag = NULL;
+}
+
+/*
+ * Note: also accepts 0-9 priorities
+ * returns ANDROID_LOG_UNKNOWN if the character is unrecognized
+ */
+static android_LogPriority filterCharToPri (char c)
+{
+    android_LogPriority pri;
+
+    c = tolower(c);
+
+    if (c >= '0' && c <= '9') {
+        if (c >= ('0'+ANDROID_LOG_SILENT)) {
+            pri = ANDROID_LOG_VERBOSE;
+        } else {
+            pri = (android_LogPriority)(c - '0');
+        }
+    } else if (c == 'v') {
+        pri = ANDROID_LOG_VERBOSE;
+    } else if (c == 'd') {
+        pri = ANDROID_LOG_DEBUG;
+    } else if (c == 'i') {
+        pri = ANDROID_LOG_INFO;
+    } else if (c == 'w') {
+        pri = ANDROID_LOG_WARN;
+    } else if (c == 'e') {
+        pri = ANDROID_LOG_ERROR;
+    } else if (c == 'f') {
+        pri = ANDROID_LOG_FATAL;
+    } else if (c == 's') {
+        pri = ANDROID_LOG_SILENT;
+    } else if (c == '*') {
+        pri = ANDROID_LOG_DEFAULT;
+    } else {
+        pri = ANDROID_LOG_UNKNOWN;
+    }
+
+    return pri;
+}
+
+static char filterPriToChar (android_LogPriority pri)
+{
+    switch (pri) {
+        case ANDROID_LOG_VERBOSE:       return 'V';
+        case ANDROID_LOG_DEBUG:         return 'D';
+        case ANDROID_LOG_INFO:          return 'I';
+        case ANDROID_LOG_WARN:          return 'W';
+        case ANDROID_LOG_ERROR:         return 'E';
+        case ANDROID_LOG_FATAL:         return 'F';
+        case ANDROID_LOG_SILENT:        return 'S';
+
+        case ANDROID_LOG_DEFAULT:
+        case ANDROID_LOG_UNKNOWN:
+        default:                        return '?';
+    }
+}
+
+static android_LogPriority filterPriForTag(
+        AndroidLogFormat *p_format, const char *tag)
+{
+    FilterInfo *p_curFilter;
+
+    for (p_curFilter = p_format->filters
+            ; p_curFilter != NULL
+            ; p_curFilter = p_curFilter->p_next
+    ) {
+        if (0 == strcmp(tag, p_curFilter->mTag)) {
+            if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
+                return p_format->global_pri;
+            } else {
+                return p_curFilter->mPri;
+            }
+        }
+    }
+
+    return p_format->global_pri;
+}
+
+/** for debugging */
+static void dumpFilters(AndroidLogFormat *p_format)
+{
+    FilterInfo *p_fi;
+
+    for (p_fi = p_format->filters ; p_fi != NULL ; p_fi = p_fi->p_next) {
+        char cPri = filterPriToChar(p_fi->mPri);
+        if (p_fi->mPri == ANDROID_LOG_DEFAULT) {
+            cPri = filterPriToChar(p_format->global_pri);
+        }
+        fprintf(stderr,"%s:%c\n", p_fi->mTag, cPri);
+    }
+
+    fprintf(stderr,"*:%c\n", filterPriToChar(p_format->global_pri));
+
+}
+
+/**
+ * returns 1 if this log line should be printed based on its priority
+ * and tag, and 0 if it should not
+ */
+int android_log_shouldPrintLine (
+        AndroidLogFormat *p_format, const char *tag, android_LogPriority pri)
+{
+    return pri >= filterPriForTag(p_format, tag);
+}
+
+AndroidLogFormat *android_log_format_new()
+{
+    AndroidLogFormat *p_ret;
+
+    p_ret = calloc(1, sizeof(AndroidLogFormat));
+
+    p_ret->global_pri = ANDROID_LOG_VERBOSE;
+    p_ret->format = FORMAT_BRIEF;
+
+    return p_ret;
+}
+
+void android_log_format_free(AndroidLogFormat *p_format)
+{
+    FilterInfo *p_info, *p_info_old;
+
+    p_info = p_format->filters;
+
+    while (p_info != NULL) {
+        p_info_old = p_info;
+        p_info = p_info->p_next;
+
+        free(p_info_old);
+    }
+
+    free(p_format);
+}
+
+
+
+void android_log_setPrintFormat(AndroidLogFormat *p_format,
+        AndroidLogPrintFormat format)
+{
+    p_format->format=format;
+}
+
+/**
+ * Returns FORMAT_OFF on invalid string
+ */
+AndroidLogPrintFormat android_log_formatFromString(const char * formatString)
+{
+    static AndroidLogPrintFormat format;
+
+    if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
+    else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
+    else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
+    else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
+    else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
+    else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
+    else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
+    else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
+    else format = FORMAT_OFF;
+
+    return format;
+}
+
+/**
+ * filterExpression: a single filter expression
+ * eg "AT:d"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ */
+
+int android_log_addFilterRule(AndroidLogFormat *p_format,
+        const char *filterExpression)
+{
+    size_t i=0;
+    size_t tagNameLength;
+    android_LogPriority pri = ANDROID_LOG_DEFAULT;
+
+    tagNameLength = strcspn(filterExpression, ":");
+
+    if (tagNameLength == 0) {
+        goto error;
+    }
+
+    if(filterExpression[tagNameLength] == ':') {
+        pri = filterCharToPri(filterExpression[tagNameLength+1]);
+
+        if (pri == ANDROID_LOG_UNKNOWN) {
+            goto error;
+        }
+    }
+
+    if(0 == strncmp("*", filterExpression, tagNameLength)) {
+        // This filter expression refers to the global filter
+        // The default level for this is DEBUG if the priority
+        // is unspecified
+        if (pri == ANDROID_LOG_DEFAULT) {
+            pri = ANDROID_LOG_DEBUG;
+        }
+
+        p_format->global_pri = pri;
+    } else {
+        // for filter expressions that don't refer to the global
+        // filter, the default is verbose if the priority is unspecified
+        if (pri == ANDROID_LOG_DEFAULT) {
+            pri = ANDROID_LOG_VERBOSE;
+        }
+
+        char *tagName;
+
+// Presently HAVE_STRNDUP is never defined, so the second case is always taken
+// Darwin doesn't have strnup, everything else does
+#ifdef HAVE_STRNDUP
+        tagName = strndup(filterExpression, tagNameLength);
+#else
+        //a few extra bytes copied...
+        tagName = strdup(filterExpression);
+        tagName[tagNameLength] = '\0';
+#endif /*HAVE_STRNDUP*/
+
+        FilterInfo *p_fi = filterinfo_new(tagName, pri);
+        free(tagName);
+
+        p_fi->p_next = p_format->filters;
+        p_format->filters = p_fi;
+    }
+
+    return 0;
+error:
+    return -1;
+}
+
+
+/**
+ * filterString: a comma/whitespace-separated set of filter expressions
+ *
+ * eg "AT:d *:i"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ *
+ */
+
+int android_log_addFilterString(AndroidLogFormat *p_format,
+        const char *filterString)
+{
+    char *filterStringCopy = strdup (filterString);
+    char *p_cur = filterStringCopy;
+    char *p_ret;
+    int err;
+
+    // Yes, I'm using strsep
+    while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
+        // ignore whitespace-only entries
+        if(p_ret[0] != '\0') {
+            err = android_log_addFilterRule(p_format, p_ret);
+
+            if (err < 0) {
+                goto error;
+            }
+        }
+    }
+
+    free (filterStringCopy);
+    return 0;
+error:
+    free (filterStringCopy);
+    return -1;
+}
+
+static inline char * strip_end(char *str)
+{
+    char *end = str + strlen(str) - 1;
+
+    while (end >= str && isspace(*end))
+        *end-- = '\0';
+    return str;
+}
+
+/**
+ * Splits a wire-format buffer into an AndroidLogEntry
+ * entry allocated by caller. Pointers will point directly into buf
+ *
+ * Returns 0 on success and -1 on invalid wire format (entry will be
+ * in unspecified state)
+ */
+int android_log_processLogBuffer(struct logger_entry *buf,
+                                 AndroidLogEntry *entry)
+{
+    size_t tag_len;
+
+    entry->tv_sec = buf->sec;
+    entry->tv_nsec = buf->nsec;
+    entry->priority = buf->msg[0];
+    entry->pid = buf->pid;
+    entry->tid = buf->tid;
+    entry->tag = buf->msg + 1;
+    tag_len = strlen(entry->tag);
+    entry->messageLen = buf->len - tag_len - 3;
+    entry->message = entry->tag + tag_len + 1;
+
+    return 0;
+}
+
+/*
+ * Extract a 4-byte value from a byte stream.
+ */
+static inline uint32_t get4LE(const uint8_t* src)
+{
+    return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+}
+
+/*
+ * Extract an 8-byte value from a byte stream.
+ */
+static inline uint64_t get8LE(const uint8_t* src)
+{
+    uint32_t low, high;
+
+    low = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+    high = src[4] | (src[5] << 8) | (src[6] << 16) | (src[7] << 24);
+    return ((long long) high << 32) | (long long) low;
+}
+
+
+/*
+ * Recursively convert binary log data to printable form.
+ *
+ * This needs to be recursive because you can have lists of lists.
+ *
+ * If we run out of room, we stop processing immediately.  It's important
+ * for us to check for space on every output element to avoid producing
+ * garbled output.
+ *
+ * Returns 0 on success, 1 on buffer full, -1 on failure.
+ */
+static int android_log_printBinaryEvent(const unsigned char** pEventData,
+    size_t* pEventDataLen, char** pOutBuf, size_t* pOutBufLen)
+{
+    const unsigned char* eventData = *pEventData;
+    size_t eventDataLen = *pEventDataLen;
+    char* outBuf = *pOutBuf;
+    size_t outBufLen = *pOutBufLen;
+    unsigned char type;
+    size_t outCount;
+    int result = 0;
+
+    if (eventDataLen < 1)
+        return -1;
+    type = *eventData++;
+    eventDataLen--;
+
+    //fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
+
+    switch (type) {
+    case EVENT_TYPE_INT:
+        /* 32-bit signed int */
+        {
+            int ival;
+
+            if (eventDataLen < 4)
+                return -1;
+            ival = get4LE(eventData);
+            eventData += 4;
+            eventDataLen -= 4;
+
+            outCount = snprintf(outBuf, outBufLen, "%d", ival);
+            if (outCount < outBufLen) {
+                outBuf += outCount;
+                outBufLen -= outCount;
+            } else {
+                /* halt output */
+                goto no_room;
+            }
+        }
+        break;
+    case EVENT_TYPE_LONG:
+        /* 64-bit signed long */
+        {
+            long long lval;
+
+            if (eventDataLen < 8)
+                return -1;
+            lval = get8LE(eventData);
+            eventData += 8;
+            eventDataLen -= 8;
+
+            outCount = snprintf(outBuf, outBufLen, "%lld", lval);
+            if (outCount < outBufLen) {
+                outBuf += outCount;
+                outBufLen -= outCount;
+            } else {
+                /* halt output */
+                goto no_room;
+            }
+        }
+        break;
+    case EVENT_TYPE_STRING:
+        /* UTF-8 chars, not NULL-terminated */
+        {
+            unsigned int strLen;
+
+            if (eventDataLen < 4)
+                return -1;
+            strLen = get4LE(eventData);
+            eventData += 4;
+            eventDataLen -= 4;
+
+            if (eventDataLen < strLen)
+                return -1;
+
+            if (strLen < outBufLen) {
+                memcpy(outBuf, eventData, strLen);
+                outBuf += strLen;
+                outBufLen -= strLen;
+            } else if (outBufLen > 0) {
+                /* copy what we can */
+                memcpy(outBuf, eventData, outBufLen);
+                outBuf += outBufLen;
+                outBufLen -= outBufLen;
+                goto no_room;
+            }
+            eventData += strLen;
+            eventDataLen -= strLen;
+            break;
+        }
+    case EVENT_TYPE_LIST:
+        /* N items, all different types */
+        {
+            unsigned char count;
+            int i;
+
+            if (eventDataLen < 1)
+                return -1;
+
+            count = *eventData++;
+            eventDataLen--;
+
+            if (outBufLen > 0) {
+                *outBuf++ = '[';
+                outBufLen--;
+            } else {
+                goto no_room;
+            }
+
+            for (i = 0; i < count; i++) {
+                result = android_log_printBinaryEvent(&eventData, &eventDataLen,
+                        &outBuf, &outBufLen);
+                if (result != 0)
+                    goto bail;
+
+                if (i < count-1) {
+                    if (outBufLen > 0) {
+                        *outBuf++ = ',';
+                        outBufLen--;
+                    } else {
+                        goto no_room;
+                    }
+                }
+            }
+
+            if (outBufLen > 0) {
+                *outBuf++ = ']';
+                outBufLen--;
+            } else {
+                goto no_room;
+            }
+        }
+        break;
+    default:
+        fprintf(stderr, "Unknown binary event type %d\n", type);
+        return -1;
+    }
+
+bail:
+    *pEventData = eventData;
+    *pEventDataLen = eventDataLen;
+    *pOutBuf = outBuf;
+    *pOutBufLen = outBufLen;
+    return result;
+
+no_room:
+    result = 1;
+    goto bail;
+}
+
+/**
+ * Convert a binary log entry to ASCII form.
+ *
+ * For convenience we mimic the processLogBuffer API.  There is no
+ * pre-defined output length for the binary data, since we're free to format
+ * it however we choose, which means we can't really use a fixed-size buffer
+ * here.
+ */
+int android_log_processBinaryLogBuffer(struct logger_entry *buf,
+    AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf,
+    int messageBufLen)
+{
+    size_t inCount;
+    unsigned int tagIndex;
+    const unsigned char* eventData;
+
+    entry->tv_sec = buf->sec;
+    entry->tv_nsec = buf->nsec;
+    entry->priority = ANDROID_LOG_INFO;
+    entry->pid = buf->pid;
+    entry->tid = buf->tid;
+
+    /*
+     * Pull the tag out.
+     */
+    eventData = (const unsigned char*) buf->msg;
+    inCount = buf->len;
+    if (inCount < 4)
+        return -1;
+    tagIndex = get4LE(eventData);
+    eventData += 4;
+    inCount -= 4;
+
+    if (map != NULL) {
+        entry->tag = android_lookupEventTag(map, tagIndex);
+    } else {
+        entry->tag = NULL;
+    }
+
+    /*
+     * If we don't have a map, or didn't find the tag number in the map,
+     * stuff a generated tag value into the start of the output buffer and
+     * shift the buffer pointers down.
+     */
+    if (entry->tag == NULL) {
+        int tagLen;
+
+        tagLen = snprintf(messageBuf, messageBufLen, "[%d]", tagIndex);
+        entry->tag = messageBuf;
+        messageBuf += tagLen+1;
+        messageBufLen -= tagLen+1;
+    }
+
+    /*
+     * Format the event log data into the buffer.
+     */
+    char* outBuf = messageBuf;
+    size_t outRemaining = messageBufLen-1;      /* leave one for nul byte */
+    int result;
+    result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf,
+                &outRemaining);
+    if (result < 0) {
+        fprintf(stderr, "Binary log entry conversion failed\n");
+        return -1;
+    } else if (result == 1) {
+        if (outBuf > messageBuf) {
+            /* leave an indicator */
+            *(outBuf-1) = '!';
+        } else {
+            /* no room to output anything at all */
+            *outBuf++ = '!';
+            outRemaining--;
+        }
+        /* pretend we ate all the data */
+        inCount = 0;
+    }
+
+    /* eat the silly terminating '\n' */
+    if (inCount == 1 && *eventData == '\n') {
+        eventData++;
+        inCount--;
+    }
+
+    if (inCount != 0) {
+        fprintf(stderr,
+            "Warning: leftover binary log data (%d bytes)\n", inCount);
+    }
+
+    /*
+     * Terminate the buffer.  The NUL byte does not count as part of
+     * entry->messageLen.
+     */
+    *outBuf = '\0';
+    entry->messageLen = outBuf - messageBuf;
+    assert(entry->messageLen == (messageBufLen-1) - outRemaining);
+
+    entry->message = messageBuf;
+
+    return 0;
+}
+
+/**
+ * Formats a log message into a buffer
+ *
+ * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer
+ * If return value != defaultBuffer, caller must call free()
+ * Returns NULL on malloc error
+ */
+
+char *android_log_formatLogLine (
+    AndroidLogFormat *p_format,
+    char *defaultBuffer,
+    size_t defaultBufferSize,
+    const AndroidLogEntry *entry,
+    size_t *p_outLength)
+{
+#if defined(HAVE_LOCALTIME_R)
+    struct tm tmBuf;
+#endif
+    struct tm* ptm;
+    char timeBuf[32];
+    char headerBuf[128];
+    char prefixBuf[128], suffixBuf[128];
+    char priChar;
+    int prefixSuffixIsHeaderFooter = 0;
+    char * ret = NULL;
+
+    priChar = filterPriToChar(entry->priority);
+
+    /*
+     * Get the current date/time in pretty form
+     *
+     * It's often useful when examining a log with "less" to jump to
+     * a specific point in the file by searching for the date/time stamp.
+     * For this reason it's very annoying to have regexp meta characters
+     * in the time stamp.  Don't use forward slashes, parenthesis,
+     * brackets, asterisks, or other special chars here.
+     */
+#if defined(HAVE_LOCALTIME_R)
+    ptm = localtime_r(&(entry->tv_sec), &tmBuf);
+#else
+    ptm = localtime(&(entry->tv_sec));
+#endif
+    //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
+    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
+
+    /*
+     * Construct a buffer containing the log header and log message.
+     */
+    size_t prefixLen, suffixLen;
+
+    switch (p_format->format) {
+        case FORMAT_TAG:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "%c/%-8s: ", priChar, entry->tag);
+            strcpy(suffixBuf, "\n"); suffixLen = 1;
+            break;
+        case FORMAT_PROCESS:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "%c(%5d) ", priChar, entry->pid);
+            suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
+                "  (%s)\n", entry->tag);
+            break;
+        case FORMAT_THREAD:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "%c(%5d:%p) ", priChar, entry->pid, (void*)entry->tid);
+            strcpy(suffixBuf, "\n");
+            suffixLen = 1;
+            break;
+        case FORMAT_RAW:
+            prefixBuf[0] = 0;
+            prefixLen = 0;
+            strcpy(suffixBuf, "\n");
+            suffixLen = 1;
+            break;
+        case FORMAT_TIME:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "%s.%03ld %c/%-8s(%5d): ", timeBuf, entry->tv_nsec / 1000000,
+                priChar, entry->tag, entry->pid);
+            strcpy(suffixBuf, "\n");
+            suffixLen = 1;
+            break;
+        case FORMAT_THREADTIME:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "%s.%03ld %5d %5d %c %-8s: ", timeBuf, entry->tv_nsec / 1000000,
+                (int)entry->pid, (int)entry->tid, priChar, entry->tag);
+            strcpy(suffixBuf, "\n");
+            suffixLen = 1;
+            break;
+        case FORMAT_LONG:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "[ %s.%03ld %5d:%p %c/%-8s ]\n",
+                timeBuf, entry->tv_nsec / 1000000, entry->pid,
+                (void*)entry->tid, priChar, entry->tag);
+            strcpy(suffixBuf, "\n\n");
+            suffixLen = 2;
+            prefixSuffixIsHeaderFooter = 1;
+            break;
+        case FORMAT_BRIEF:
+        default:
+            prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+                "%c/%-8s(%5d): ", priChar, entry->tag, entry->pid);
+            strcpy(suffixBuf, "\n");
+            suffixLen = 1;
+            break;
+    }
+
+    /* the following code is tragically unreadable */
+
+    size_t numLines;
+    size_t i;
+    char *p;
+    size_t bufferSize;
+    const char *pm;
+
+    if (prefixSuffixIsHeaderFooter) {
+        // we're just wrapping message with a header/footer
+        numLines = 1;
+    } else {
+        pm = entry->message;
+        numLines = 0;
+
+        // The line-end finding here must match the line-end finding
+        // in for ( ... numLines...) loop below
+        while (pm < (entry->message + entry->messageLen)) {
+            if (*pm++ == '\n') numLines++;
+        }
+        // plus one line for anything not newline-terminated at the end
+        if (pm > entry->message && *(pm-1) != '\n') numLines++;
+    }
+
+    // this is an upper bound--newlines in message may be counted
+    // extraneously
+    bufferSize = (numLines * (prefixLen + suffixLen)) + entry->messageLen + 1;
+
+    if (defaultBufferSize >= bufferSize) {
+        ret = defaultBuffer;
+    } else {
+        ret = (char *)malloc(bufferSize);
+
+        if (ret == NULL) {
+            return ret;
+        }
+    }
+
+    ret[0] = '\0';       /* to start strcat off */
+
+    p = ret;
+    pm = entry->message;
+
+    if (prefixSuffixIsHeaderFooter) {
+        strcat(p, prefixBuf);
+        p += prefixLen;
+        strncat(p, entry->message, entry->messageLen);
+        p += entry->messageLen;
+        strcat(p, suffixBuf);
+        p += suffixLen;
+    } else {
+        while(pm < (entry->message + entry->messageLen)) {
+            const char *lineStart;
+            size_t lineLen;
+
+            lineStart = pm;
+
+            // Find the next end-of-line in message
+            while (pm < (entry->message + entry->messageLen)
+                    && *pm != '\n') pm++;
+            lineLen = pm - lineStart;
+
+            strcat(p, prefixBuf);
+            p += prefixLen;
+            strncat(p, lineStart, lineLen);
+            p += lineLen;
+            strcat(p, suffixBuf);
+            p += suffixLen;
+
+            if (*pm == '\n') pm++;
+        }
+    }
+
+    if (p_outLength != NULL) {
+        *p_outLength = p - ret;
+    }
+
+    return ret;
+}
+
+/**
+ * Either print or do not print log line, based on filter
+ *
+ * Returns count bytes written
+ */
+
+int android_log_filterAndPrintLogLine(
+    AndroidLogFormat *p_format,
+    int fd,
+    const AndroidLogEntry *entry)
+{
+    int ret;
+    char defaultBuffer[512];
+    char *outBuffer = NULL;
+    size_t totalLen;
+
+    if (0 == android_log_shouldPrintLine(p_format, entry->tag,
+            entry->priority)) {
+        return 0;
+    }
+
+    outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
+            sizeof(defaultBuffer), entry, &totalLen);
+
+    if (!outBuffer)
+        return -1;
+
+    do {
+        ret = write(fd, outBuffer, totalLen);
+    } while (ret < 0 && errno == EINTR);
+
+    if (ret < 0) {
+        fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
+        ret = 0;
+        goto done;
+    }
+
+    if (((size_t)ret) < totalLen) {
+        fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
+                (int)totalLen);
+        goto done;
+    }
+
+done:
+    if (outBuffer != defaultBuffer) {
+        free(outBuffer);
+    }
+
+    return ret;
+}
+
+
+
+void logprint_run_tests()
+{
+#if 0
+
+    fprintf(stderr, "tests disabled\n");
+
+#else
+
+    int err;
+    const char *tag;
+    AndroidLogFormat *p_format;
+
+    p_format = android_log_format_new();
+
+    fprintf(stderr, "running tests\n");
+
+    tag = "random";
+
+    android_log_addFilterRule(p_format,"*:i");
+
+    assert (ANDROID_LOG_INFO == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+    android_log_addFilterRule(p_format, "*");
+    assert (ANDROID_LOG_DEBUG == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+    android_log_addFilterRule(p_format, "*:v");
+    assert (ANDROID_LOG_VERBOSE == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+    android_log_addFilterRule(p_format, "*:i");
+    assert (ANDROID_LOG_INFO == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+
+    android_log_addFilterRule(p_format, "random");
+    assert (ANDROID_LOG_VERBOSE == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+    android_log_addFilterRule(p_format, "random:v");
+    assert (ANDROID_LOG_VERBOSE == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+    android_log_addFilterRule(p_format, "random:d");
+    assert (ANDROID_LOG_DEBUG == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+    android_log_addFilterRule(p_format, "random:w");
+    assert (ANDROID_LOG_WARN == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+
+    android_log_addFilterRule(p_format, "crap:*");
+    assert (ANDROID_LOG_VERBOSE== filterPriForTag(p_format, "crap"));
+    assert(android_log_shouldPrintLine(p_format, "crap", ANDROID_LOG_VERBOSE) > 0);
+
+    // invalid expression
+    err = android_log_addFilterRule(p_format, "random:z");
+    assert (err < 0);
+    assert (ANDROID_LOG_WARN == filterPriForTag(p_format, "random"));
+    assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+
+    // Issue #550946
+    err = android_log_addFilterString(p_format, " ");
+    assert(err == 0);
+    assert(ANDROID_LOG_WARN == filterPriForTag(p_format, "random"));
+
+    // note trailing space
+    err = android_log_addFilterString(p_format, "*:s random:d ");
+    assert(err == 0);
+    assert(ANDROID_LOG_DEBUG == filterPriForTag(p_format, "random"));
+
+    err = android_log_addFilterString(p_format, "*:s random:z");
+    assert(err < 0);
+
+
+#if 0
+    char *ret;
+    char defaultBuffer[512];
+
+    ret = android_log_formatLogLine(p_format,
+        defaultBuffer, sizeof(defaultBuffer), 0, ANDROID_LOG_ERROR, 123,
+        123, 123, "random", "nofile", strlen("Hello"), "Hello", NULL);
+#endif
+
+
+    fprintf(stderr, "tests complete\n");
+#endif
+}