Move String8 path functions to androidfw and aapt

Test: m checkbuild
Bug: 295394788
Change-Id: I9488bc5632cbd47c83f6b5f2df4c87eb324a1e8e
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index c80fb18..86e1fe3d 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -80,7 +80,10 @@
     export_include_dirs: ["include"],
     export_shared_lib_headers: ["libz"],
     static_libs: ["libincfs-utils"],
-    whole_static_libs: ["libincfs-utils"],
+    whole_static_libs: [
+        "libandroidfw_pathutils",
+        "libincfs-utils",
+    ],
     export_static_lib_headers: ["libincfs-utils"],
     target: {
         android: {
@@ -132,6 +135,28 @@
     },
 }
 
+cc_library_static {
+    name: "libandroidfw_pathutils",
+    defaults: ["libandroidfw_defaults"],
+    host_supported: true,
+    export_include_dirs: ["include_pathutils"],
+    srcs: [
+        "PathUtils.cpp",
+    ],
+    shared_libs: [
+        "libutils",
+    ],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
+    visibility: [
+        ":__subpackages__",
+        "//frameworks/base/tools/aapt",
+    ],
+}
+
 common_test_libs = [
     "libandroidfw",
     "libbase",
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 795bb3c..fd6e18e 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -26,6 +26,7 @@
 #include <androidfw/AssetDir.h>
 #include <androidfw/AssetManager.h>
 #include <androidfw/misc.h>
+#include <androidfw/PathUtils.h>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/ZipFileRO.h>
 #include <cutils/atomic.h>
@@ -88,7 +89,7 @@
     const char* root = getenv("ANDROID_DATA");
     LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
     String8 path(root);
-    path.appendPath(kResourceCache);
+    appendPath(path, kResourceCache);
 
     char buf[256]; // 256 chars should be enough for anyone...
     strncpy(buf, pkgPath.c_str(), 255);
@@ -104,7 +105,7 @@
         }
         ++p;
     }
-    path.appendPath(filename);
+    appendPath(path, filename);
     path.append("@idmap");
 
     return path;
@@ -181,7 +182,7 @@
 
     String8 realPath(path);
     if (kAppZipName) {
-        realPath.appendPath(kAppZipName);
+        appendPath(realPath, kAppZipName);
     }
     ap.type = ::getFileType(realPath.c_str());
     if (ap.type == kFileTypeRegular) {
@@ -367,7 +368,7 @@
     LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
 
     String8 path(root);
-    path.appendPath(kSystemAssets);
+    appendPath(path, kSystemAssets);
 
     return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */);
 }
@@ -439,7 +440,7 @@
     LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
 
     String8 assetName(kAssetsRoot);
-    assetName.appendPath(fileName);
+    appendPath(assetName, fileName);
 
     /*
      * For each top-level asset path, search for the asset.
@@ -587,8 +588,8 @@
                 const char* data = getenv("ANDROID_DATA");
                 LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
                 String8 overlaysListPath(data);
-                overlaysListPath.appendPath(kResourceCache);
-                overlaysListPath.appendPath("overlays.list");
+                appendPath(overlaysListPath, kResourceCache);
+                appendPath(overlaysListPath, "overlays.list");
                 addSystemOverlays(overlaysListPath.c_str(), ap.path, sharedRes, nextEntryIdx);
 #endif
                 sharedRes = const_cast<AssetManager*>(this)->
@@ -789,7 +790,7 @@
     /* look at the filesystem on disk */
     if (ap.type == kFileTypeDirectory) {
         String8 path(ap.path);
-        path.appendPath(fileName);
+        appendPath(path, fileName);
 
         pAsset = openAssetFromFileLocked(path, mode);
 
@@ -841,9 +842,9 @@
     sourceName.append(zipFileName);
     sourceName.append(":");
     if (dirName.length() > 0) {
-        sourceName.appendPath(dirName);
+        appendPath(sourceName, dirName);
     }
-    sourceName.appendPath(fileName);
+    appendPath(sourceName, fileName);
     return sourceName;
 }
 
@@ -853,7 +854,7 @@
 String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir)
 {
     String8 path(ap.path);
-    if (rootDir != NULL) path.appendPath(rootDir);
+    if (rootDir != NULL) appendPath(path, rootDir);
     return path;
 }
 
@@ -897,7 +898,7 @@
 {
     Asset* pAsset = NULL;
 
-    if (strcasecmp(pathName.getPathExtension().c_str(), ".gz") == 0) {
+    if (strcasecmp(getPathExtension(pathName).c_str(), ".gz") == 0) {
         //printf("TRYING '%s'\n", (const char*) pathName);
         pAsset = Asset::createFromCompressedFile(pathName.c_str(), mode);
     } else {
@@ -1078,8 +1079,7 @@
     //printf("scanAndMergeDir: %s %s %s\n", ap.path.c_str(), rootDir, dirName);
 
     String8 path = createPathNameLocked(ap, rootDir);
-    if (dirName[0] != '\0')
-        path.appendPath(dirName);
+    if (dirName[0] != '\0') appendPath(path, dirName);
 
     SortedVector<AssetDir::FileInfo>* pContents = scanDirLocked(path);
     if (pContents == NULL)
@@ -1176,7 +1176,7 @@
             fileType = kFileTypeUnknown;
 #else
         // stat the file
-        fileType = ::getFileType(path.appendPathCopy(entry->d_name).c_str());
+        fileType = ::getFileType(appendPathCopy(path, entry->d_name).c_str());
 #endif
 
         if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
@@ -1184,9 +1184,9 @@
 
         AssetDir::FileInfo info;
         info.set(String8(entry->d_name), fileType);
-        if (strcasecmp(info.getFileName().getPathExtension().c_str(), ".gz") == 0)
-            info.setFileName(info.getFileName().getBasePath());
-        info.setSourceName(path.appendPathCopy(info.getFileName()));
+        if (strcasecmp(getPathExtension(info.getFileName()).c_str(), ".gz") == 0)
+            info.setFileName(getBasePath(info.getFileName()));
+        info.setSourceName(appendPathCopy(path, info.getFileName()));
         pContents->add(info);
     }
 
@@ -1220,7 +1220,7 @@
 
     /* convert "sounds" to "rootDir/sounds" */
     if (rootDir != NULL) dirName = rootDir;
-    dirName.appendPath(baseDirName);
+    appendPath(dirName, baseDirName);
 
     /*
      * Scan through the list of files, looking for a match.  The files in
@@ -1269,7 +1269,7 @@
             if (nextSlash == NULL) {
                 /* this is a file in the requested directory */
 
-                info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
+                info.set(getPathLeaf(String8(nameBuf)), kFileTypeRegular);
 
                 info.setSourceName(
                     createZipSourceNameLocked(zipName, dirName, info.getFileName()));
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
index 3582609..1a6a952 100644
--- a/libs/androidfw/BackupHelpers.cpp
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -30,6 +30,7 @@
 #include <utime.h>
 #include <zlib.h>
 
+#include <androidfw/PathUtils.h>
 #include <log/log.h>
 #include <utils/ByteOrder.h>
 #include <utils/KeyedVector.h>
@@ -606,14 +607,14 @@
             prefix += packageName;
         }
         if (domain.length() > 0) {
-            prefix.appendPath(domain);
+            appendPath(prefix, domain);
         }
 
         // pax extended means we don't put in a prefix field, and put a different
         // string in the basic name field.  We can also construct the full path name
         // out of the substrings we've now built.
         fullname = prefix;
-        fullname.appendPath(relpath);
+        appendPath(fullname, relpath);
 
         // ustar:
         //    [   0 : 100 ]; file name/path
@@ -654,7 +655,7 @@
         // Now build the pax *header* templated on the ustar header
         memcpy(paxHeader, buf, 512);
 
-        String8 leaf = fullname.getPathLeaf();
+        String8 leaf = getPathLeaf(fullname);
         memset(paxHeader, 0, 100);                  // rewrite the name area
         snprintf(paxHeader, 100, "PaxHeader/%s", leaf.c_str());
         memset(paxHeader + 345, 0, 155);            // rewrite the prefix area
diff --git a/libs/androidfw/PathUtils.cpp b/libs/androidfw/PathUtils.cpp
new file mode 100644
index 0000000..df7a9f0
--- /dev/null
+++ b/libs/androidfw/PathUtils.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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 <androidfw/PathUtils.h>
+
+#include <utils/Compat.h>
+
+namespace android {
+
+String8 getPathLeaf(const String8& str) {
+    const char* cp;
+    const char*const buf = str.c_str();
+
+    cp = strrchr(buf, OS_PATH_SEPARATOR);
+    if (cp == nullptr)
+        return str;
+    else
+        return String8(cp+1);
+}
+
+String8 getPathDir(const String8& str8) {
+    const char* cp;
+    const char*const str = str8.c_str();
+
+    cp = strrchr(str, OS_PATH_SEPARATOR);
+    if (cp == nullptr)
+        return String8();
+    else
+        return String8(str, cp - str);
+}
+
+static char* findExtension(const String8& str8) {
+    const char* lastSlash;
+    const char* lastDot;
+    const char* const str = str8.c_str();
+
+    // only look at the filename
+    lastSlash = strrchr(str, OS_PATH_SEPARATOR);
+    if (lastSlash == nullptr)
+        lastSlash = str;
+    else
+        lastSlash++;
+
+    // find the last dot
+    lastDot = strrchr(lastSlash, '.');
+    if (lastDot == nullptr)
+        return nullptr;
+
+    // looks good, ship it
+    return const_cast<char*>(lastDot);
+}
+
+String8 getPathExtension(const String8& str) {
+    char* ext;
+
+    ext = findExtension(str);
+    if (ext != nullptr)
+        return String8(ext);
+    else
+        return String8();
+}
+
+String8 getBasePath(const String8& str8) {
+    char* ext;
+    const char* const str = str8.c_str();
+
+    ext = findExtension(str8);
+    if (ext == nullptr)
+        return str8;
+    else
+        return String8(str, ext - str);
+}
+
+static void setPathName(String8& s, const char* name) {
+    size_t len = strlen(name);
+    char* buf = s.lockBuffer(len);
+
+    memcpy(buf, name, len);
+
+    // remove trailing path separator, if present
+    if (len > 0 && buf[len - 1] == OS_PATH_SEPARATOR) len--;
+    buf[len] = '\0';
+
+    s.unlockBuffer(len);
+}
+
+String8& appendPath(String8& str, const char* name) {
+    // TODO: The test below will fail for Win32 paths. Fix later or ignore.
+    if (name[0] != OS_PATH_SEPARATOR) {
+        if (*name == '\0') {
+            // nothing to do
+            return str;
+        }
+
+        size_t len = str.length();
+        if (len == 0) {
+            // no existing filename, just use the new one
+            setPathName(str, name);
+            return str;
+        }
+
+        // make room for oldPath + '/' + newPath
+        int newlen = strlen(name);
+
+        char* buf = str.lockBuffer(len+1+newlen);
+
+        // insert a '/' if needed
+        if (buf[len-1] != OS_PATH_SEPARATOR)
+            buf[len++] = OS_PATH_SEPARATOR;
+
+        memcpy(buf+len, name, newlen+1);
+        len += newlen;
+
+        str.unlockBuffer(len);
+        return str;
+    } else {
+        setPathName(str, name);
+        return str;
+    }
+}
+
+} // namespace android
diff --git a/libs/androidfw/include_pathutils/androidfw/PathUtils.h b/libs/androidfw/include_pathutils/androidfw/PathUtils.h
new file mode 100644
index 0000000..4debe8d
--- /dev/null
+++ b/libs/androidfw/include_pathutils/androidfw/PathUtils.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <utils/String8.h>
+
+/* This library contains path manipulation functions that are used only by androidfw and aapt.
+ * When it's possible, migrate all uses to std::filesystem::path.
+ */
+
+namespace android {
+
+/**
+ * Get just the filename component.
+ *
+ * DEPRECATED: use std::filesystem::path::filename
+ *
+ * "/tmp/foo/bar.c" --> "bar.c"
+ */
+String8 getPathLeaf(const String8& str);
+
+/**
+ * Remove the last (file name) component, leaving just the directory
+ * name.
+ *
+ * DEPRECATED: use std::filesystem::path::parent_path
+ *
+ * "/tmp/foo/bar.c" --> "/tmp/foo"
+ * "/tmp" --> "" // ????? shouldn't this be "/" ???? XXX
+ * "bar.c" --> ""
+ */
+String8 getPathDir(const String8& str);
+
+/**
+ * Return the filename extension.  This is the last '.' and any number
+ * of characters that follow it.  The '.' is included in case we
+ * decide to expand our definition of what constitutes an extension.
+ *
+ * DEPRECATED: use std::filesystem::path::extension
+ *
+ * "/tmp/foo/bar.c" --> ".c"
+ * "/tmp" --> ""
+ * "/tmp/foo.bar/baz" --> ""
+ * "foo.jpeg" --> ".jpeg"
+ * "foo." --> ""
+ */
+String8 getPathExtension(const String8& str);
+
+/**
+ * Return the path without the extension.  Rules for what constitutes
+ * an extension are described in the comment for getPathExtension().
+ *
+ * DEPRECATED: use std::filesystem::path::stem and std::filesystem::path::parent_path
+ *
+ * "/tmp/foo/bar.c" --> "/tmp/foo/bar"
+ */
+String8 getBasePath(const String8& str);
+
+/**
+ * Add a component to the pathname.  We guarantee that there is
+ * exactly one path separator between the old path and the new.
+ * If there is no existing name, we just copy the new name in.
+ *
+ * DEPRECATED: use std::filesystem::path::operator/=
+ *
+ * If leaf is a fully qualified path (i.e. starts with '/', it
+ * replaces whatever was there before.
+ */
+String8& appendPath(String8& str, const char* leaf);
+inline String8& appendPath(String8& str, const String8& leaf) {
+    return appendPath(str, leaf.c_str());
+}
+
+/**
+ * Like appendPath(), but does not affect this string.  Returns a new one instead.
+ *
+ * DEPRECATED: use std::filesystem::operator/
+ */
+inline String8 appendPathCopy(String8 str, const char* leaf) { return appendPath(str, leaf); }
+inline String8 appendPathCopy(String8 str, const String8& leaf) {
+    return appendPath(str, leaf.c_str());
+}
+
+} // namespace android
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 0fc2617..0aaf3e8 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -7,7 +7,9 @@
 #include "AaptUtil.h"
 #include "Main.h"
 #include "ResourceFilter.h"
+#include "Utils.h"
 
+#include <androidfw/PathUtils.h>
 #include <utils/misc.h>
 #include <utils/SortedVector.h>
 
@@ -96,7 +98,7 @@
     char *matchedPattern = NULL;
 
     String8 fullPath(root);
-    fullPath.appendPath(path);
+    appendPath(fullPath, String8(path));
     FileType type = getFileType(fullPath);
 
     int plen = strlen(path);
@@ -508,7 +510,7 @@
 {
     if (hasData()) {
         String8 name(mGroupEntry.toDirName(String8()));
-        name.appendPath(mPath);
+        appendPath(name, mPath);
         name.append(" #generated");
         return name;
     }
@@ -615,7 +617,7 @@
     String8 remain = path;
 
     sp<AaptDir> subdir = this;
-    while (name = remain.walkPath(&remain), remain != "") {
+    while (name = walkPath(remain, &remain), remain != "") {
         subdir = subdir->makeDir(name);
     }
 
@@ -623,7 +625,7 @@
     if (i >= 0) {
         return subdir->mDirs.valueAt(i);
     }
-    sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
+    sp<AaptDir> dir = new AaptDir(name, appendPathCopy(subdir->mPath, name));
     subdir->mDirs.add(name, dir);
     return dir;
 }
@@ -645,7 +647,7 @@
     if (mFiles.indexOfKey(leafName) >= 0) {
         group = mFiles.valueFor(leafName);
     } else {
-        group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
+        group = new AaptGroup(leafName, appendPathCopy(mPath, leafName));
         mFiles.add(leafName, group);
     }
 
@@ -684,7 +686,7 @@
             // Add fully qualified path for dependency purposes
             // if we're collecting them
             if (fullResPaths != NULL) {
-                fullResPaths->add(srcDir.appendPathCopy(name));
+                fullResPaths->add(appendPathCopy(srcDir, name));
             }
         }
         closedir(dir);
@@ -701,7 +703,7 @@
         String8 pathName(srcDir);
         FileType type;
 
-        pathName.appendPath(fileNames[i].c_str());
+        appendPath(pathName, fileNames[i]);
         type = getFileType(pathName.c_str());
         if (type == kFileTypeDirectory) {
             sp<AaptDir> subdir;
@@ -709,7 +711,7 @@
             if (mDirs.indexOfKey(fileNames[i]) >= 0) {
                 subdir = mDirs.valueFor(fileNames[i]);
             } else {
-                subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
+                subdir = new AaptDir(fileNames[i], appendPathCopy(mPath, fileNames[i]));
                 notAdded = true;
             }
             ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
@@ -821,11 +823,11 @@
 {
     if (mFiles.size() > 0) {
         // Arbitrarily pull the first file out of the list as the source dir.
-        return mFiles.valueAt(0)->getPrintableSource().getPathDir();
+        return getPathDir(mFiles.valueAt(0)->getPrintableSource());
     }
     if (mDirs.size() > 0) {
         // Or arbitrarily pull the first dir out of the list as the source dir.
-        return mDirs.valueAt(0)->getPrintableSource().getPathDir();
+        return getPathDir(mDirs.valueAt(0)->getPrintableSource());
     }
 
     // Should never hit this case, but to be safe...
@@ -908,8 +910,8 @@
     sp<AaptFile> file;
     String8 root, remain(filePath), partialPath;
     while (remain.length() > 0) {
-        root = remain.walkPath(&remain);
-        partialPath.appendPath(root);
+        root = walkPath(remain, &remain);
+        appendPath(partialPath, root);
 
         const String8 rootStr(root);
 
@@ -924,7 +926,7 @@
                     return NULL;
                 }
             }
-            file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
+            file = new AaptFile(appendPathCopy(srcDir, filePath), entry, resType);
             status_t res = group->addFile(file);
             if (res != NO_ERROR) {
                 return NULL;
@@ -981,7 +983,7 @@
     if (bundle->getAndroidManifestFile() != NULL) {
         // place at root of zip.
         String8 srcFile(bundle->getAndroidManifestFile());
-        addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
+        addFile(getPathLeaf(srcFile), AaptGroupEntry(), getPathDir(srcFile),
                 NULL, String8());
         totalCount++;
     }
@@ -1154,7 +1156,7 @@
         }
 
         String8 subdirName(srcDir);
-        subdirName.appendPath(entry->d_name);
+        appendPath(subdirName, entry->d_name);
 
         AaptGroupEntry group;
         String8 resType;
@@ -1239,16 +1241,16 @@
 
         String8 entryName(entry->getFileName());
 
-        String8 dirName = entryName.getPathDir();
+        String8 dirName = getPathDir(entryName);
         sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
 
         String8 resType;
         AaptGroupEntry kind;
 
         String8 remain;
-        if (entryName.walkPath(&remain) == kResourceDir) {
+        if (walkPath(entryName, &remain) == kResourceDir) {
             // these are the resources, pull their type out of the directory name
-            kind.initFromDirName(remain.walkPath().c_str(), &resType);
+            kind.initFromDirName(walkPath(remain).c_str(), &resType);
         } else {
             // these are untyped and don't have an AaptGroupEntry
         }
@@ -1258,10 +1260,10 @@
         }
 
         // use the one from the zip file if they both exist.
-        dir->removeFile(entryName.getPathLeaf());
+        dir->removeFile(getPathLeaf(entryName));
 
         sp<AaptFile> file = new AaptFile(entryName, kind, resType);
-        status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
+        status_t err = dir->addLeafFile(getPathLeaf(entryName), file);
         if (err != NO_ERROR) {
             fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.c_str());
             count = err;
@@ -1374,7 +1376,7 @@
                     // containing no entries.
                     continue;
                 }
-                if (file->getPath().getPathExtension() == ".xml") {
+                if (getPathExtension(file->getPath()) == ".xml") {
                     // We can't remove .xml files at this point, because when
                     // we parse them they may add identifier resources, so
                     // removing them can cause our resource identifiers to
@@ -1411,7 +1413,7 @@
                     // containing no entries.
                     continue;
                 }
-                if (file->getPath().getPathExtension() == ".xml") {
+                if (getPathExtension(file->getPath()) == ".xml") {
                     // We can't remove .xml files at this point, because when
                     // we parse them they may add identifier resources, so
                     // removing them can cause our resource identifiers to
diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp
index cecd95a..68db56d 100644
--- a/tools/aapt/Android.bp
+++ b/tools/aapt/Android.bp
@@ -51,6 +51,10 @@
         "libz",
     ],
 
+    whole_static_libs: [
+        "libandroidfw_pathutils",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h
index 2dc143c..dc5493f 100644
--- a/tools/aapt/CacheUpdater.h
+++ b/tools/aapt/CacheUpdater.h
@@ -7,6 +7,7 @@
 #ifndef CACHE_UPDATER_H
 #define CACHE_UPDATER_H
 
+#include <androidfw/PathUtils.h>
 #include <utils/String8.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -16,6 +17,8 @@
 #include <direct.h>
 #endif
 
+#include "Utils.h"
+
 using namespace android;
 
 /** CacheUpdater
@@ -72,14 +75,14 @@
             do {
                 // As we remove the end of existsPath add it to
                 // the string of paths to create.
-                toCreate = existsPath.getPathLeaf().appendPath(toCreate);
-                existsPath = existsPath.getPathDir();
+                toCreate = appendPathCopy(getPathLeaf(existsPath), toCreate);
+                existsPath = getPathDir(existsPath);
             } while (stat(existsPath.c_str(),&s) == -1);
 
             // Walk forwards and build directories as we go
             do {
                 // Advance to the next segment of the path
-                existsPath.appendPath(toCreate.walkPath(&remains));
+                appendPath(existsPath, walkPath(toCreate, &remains));
                 toCreate = remains;
 #ifdef _WIN32
                 _mkdir(existsPath.c_str());
@@ -101,7 +104,7 @@
     virtual void processImage(String8 source, String8 dest)
     {
         // Make sure we're trying to write to a directory that is extant
-        ensureDirectoriesExist(dest.getPathDir());
+        ensureDirectoriesExist(getPathDir(dest));
 
         preProcessImageToCache(bundle, source, dest);
     };
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 60f3f27..800466a 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -12,6 +12,7 @@
 #include "ResourceTable.h"
 #include "XMLNode.h"
 
+#include <androidfw/PathUtils.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 #include <utils/List.h>
@@ -2486,12 +2487,12 @@
     for (int i = 1; i < bundle->getFileSpecCount(); i++) {
         const char* fileName = bundle->getFileSpecEntry(i);
 
-        if (strcasecmp(String8(fileName).getPathExtension().c_str(), ".gz") == 0) {
+        if (strcasecmp(getPathExtension(String8(fileName)).c_str(), ".gz") == 0) {
             printf(" '%s'... (from gzip)\n", fileName);
-            result = zip->addGzip(fileName, String8(fileName).getBasePath().c_str(), NULL);
+            result = zip->addGzip(fileName, getBasePath(String8(fileName)).c_str(), NULL);
         } else {
             if (bundle->getJunkPath()) {
-                String8 storageName = String8(fileName).getPathLeaf();
+                String8 storageName = getPathLeaf(String8(fileName));
                 printf(" '%s' as '%s'...\n", fileName,
                         ResTable::normalizeForOutput(storageName.c_str()).c_str());
                 result = zip->add(fileName, storageName.c_str(),
@@ -2617,10 +2618,10 @@
         return original;
     }
 
-    String8 ext(original.getPathExtension());
+    String8 ext(getPathExtension(original));
     if (ext == String8(".apk")) {
         return String8::format("%s_%s%s",
-                original.getBasePath().c_str(),
+                getBasePath(original).c_str(),
                 split->getDirectorySafeName().c_str(),
                 ext.c_str());
     }
@@ -2756,7 +2757,7 @@
             // generate the dependency file in the R.java package subdirectory
             // e.g. gen/com/foo/app/R.java.d
             dependencyFile = String8(bundle->getRClassDir());
-            dependencyFile.appendPath("R.java.d");
+            appendPath(dependencyFile, "R.java.d");
         }
         // Make sure we have a clean dependency file to start with
         fp = fopen(dependencyFile, "w");
diff --git a/tools/aapt/CrunchCache.cpp b/tools/aapt/CrunchCache.cpp
index 1f2febe..e731ce0 100644
--- a/tools/aapt/CrunchCache.cpp
+++ b/tools/aapt/CrunchCache.cpp
@@ -5,6 +5,7 @@
 // This file defines functions laid out and documented in
 // CrunchCache.h
 
+#include <androidfw/PathUtils.h>
 #include <utils/Compat.h>
 #include <utils/Vector.h>
 #include <utils/String8.h>
@@ -52,15 +53,15 @@
         relativePath = String8(rPathPtr + offset);
 
         if (forceOverwrite || needsUpdating(relativePath)) {
-            cu->processImage(mSourcePath.appendPathCopy(relativePath),
-                             mDestPath.appendPathCopy(relativePath));
+            cu->processImage(appendPathCopy(mSourcePath, relativePath),
+                             appendPathCopy(mDestPath, relativePath));
             numFilesUpdated++;
             // crunchFile(relativePath);
         }
         // Delete this file from the source files and (if it exists) from the
         // dest files.
         mSourceFiles.removeItemsAt(0);
-        mDestFiles.removeItem(mDestPath.appendPathCopy(relativePath));
+        mDestFiles.removeItem(appendPathCopy(mDestPath, relativePath));
     }
 
     // Iterate through what's left of destFiles and delete leftovers
@@ -99,7 +100,7 @@
     // Retrieve modification dates for this file entry under the source and
     // cache directory trees. The vectors will return a modification date of 0
     // if the file doesn't exist.
-    time_t sourceDate = mSourceFiles.valueFor(mSourcePath.appendPathCopy(relativePath));
-    time_t destDate = mDestFiles.valueFor(mDestPath.appendPathCopy(relativePath));
+    time_t sourceDate = mSourceFiles.valueFor(appendPathCopy(mSourcePath, relativePath));
+    time_t destDate = mDestFiles.valueFor(appendPathCopy(mDestPath, relativePath));
     return sourceDate > destDate;
 }
diff --git a/tools/aapt/DirectoryWalker.h b/tools/aapt/DirectoryWalker.h
index cea3a6e..7f60d4d 100644
--- a/tools/aapt/DirectoryWalker.h
+++ b/tools/aapt/DirectoryWalker.h
@@ -7,6 +7,7 @@
 #ifndef DIRECTORYWALKER_H
 #define DIRECTORYWALKER_H
 
+#include <androidfw/PathUtils.h>
 #include <dirent.h>
 #include <sys/types.h>
 #include <sys/param.h>
@@ -77,7 +78,7 @@
 
         mEntry = *entryPtr;
         // Get stats
-        String8 fullPath = mBasePath.appendPathCopy(mEntry.d_name);
+        String8 fullPath = appendPathCopy(mBasePath, mEntry.d_name);
         stat(fullPath.c_str(),&mStats);
         return &mEntry;
     };
diff --git a/tools/aapt/FileFinder.cpp b/tools/aapt/FileFinder.cpp
index a5c19806..69a8fa9 100644
--- a/tools/aapt/FileFinder.cpp
+++ b/tools/aapt/FileFinder.cpp
@@ -5,6 +5,7 @@
 // File Finder implementation.
 // Implementation for the functions declared and documented in FileFinder.h
 
+#include <androidfw/PathUtils.h>
 #include <utils/Vector.h>
 #include <utils/String8.h>
 #include <utils/KeyedVector.h>
@@ -57,7 +58,7 @@
         if (entry->d_name[0] == '.') // Skip hidden files and directories
             continue;
 
-        String8 fullPath = basePath.appendPathCopy(entryName);
+        String8 fullPath = appendPathCopy(basePath, entryName);
         // If this entry is a directory we'll recurse into it
         if (isDirectory(fullPath.c_str()) ) {
             DirectoryWalker* copy = dw->clone();
@@ -83,10 +84,10 @@
 {
     // Loop over the extensions, checking for a match
     bool done = false;
-    String8 ext(path.getPathExtension());
+    String8 ext(getPathExtension(path));
     ext.toLower();
     for (size_t i = 0; i < extensions.size() && !done; ++i) {
-        String8 ext2 = extensions[i].getPathExtension();
+        String8 ext2 = getPathExtension(extensions[i]);
         ext2.toLower();
         // Compare the extensions. If a match is found, add to storage.
         if (ext == ext2) {
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index c6c7e96..cd4de90 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -8,6 +8,7 @@
 
 #include "Images.h"
 
+#include <androidfw/PathUtils.h>
 #include <androidfw/ResourceTypes.h>
 #include <utils/ByteOrder.h>
 
@@ -1357,7 +1358,7 @@
 status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets */,
                          const sp<AaptFile>& file, String8* /* outNewLeafName */)
 {
-    String8 ext(file->getPath().getPathExtension());
+    String8 ext(getPathExtension(file->getPath()));
 
     // We currently only process PNG images.
     if (strcmp(ext.c_str(), ".png") != 0) {
@@ -1518,7 +1519,7 @@
 
     // Check to see if we're dealing with a 9-patch
     // If we are, process appropriately
-    if (source.getBasePath().getPathExtension() == ".9")  {
+    if (getPathExtension(getBasePath(source)) == ".9")  {
         if (do_9patch(source.c_str(), &imageInfo) != NO_ERROR) {
             return error;
         }
@@ -1584,12 +1585,12 @@
 status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
                           ResourceTable* table, const sp<AaptFile>& file)
 {
-    String8 ext(file->getPath().getPathExtension());
+    String8 ext(getPathExtension(file->getPath()));
 
     // At this point, now that we have all the resource data, all we need to
     // do is compile XML files.
     if (strcmp(ext.c_str(), ".xml") == 0) {
-        String16 resourceName(parseResourceName(file->getSourceFile().getPathLeaf()));
+        String16 resourceName(parseResourceName(getPathLeaf(file->getSourceFile())));
         return compileXmlFile(bundle, assets, resourceName, file, table);
     }
 
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index a7ff5fa..5e0f87f 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -10,6 +10,7 @@
 #include "ResourceFilter.h"
 #include "Utils.h"
 
+#include <androidfw/PathUtils.h>
 #include <androidfw/misc.h>
 
 #include <utils/Log.h>
@@ -170,7 +171,7 @@
     /* anything here? */
     if (zip->getNumEntries() == 0) {
         if (bundle->getVerbose()) {
-            printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().c_str());
+            printf("Archive is empty -- removing %s\n", getPathLeaf(outputFile).c_str());
         }
         delete zip;        // close the file so we can remove it in Win32
         zip = NULL;
@@ -274,9 +275,9 @@
         return true;
     }
 
-    if (strcasecmp(storageName.getPathExtension().c_str(), ".gz") == 0) {
+    if (strcasecmp(getPathExtension(storageName).c_str(), ".gz") == 0) {
         fromGzip = true;
-        storageName = storageName.getBasePath();
+        storageName = getBasePath(storageName);
     }
 
     if (bundle->getUpdate()) {
@@ -366,7 +367,7 @@
  */
 bool okayToCompress(Bundle* bundle, const String8& pathName)
 {
-    String8 ext = pathName.getPathExtension();
+    String8 ext = getPathExtension(pathName);
     int i;
 
     if (ext.length() == 0)
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 4a360ed..647790b 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -19,6 +19,8 @@
 #include "WorkQueue.h"
 #include "XMLNode.h"
 
+#include <androidfw/PathUtils.h>
+
 #include <algorithm>
 
 // STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary.
@@ -143,8 +145,8 @@
                         mParams.inputFlags, mParams.navigation);
             }
             mPath = "res";
-            mPath.appendPath(file->getGroupEntry().toDirName(mResType));
-            mPath.appendPath(leaf);
+            appendPath(mPath, file->getGroupEntry().toDirName(mResType));
+            appendPath(mPath, leaf);
             mBaseName = parseResourceName(leaf);
             if (mBaseName == "") {
                 fprintf(stderr, "Error: malformed resource filename %s\n",
@@ -1686,7 +1688,7 @@
         ResourceDirIterator it(fonts, String8("font"));
         while ((err=it.next()) == NO_ERROR) {
             // fonts can be resources other than xml.
-            if (it.getFile()->getPath().getPathExtension() == ".xml") {
+            if (getPathExtension(it.getFile()->getPath()) == ".xml") {
                 String8 src = it.getFile()->getPrintableSource();
                 err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                         it.getFile(), &table, xmlFlags);
@@ -1716,7 +1718,7 @@
                              workItem.file, &table, xmlCompilationFlags);
 
         if (err == NO_ERROR && workItem.file->hasData()) {
-            assets->addResource(workItem.resPath.getPathLeaf(),
+            assets->addResource(getPathLeaf(workItem.resPath),
                                 workItem.resPath,
                                 workItem.file,
                                 workItem.file->getResourceType());
@@ -2851,7 +2853,7 @@
                 s++;
                 if (s > last && (*s == '.' || *s == 0)) {
                     String8 part(last, s-last);
-                    dest.appendPath(part);
+                    appendPath(dest, part);
 #ifdef _WIN32
                     _mkdir(dest.c_str());
 #else
@@ -2861,7 +2863,7 @@
                 }
             } while (*s);
         }
-        dest.appendPath(className);
+        appendPath(dest, className);
         dest.append(".java");
         FILE* fp = fopen(dest.c_str(), "w+");
         if (fp == NULL) {
@@ -2892,7 +2894,7 @@
 
         if (textSymbolsDest != NULL && R == className) {
             String8 textDest(textSymbolsDest);
-            textDest.appendPath(className);
+            appendPath(textDest, className);
             textDest.append(".txt");
 
             FILE* fp = fopen(textDest.c_str(), "w+");
@@ -2918,7 +2920,7 @@
         if (bundle->getGenDependencies() && R == className) {
             // Add this R.java to the dependency file
             String8 dependencyFile(bundle->getRClassDir());
-            dependencyFile.appendPath("R.java.d");
+            appendPath(dependencyFile, "R.java.d");
 
             FILE *fp = fopen(dependencyFile.c_str(), "a");
             fprintf(fp,"%s \\\n", dest.c_str());
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index bccf73e..421cae7 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -14,6 +14,7 @@
 #include "Utils.h"
 
 #include <algorithm>
+#include <androidfw/PathUtils.h>
 #include <androidfw/ResourceTypes.h>
 #include <utils/ByteOrder.h>
 #include <utils/TypeHelpers.h>
@@ -83,7 +84,7 @@
         sp<AaptDir> resDir = assets->getDirs().valueFor(String8("res"));
         sp<AaptDir> dir = resDir->getDirs().valueFor(target->getGroupEntry().toDirName(
                 target->getResourceType()));
-        dir->removeFile(target->getPath().getPathLeaf());
+        dir->removeFile(getPathLeaf(target->getPath()));
         return NO_ERROR;
     }
 
diff --git a/tools/aapt/Utils.cpp b/tools/aapt/Utils.cpp
index 36b018e..946916a 100644
--- a/tools/aapt/Utils.cpp
+++ b/tools/aapt/Utils.cpp
@@ -36,3 +36,26 @@
     }
 #endif
 }
+
+String8 walkPath(const String8& path, String8* outRemains) {
+    const char* cp;
+    const char* const str = path.c_str();
+    const char* buf = str;
+
+    cp = strchr(buf, OS_PATH_SEPARATOR);
+    if (cp == buf) {
+        // don't include a leading '/'.
+        buf = buf + 1;
+        cp = strchr(buf, OS_PATH_SEPARATOR);
+    }
+
+    if (cp == nullptr) {
+        String8 res = buf != str ? String8(buf) : path;
+        if (outRemains) *outRemains = String8();
+        return res;
+    }
+
+    String8 res(buf, cp - buf);
+    if (outRemains) *outRemains = String8(cp + 1);
+    return res;
+}
diff --git a/tools/aapt/Utils.h b/tools/aapt/Utils.h
index 8eb5941..f0d6979 100644
--- a/tools/aapt/Utils.h
+++ b/tools/aapt/Utils.h
@@ -26,3 +26,13 @@
 // If the default OS separator is backslash, this converts all
 // backslashes to slashes, in-place. Otherwise it does nothing.
 void convertToResPath(android::String8&);
+
+/**
+ * Retrieve the front (root dir) component.  Optionally also return the
+ * remaining components.
+ *
+ * "/tmp/foo/bar.c" --> "tmp" (remain = "foo/bar.c")
+ * "/tmp" --> "tmp" (remain = "")
+ * "bar.c" --> "bar.c" (remain = "")
+ */
+android::String8 walkPath(const android::String8& path, android::String8* outRemains = nullptr);