DexManager.java revision b8976d8f22fecaa9ed39276d9d8ded17d35b51a6
1/* 2 * Copyright (C) 2016 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.android.server.pm.dex; 18 19import android.content.pm.PackageInfo; 20import android.content.pm.PackageParser; 21import android.content.pm.ApplicationInfo; 22 23import android.util.Slog; 24 25import com.android.server.pm.PackageManagerServiceUtils; 26 27import java.io.File; 28import java.io.IOException; 29import java.util.List; 30import java.util.HashMap; 31import java.util.HashSet; 32import java.util.Map; 33import java.util.Set; 34 35/** 36 * This class keeps track of how dex files are used. 37 * Every time it gets a notification about a dex file being loaded it tracks 38 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 39 * 40 * TODO(calin): Extract related dexopt functionality from PackageManagerService 41 * into this class. 42 */ 43public class DexManager { 44 private static final String TAG = "DexManager"; 45 46 private static final boolean DEBUG = false; 47 48 // Maps package name to code locations. 49 // It caches the code locations for the installed packages. This allows for 50 // faster lookups (no locks) when finding what package owns the dex file. 51 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 52 53 // PackageDexUsage handles the actual I/O operations. It is responsible to 54 // encode and save the dex usage data. 55 private final PackageDexUsage mPackageDexUsage; 56 57 // Possible outcomes of a dex search. 58 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 59 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 60 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 61 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 62 63 public DexManager() { 64 mPackageCodeLocationsCache = new HashMap<>(); 65 mPackageDexUsage = new PackageDexUsage(); 66 } 67 68 /** 69 * Notify about dex files loads. 70 * Note that this method is invoked when apps load dex files and it should 71 * return as fast as possible. 72 * 73 * @param loadingPackage the package performing the load 74 * @param dexPaths the list of dex files being loaded 75 * @param loaderIsa the ISA of the app loading the dex files 76 * @param loaderUserId the user id which runs the code loading the dex files 77 */ 78 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, 79 String loaderIsa, int loaderUserId) { 80 try { 81 notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); 82 } catch (Exception e) { 83 Slog.w(TAG, "Exception while notifying dex load for package " + 84 loadingAppInfo.packageName, e); 85 } 86 } 87 88 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, 89 String loaderIsa, int loaderUserId) { 90 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 91 Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + 92 loaderIsa + "?"); 93 return; 94 } 95 96 for (String dexPath : dexPaths) { 97 // Find the owning package name. 98 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 99 100 if (DEBUG) { 101 Slog.i(TAG, loadingAppInfo.packageName 102 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 103 } 104 105 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 106 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 107 // different apps share the same runtime. In that case we should not mark the dex 108 // file as isUsedByOtherApps. Currently this is a safe approximation. 109 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 110 searchResult.mOwningPackageName); 111 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 112 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 113 114 if (primaryOrSplit && !isUsedByOtherApps) { 115 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 116 // do not record it. This case does not bring any new usable information 117 // and can be safely skipped. 118 continue; 119 } 120 121 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, 122 // or UsedBytOtherApps), record will return true and we trigger an async write 123 // to disk to make sure we don't loose the data in case of a reboot. 124 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 125 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) { 126 mPackageDexUsage.maybeWriteAsync(); 127 } 128 } else { 129 // This can happen in a few situations: 130 // - bogus dex loads 131 // - recent installs/uninstalls that we didn't detect. 132 // - new installed splits 133 // If we can't find the owner of the dex we simply do not track it. The impact is 134 // that the dex file will not be considered for offline optimizations. 135 // TODO(calin): add hooks for install/uninstall notifications to 136 // capture new or obsolete packages. 137 if (DEBUG) { 138 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 139 } 140 } 141 } 142 } 143 144 /** 145 * Read the dex usage from disk and populate the code cache locations. 146 * @param existingPackages a map containing information about what packages 147 * are available to what users. Only packages in this list will be 148 * recognized during notifyDexLoad(). 149 */ 150 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 151 try { 152 loadInternal(existingPackages); 153 } catch (Exception e) { 154 mPackageDexUsage.clear(); 155 Slog.w(TAG, "Exception while loading package dex usage. " + 156 "Starting with a fresh state.", e); 157 } 158 } 159 160 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 161 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 162 // Cache the code locations for the installed packages. This allows for 163 // faster lookups (no locks) when finding what package owns the dex file. 164 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 165 List<PackageInfo> packageInfoList = entry.getValue(); 166 int userId = entry.getKey(); 167 for (PackageInfo pi : packageInfoList) { 168 // Cache the code locations. 169 PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName); 170 if (pcl != null) { 171 pcl.mergeAppDataDirs(pi.applicationInfo, userId); 172 } else { 173 mPackageCodeLocationsCache.put(pi.packageName, 174 new PackageCodeLocations(pi.applicationInfo, userId)); 175 } 176 // Cache a map from package name to the set of user ids who installed the package. 177 // We will use it to sync the data and remove obsolete entries from 178 // mPackageDexUsage. 179 Set<Integer> users = putIfAbsent( 180 packageToUsersMap, pi.packageName, new HashSet<>()); 181 users.add(userId); 182 } 183 } 184 185 mPackageDexUsage.read(); 186 mPackageDexUsage.syncData(packageToUsersMap); 187 } 188 189 /** 190 * Get the package dex usage for the given package name. 191 * @return the package data or null if there is no data available for this package. 192 */ 193 public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) { 194 return mPackageDexUsage.getPackageUseInfo(packageName); 195 } 196 197 /** 198 * Retrieves the package which owns the given dexPath. 199 */ 200 private DexSearchResult getDexPackage( 201 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 202 // Ignore framework code. 203 // TODO(calin): is there a better way to detect it? 204 if (dexPath.startsWith("/system/framework/")) { 205 new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); 206 } 207 208 // First, check if the package which loads the dex file actually owns it. 209 // Most of the time this will be true and we can return early. 210 PackageCodeLocations loadingPackageCodeLocations = 211 new PackageCodeLocations(loadingAppInfo, userId); 212 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 213 if (outcome != DEX_SEARCH_NOT_FOUND) { 214 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 215 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 216 } 217 218 // The loadingPackage does not own the dex file. 219 // Perform a reverse look-up in the cache to detect if any package has ownership. 220 // Note that we can have false negatives if the cache falls out of date. 221 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 222 outcome = pcl.searchDex(dexPath, userId); 223 if (outcome != DEX_SEARCH_NOT_FOUND) { 224 return new DexSearchResult(pcl.mPackageName, outcome); 225 } 226 } 227 228 // Cache miss. Return not found for the moment. 229 // 230 // TODO(calin): this may be because of a newly installed package, an update 231 // or a new added user. We can either perform a full look up again or register 232 // observers to be notified of package/user updates. 233 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 234 } 235 236 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 237 V existingValue = map.putIfAbsent(key, newValue); 238 return existingValue == null ? newValue : existingValue; 239 } 240 241 /** 242 * Convenience class to store the different locations where a package might 243 * own code. 244 */ 245 private static class PackageCodeLocations { 246 private final String mPackageName; 247 private final String mBaseCodePath; 248 private final Set<String> mSplitCodePaths; 249 // Maps user id to the application private directory. 250 private final Map<Integer, Set<String>> mAppDataDirs; 251 252 public PackageCodeLocations(ApplicationInfo ai, int userId) { 253 mPackageName = ai.packageName; 254 mBaseCodePath = ai.sourceDir; 255 mSplitCodePaths = new HashSet<>(); 256 if (ai.splitSourceDirs != null) { 257 for (String split : ai.splitSourceDirs) { 258 mSplitCodePaths.add(split); 259 } 260 } 261 mAppDataDirs = new HashMap<>(); 262 mergeAppDataDirs(ai, userId); 263 } 264 265 public void mergeAppDataDirs(ApplicationInfo ai, int userId) { 266 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 267 dataDirs.add(ai.dataDir); 268 269 // Compute and cache the real path as well since data dir may be a symlink. 270 // e.g. /data/data/ -> /data/user/0/ 271 try { 272 dataDirs.add(PackageManagerServiceUtils.realpath(new File(ai.dataDir))); 273 } catch (IOException e) { 274 Slog.w(TAG, "Error to get realpath of " + ai.dataDir, e); 275 } 276 277 } 278 279 public int searchDex(String dexPath, int userId) { 280 // First check that this package is installed or active for the given user. 281 // If we don't have a data dir it means this user is trying to load something 282 // unavailable for them. 283 Set<String> userDataDirs = mAppDataDirs.get(userId); 284 if (userDataDirs == null) { 285 Slog.w(TAG, "Trying to load a dex path which does not exist for the current " + 286 "user. dexPath=" + dexPath + ", userId=" + userId); 287 return DEX_SEARCH_NOT_FOUND; 288 } 289 290 if (mBaseCodePath.equals(dexPath)) { 291 return DEX_SEARCH_FOUND_PRIMARY; 292 } 293 if (mSplitCodePaths.contains(dexPath)) { 294 return DEX_SEARCH_FOUND_SPLIT; 295 } 296 for (String dataDir : userDataDirs) { 297 if (dexPath.startsWith(dataDir)) { 298 return DEX_SEARCH_FOUND_SECONDARY; 299 } 300 } 301 return DEX_SEARCH_NOT_FOUND; 302 } 303 } 304 305 /** 306 * Convenience class to store ownership search results. 307 */ 308 private class DexSearchResult { 309 private String mOwningPackageName; 310 private int mOutcome; 311 312 public DexSearchResult(String owningPackageName, int outcome) { 313 this.mOwningPackageName = owningPackageName; 314 this.mOutcome = outcome; 315 } 316 317 @Override 318 public String toString() { 319 return mOwningPackageName + "-" + mOutcome; 320 } 321 } 322 323 324} 325