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