173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson/* 273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * Copyright (C) 2012 The Android Open Source Project 373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * 473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License"); 573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * you may not use this file except in compliance with the License. 673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * You may obtain a copy of the License at 773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * 873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * http://www.apache.org/licenses/LICENSE-2.0 973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * 1073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * Unless required by applicable law or agreed to in writing, software 1173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS, 1273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * See the License for the specific language governing permissions and 1473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * limitations under the License. 1573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson */ 1673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 17b8a5896885d4da2b798888b688a46df1adbf5b89Paul Duffinpackage com.android.dx; 1873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 1973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilsonimport java.io.File; 20ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubinimport java.lang.reflect.Field; 2173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilsonimport java.util.ArrayList; 2273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilsonimport java.util.List; 2373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 2473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson/** 2573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * Uses heuristics to guess the application's private data directory. 2673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson */ 2773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilsonclass AppDataDirGuesser { 2873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson public File guess() { 2973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson try { 3073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson ClassLoader classLoader = guessSuitableClassLoader(); 3173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson // Check that we have an instance of the PathClassLoader. 3273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson Class<?> clazz = Class.forName("dalvik.system.PathClassLoader"); 3373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson clazz.cast(classLoader); 3473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson // Use the toString() method to calculate the data directory. 35ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin String pathFromThisClassLoader = getPathFromThisClassLoader(classLoader, clazz); 3673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson File[] results = guessPath(pathFromThisClassLoader); 3773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson if (results.length > 0) { 3873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return results[0]; 3973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 4073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } catch (ClassCastException ignored) { 4173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } catch (ClassNotFoundException ignored) { 4273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 4373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return null; 4473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 4573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 4673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson private ClassLoader guessSuitableClassLoader() { 4773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return AppDataDirGuesser.class.getClassLoader(); 4873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 4973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 50b8a5896885d4da2b798888b688a46df1adbf5b89Paul Duffin private String getPathFromThisClassLoader(ClassLoader classLoader, Class<?> pathClassLoaderClass) { 51ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin // Prior to ICS, we can simply read the "path" field of the 52ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin // PathClassLoader. 53ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin try { 54ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin Field pathField = pathClassLoaderClass.getDeclaredField("path"); 55ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin pathField.setAccessible(true); 56ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin return (String) pathField.get(classLoader); 57ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin } catch (NoSuchFieldException ignored) { 58ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin } catch (IllegalAccessException ignored) { 59ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin } catch (ClassCastException ignored) { 60ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin } 61ebb25ef16a03f478946593d2d2ada043e0007220Alex Klyubin 6273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson // Parsing toString() method: yuck. But no other way to get the path. 6373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson String result = classLoader.toString(); 641b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe return processClassLoaderString(result); 651b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 661b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe 671b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe /** 681b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * Given the result of a ClassLoader.toString() call, process the result so that guessPath 691b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * can use it. There are currently two variants. For Android 4.3 and later, the string 701b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * "DexPathList" should be recognized and the array of dex path elements is parsed. for 711b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * earlier versions, the last nested array ('[' ... ']') is enclosing the string we are 721b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * interested in. 731b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe */ 741b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe static String processClassLoaderString(String input) { 751b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe if (input.contains("DexPathList")) { 761b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe return processClassLoaderString43OrLater(input); 771b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } else { 781b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe return processClassLoaderString42OrEarlier(input); 791b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 801b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 811b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe 821b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe private static String processClassLoaderString42OrEarlier(String input) { 831b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe /* The toString output looks like this: 841b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * dalvik.system.PathClassLoader[dexPath=path/to/apk,libraryPath=path/to/libs] 851b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe */ 861b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe int index = input.lastIndexOf('['); 871b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe input = (index == -1) ? input : input.substring(index + 1); 881b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe index = input.indexOf(']'); 891b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe input = (index == -1) ? input : input.substring(0, index); 901b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe return input; 911b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 921b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe 931b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe private static String processClassLoaderString43OrLater(String input) { 941b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe /* The toString output looks like this: 951b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe * dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/{NAME}", ...], nativeLibraryDirectories=[...]]] 961b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe */ 971b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe int start = input.indexOf("DexPathList") + "DexPathList".length(); 981b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe if (input.length() > start + 4) { // [[ + ]] 991b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe String trimmed = input.substring(start); 1001b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe int end = trimmed.indexOf(']'); 1011b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe if (trimmed.charAt(0) == '[' && trimmed.charAt(1) == '[' && end >= 0) { 1021b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe trimmed = trimmed.substring(2, end); 1031b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe // Comma-separated list, Arrays.toString output. 1041b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe String split[] = trimmed.split(","); 1051b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe 1061b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe // Clean up parts. Each path element is the type of the element plus the path in 1071b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe // quotes. 1081b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe for (int i = 0; i < split.length; i++) { 1091b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe int quoteStart = split[i].indexOf('"'); 1101b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe int quoteEnd = split[i].lastIndexOf('"'); 1111b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe if (quoteStart > 0 && quoteStart < quoteEnd) { 1121b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe split[i] = split[i].substring(quoteStart + 1, quoteEnd); 1131b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 1141b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 1151b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe 1161b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe // Need to rejoin components. 1171b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe StringBuilder sb = new StringBuilder(); 1181b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe for (String s : split) { 1191b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe if (sb.length() > 0) { 1201b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe sb.append(':'); 1211b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 1221b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe sb.append(s); 1231b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 1241b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe return sb.toString(); 1251b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 1261b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe } 1271b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe 1281b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe // This is technically a parsing failure. Return the original string, maybe a later 1291b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe // stage can still salvage this. 1301b545bb6e1921249ded43b25789d6fdbdeebec47Andreas Gampe return input; 13173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 13273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 13373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson File[] guessPath(String input) { 134b8a5896885d4da2b798888b688a46df1adbf5b89Paul Duffin List<File> results = new ArrayList<>(); 13595689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy for (String potential : splitPathList(input)) { 13673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson if (!potential.startsWith("/data/app/")) { 13773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson continue; 13873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 13973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson int start = "/data/app/".length(); 14073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson int end = potential.lastIndexOf(".apk"); 14173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson if (end != potential.length() - 4) { 14273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson continue; 14373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 14473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson int dash = potential.indexOf("-"); 14573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson if (dash != -1) { 14673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson end = dash; 14773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 148524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin String packageName = potential.substring(start, end); 149524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin File dataDir = new File("/data/data/" + packageName); 150524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin if (isWriteableDirectory(dataDir)) { 151524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin File cacheDir = new File(dataDir, "cache"); 152524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin // The cache directory might not exist -- create if necessary 153524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin if (fileOrDirExists(cacheDir) || cacheDir.mkdir()) { 154524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin if (isWriteableDirectory(cacheDir)) { 155524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin results.add(cacheDir); 156524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin } 157524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin } 15873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 15973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 16073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return results.toArray(new File[results.size()]); 16173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 16273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 16395689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy static String[] splitPathList(String input) { 16495689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy String trimmed = input; 16595689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy if (input.startsWith("dexPath=")) { 16695689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy int start = "dexPath=".length(); 16795689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy int end = input.indexOf(','); 16895689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy 16995689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy trimmed = (end == -1) ? input.substring(start) : input.substring(start, end); 17095689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy } 17195689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy 17295689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy return trimmed.split(":"); 17395689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy } 17495689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy 175524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin boolean fileOrDirExists(File file) { 176524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin return file.exists(); 177524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin } 178524c023fb37b41e06b69f1b696100dd465acb353Alex Klyubin 17973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson boolean isWriteableDirectory(File file) { 18073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return file.isDirectory() && file.canWrite(); 18173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 18273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson} 183