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 // Strip out the bit between square brackets, that's our path. 65 String result = classLoader.toString(); 66 int index = result.lastIndexOf('['); 67 result = (index == -1) ? result : result.substring(index + 1); 68 index = result.indexOf(']'); 69 return (index == -1) ? result : result.substring(0, index); 70 } 71 72 File[] guessPath(String input) { 73 List<File> results = new ArrayList<File>(); 74 for (String potential : splitPathList(input)) { 75 if (!potential.startsWith("/data/app/")) { 76 continue; 77 } 78 int start = "/data/app/".length(); 79 int end = potential.lastIndexOf(".apk"); 80 if (end != potential.length() - 4) { 81 continue; 82 } 83 int dash = potential.indexOf("-"); 84 if (dash != -1) { 85 end = dash; 86 } 87 String packageName = potential.substring(start, end); 88 File dataDir = new File("/data/data/" + packageName); 89 if (isWriteableDirectory(dataDir)) { 90 File cacheDir = new File(dataDir, "cache"); 91 // The cache directory might not exist -- create if necessary 92 if (fileOrDirExists(cacheDir) || cacheDir.mkdir()) { 93 if (isWriteableDirectory(cacheDir)) { 94 results.add(cacheDir); 95 } 96 } 97 } 98 } 99 return results.toArray(new File[results.size()]); 100 } 101 102 static String[] splitPathList(String input) { 103 String trimmed = input; 104 if (input.startsWith("dexPath=")) { 105 int start = "dexPath=".length(); 106 int end = input.indexOf(','); 107 108 trimmed = (end == -1) ? input.substring(start) : input.substring(start, end); 109 } 110 111 return trimmed.split(":"); 112 } 113 114 boolean fileOrDirExists(File file) { 115 return file.exists(); 116 } 117 118 boolean isWriteableDirectory(File file) { 119 return file.isDirectory() && file.canWrite(); 120 } 121} 122