1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx; 18 19 import java.io.File; 20 import java.lang.reflect.Field; 21 import java.util.ArrayList; 22 import java.util.List; 23 24 /** 25 * Uses heuristics to guess the application's private data directory. 26 */ 27 class AppDataDirGuesser { 28 public File guess() { 29 try { 30 ClassLoader classLoader = guessSuitableClassLoader(); 31 // Check that we have an instance of the PathClassLoader. 32 Class<?> clazz = Class.forName("dalvik.system.PathClassLoader"); 33 clazz.cast(classLoader); 34 // Use the toString() method to calculate the data directory. 35 String pathFromThisClassLoader = getPathFromThisClassLoader(classLoader, clazz); 36 File[] results = guessPath(pathFromThisClassLoader); 37 if (results.length > 0) { 38 return results[0]; 39 } 40 } catch (ClassCastException ignored) { 41 } catch (ClassNotFoundException ignored) { 42 } 43 return null; 44 } 45 46 private ClassLoader guessSuitableClassLoader() { 47 return AppDataDirGuesser.class.getClassLoader(); 48 } 49 50 private String getPathFromThisClassLoader(ClassLoader classLoader, Class<?> pathClassLoaderClass) { 51 // Prior to ICS, we can simply read the "path" field of the 52 // PathClassLoader. 53 try { 54 Field pathField = pathClassLoaderClass.getDeclaredField("path"); 55 pathField.setAccessible(true); 56 return (String) pathField.get(classLoader); 57 } catch (NoSuchFieldException ignored) { 58 } catch (IllegalAccessException ignored) { 59 } catch (ClassCastException ignored) { 60 } 61 62 // Parsing toString() method: yuck. But no other way to get the path. 63 String result = classLoader.toString(); 64 return processClassLoaderString(result); 65 } 66 67 /** 68 * Given the result of a ClassLoader.toString() call, process the result so that guessPath 69 * can use it. There are currently two variants. For Android 4.3 and later, the string 70 * "DexPathList" should be recognized and the array of dex path elements is parsed. for 71 * earlier versions, the last nested array ('[' ... ']') is enclosing the string we are 72 * interested in. 73 */ 74 static String processClassLoaderString(String input) { 75 if (input.contains("DexPathList")) { 76 return processClassLoaderString43OrLater(input); 77 } else { 78 return processClassLoaderString42OrEarlier(input); 79 } 80 } 81 82 private static String processClassLoaderString42OrEarlier(String input) { 83 /* The toString output looks like this: 84 * dalvik.system.PathClassLoader[dexPath=path/to/apk,libraryPath=path/to/libs] 85 */ 86 int index = input.lastIndexOf('['); 87 input = (index == -1) ? input : input.substring(index + 1); 88 index = input.indexOf(']'); 89 input = (index == -1) ? input : input.substring(0, index); 90 return input; 91 } 92 93 private static String processClassLoaderString43OrLater(String input) { 94 /* The toString output looks like this: 95 * dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/{NAME}", ...], nativeLibraryDirectories=[...]]] 96 */ 97 int start = input.indexOf("DexPathList") + "DexPathList".length(); 98 if (input.length() > start + 4) { // [[ + ]] 99 String trimmed = input.substring(start); 100 int end = trimmed.indexOf(']'); 101 if (trimmed.charAt(0) == '[' && trimmed.charAt(1) == '[' && end >= 0) { 102 trimmed = trimmed.substring(2, end); 103 // Comma-separated list, Arrays.toString output. 104 String split[] = trimmed.split(","); 105 106 // Clean up parts. Each path element is the type of the element plus the path in 107 // quotes. 108 for (int i = 0; i < split.length; i++) { 109 int quoteStart = split[i].indexOf('"'); 110 int quoteEnd = split[i].lastIndexOf('"'); 111 if (quoteStart > 0 && quoteStart < quoteEnd) { 112 split[i] = split[i].substring(quoteStart + 1, quoteEnd); 113 } 114 } 115 116 // Need to rejoin components. 117 StringBuilder sb = new StringBuilder(); 118 for (String s : split) { 119 if (sb.length() > 0) { 120 sb.append(':'); 121 } 122 sb.append(s); 123 } 124 return sb.toString(); 125 } 126 } 127 128 // This is technically a parsing failure. Return the original string, maybe a later 129 // stage can still salvage this. 130 return input; 131 } 132 133 File[] guessPath(String input) { 134 List<File> results = new ArrayList<>(); 135 for (String potential : splitPathList(input)) { 136 if (!potential.startsWith("/data/app/")) { 137 continue; 138 } 139 int start = "/data/app/".length(); 140 int end = potential.lastIndexOf(".apk"); 141 if (end != potential.length() - 4) { 142 continue; 143 } 144 int dash = potential.indexOf("-"); 145 if (dash != -1) { 146 end = dash; 147 } 148 String packageName = potential.substring(start, end); 149 File dataDir = new File("/data/data/" + packageName); 150 if (isWriteableDirectory(dataDir)) { 151 File cacheDir = new File(dataDir, "cache"); 152 // The cache directory might not exist -- create if necessary 153 if (fileOrDirExists(cacheDir) || cacheDir.mkdir()) { 154 if (isWriteableDirectory(cacheDir)) { 155 results.add(cacheDir); 156 } 157 } 158 } 159 } 160 return results.toArray(new File[results.size()]); 161 } 162 163 static String[] splitPathList(String input) { 164 String trimmed = input; 165 if (input.startsWith("dexPath=")) { 166 int start = "dexPath=".length(); 167 int end = input.indexOf(','); 168 169 trimmed = (end == -1) ? input.substring(start) : input.substring(start, end); 170 } 171 172 return trimmed.split(":"); 173 } 174 175 boolean fileOrDirExists(File file) { 176 return file.exists(); 177 } 178 179 boolean isWriteableDirectory(File file) { 180 return file.isDirectory() && file.canWrite(); 181 } 182 } 183