1 /* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.google.testing.littlemock; 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 * Utility class for helping guess the application data directory. 26 */ 27 public class AppDataDirGuesser { 28 29 /** A single default instance of app data dir guesser, for overriding if you really need to. */ 30 private static volatile AppDataDirGuesser sInjectableInstance = new AppDataDirGuesser(); 31 32 public static void setInstance(AppDataDirGuesser instance) { 33 sInjectableInstance = instance; 34 } 35 36 public static AppDataDirGuesser getsInstance() { 37 return sInjectableInstance; 38 } 39 40 public File guessSuitableDirectoryForGeneratedClasses() { 41 try { 42 ClassLoader classLoader = AppDataDirGuesser.class.getClassLoader(); 43 // Check that we have an instance of the PathClassLoader. 44 Class<?> clazz = Class.forName("dalvik.system.PathClassLoader"); 45 clazz.cast(classLoader); 46 // Use the toString() method to calculate the data directory. 47 String pathFromThisClassLoader = 48 getPathFromPathClassLoader(classLoader, clazz); 49 File[] results = guessPath(pathFromThisClassLoader); 50 if (results.length > 0) { 51 return results[0]; 52 } 53 } catch (ClassCastException e) { 54 // Fall through, we will return null. 55 } catch (ClassNotFoundException e) { 56 // Fall through, we will return null. 57 } 58 return null; 59 } 60 61 private String getPathFromPathClassLoader( 62 ClassLoader classLoader, Class<?> pathClassLoaderClass) { 63 // Prior to ICS, we can simply read the "path" field of the 64 // PathClassLoader. 65 try { 66 Field pathField = pathClassLoaderClass.getDeclaredField("path"); 67 pathField.setAccessible(true); 68 return (String) pathField.get(classLoader); 69 } catch (NoSuchFieldException e) { 70 // Ignore and fall back on parsing the output of toString below 71 } catch (IllegalAccessException e) { 72 // Ignore and fall back on parsing the output of toString below 73 } catch (ClassCastException e) { 74 // Ignore and fall back on parsing the output of toString below 75 } 76 77 // Parsing toString() method: yuck. But no other way to get the path. 78 // Strip out the bit between square brackets, that's our path. 79 String result = classLoader.toString(); 80 int index = result.lastIndexOf('['); 81 result = (index == -1) ? result : result.substring(index + 1); 82 index = result.indexOf(']'); 83 return (index == -1) ? result : result.substring(0, index); 84 } 85 86 // @VisibleForTesting 87 File[] guessPath(String input) { 88 List<File> results = new ArrayList<File>(); 89 for (String potential : splitPathList(input)) { 90 if (!potential.startsWith("/data/app/")) { 91 continue; 92 } 93 int start = "/data/app/".length(); 94 int end = potential.lastIndexOf(".apk"); 95 if (end != potential.length() - 4) { 96 continue; 97 } 98 int dash = potential.indexOf("-"); 99 if (dash != -1) { 100 end = dash; 101 } 102 String packageName = potential.substring(start, end); 103 File dataDir = new File("/data/data/" + packageName); 104 if (isWriteableDirectory(dataDir)) { 105 File cacheDir = new File(dataDir, "cache"); 106 if (fileOrDirExists(cacheDir) || makeDirectory(cacheDir)) { 107 if (isWriteableDirectory(cacheDir)) { 108 results.add(cacheDir); 109 } 110 } 111 } 112 } 113 114 return results.toArray(new File[results.size()]); 115 } 116 117 // @VisibleForTesting 118 static String[] splitPathList(String input) { 119 String trimmed = input; 120 if (input.startsWith("dexPath=")) { 121 int start = "dexPath=".length(); 122 int end = input.indexOf(','); 123 124 trimmed = (end == -1) ? input.substring(start) : 125 input.substring(start, end); 126 } 127 128 return trimmed.split(":"); 129 } 130 131 // @VisibleForTesting 132 boolean fileOrDirExists(File file) { 133 return file.exists(); 134 } 135 136 // @VisibleForTesting 137 boolean makeDirectory(File file) { 138 return file.mkdir(); 139 } 140 141 // @VisibleForTesting 142 boolean isWriteableDirectory(File file) { 143 return file.isDirectory() && file.canWrite(); 144 } 145 } 146