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