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 17package com.google.dexmaker; 18 19import java.io.File; 20import java.lang.reflect.Field; 21import java.util.ArrayList; 22import java.util.List; 23 24/** 25 * Uses heuristics to guess the application's private data directory. 26 */ 27class 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, 51 Class<?> pathClassLoaderClass) { 52 // Prior to ICS, we can simply read the "path" field of the 53 // PathClassLoader. 54 try { 55 Field pathField = pathClassLoaderClass.getDeclaredField("path"); 56 pathField.setAccessible(true); 57 return (String) pathField.get(classLoader); 58 } catch (NoSuchFieldException ignored) { 59 } catch (IllegalAccessException ignored) { 60 } catch (ClassCastException ignored) { 61 } 62 63 // Parsing toString() method: yuck. But no other way to get the path. 64 String result = classLoader.toString(); 65 return processClassLoaderString(result); 66 } 67 68 /** 69 * Given the result of a ClassLoader.toString() call, process the result so that guessPath 70 * can use it. There are currently two variants. For Android 4.3 and later, the string 71 * "DexPathList" should be recognized and the array of dex path elements is parsed. for 72 * earlier versions, the last nested array ('[' ... ']') is enclosing the string we are 73 * interested in. 74 */ 75 static String processClassLoaderString(String input) { 76 if (input.contains("DexPathList")) { 77 return processClassLoaderString43OrLater(input); 78 } else { 79 return processClassLoaderString42OrEarlier(input); 80 } 81 } 82 83 private static String processClassLoaderString42OrEarlier(String input) { 84 /* The toString output looks like this: 85 * dalvik.system.PathClassLoader[dexPath=path/to/apk,libraryPath=path/to/libs] 86 */ 87 int index = input.lastIndexOf('['); 88 input = (index == -1) ? input : input.substring(index + 1); 89 index = input.indexOf(']'); 90 input = (index == -1) ? input : input.substring(0, index); 91 return input; 92 } 93 94 private static String processClassLoaderString43OrLater(String input) { 95 /* The toString output looks like this: 96 * dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/{NAME}", ...], nativeLibraryDirectories=[...]]] 97 */ 98 int start = input.indexOf("DexPathList") + "DexPathList".length(); 99 if (input.length() > start + 4) { // [[ + ]] 100 String trimmed = input.substring(start); 101 int end = trimmed.indexOf(']'); 102 if (trimmed.charAt(0) == '[' && trimmed.charAt(1) == '[' && end >= 0) { 103 trimmed = trimmed.substring(2, end); 104 // Comma-separated list, Arrays.toString output. 105 String split[] = trimmed.split(","); 106 107 // Clean up parts. Each path element is the type of the element plus the path in 108 // quotes. 109 for (int i = 0; i < split.length; i++) { 110 int quoteStart = split[i].indexOf('"'); 111 int quoteEnd = split[i].lastIndexOf('"'); 112 if (quoteStart > 0 && quoteStart < quoteEnd) { 113 split[i] = split[i].substring(quoteStart + 1, quoteEnd); 114 } 115 } 116 117 // Need to rejoin components. 118 StringBuilder sb = new StringBuilder(); 119 for (String s : split) { 120 if (sb.length() > 0) { 121 sb.append(':'); 122 } 123 sb.append(s); 124 } 125 return sb.toString(); 126 } 127 } 128 129 // This is technically a parsing failure. Return the original string, maybe a later 130 // stage can still salvage this. 131 return input; 132 } 133 134 File[] guessPath(String input) { 135 List<File> results = new ArrayList<File>(); 136 for (String potential : splitPathList(input)) { 137 if (!potential.startsWith("/data/app/")) { 138 continue; 139 } 140 int start = "/data/app/".length(); 141 int end = potential.lastIndexOf(".apk"); 142 if (end != potential.length() - 4) { 143 continue; 144 } 145 int dash = potential.indexOf("-"); 146 if (dash != -1) { 147 end = dash; 148 } 149 String packageName = potential.substring(start, end); 150 File dataDir = new File("/data/data/" + packageName); 151 if (isWriteableDirectory(dataDir)) { 152 File cacheDir = new File(dataDir, "cache"); 153 // The cache directory might not exist -- create if necessary 154 if (fileOrDirExists(cacheDir) || cacheDir.mkdir()) { 155 if (isWriteableDirectory(cacheDir)) { 156 results.add(cacheDir); 157 } 158 } 159 } 160 } 161 return results.toArray(new File[results.size()]); 162 } 163 164 static String[] splitPathList(String input) { 165 String trimmed = input; 166 if (input.startsWith("dexPath=")) { 167 int start = "dexPath=".length(); 168 int end = input.indexOf(','); 169 170 trimmed = (end == -1) ? input.substring(start) : input.substring(start, end); 171 } 172 173 return trimmed.split(":"); 174 } 175 176 boolean fileOrDirExists(File file) { 177 return file.exists(); 178 } 179 180 boolean isWriteableDirectory(File file) { 181 return file.isDirectory() && file.canWrite(); 182 } 183} 184