1 /* 2 * Copyright (C) 2020 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.server.pm.parsing; 18 19 import android.annotation.AnyThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.ActivityThread; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageParser; 26 import android.content.pm.PackageParser.PackageParserException; 27 import android.content.pm.parsing.ParsingPackage; 28 import android.content.pm.parsing.ParsingPackageUtils; 29 import android.content.pm.parsing.ParsingUtils; 30 import android.content.pm.parsing.result.ParseInput; 31 import android.content.pm.parsing.result.ParseResult; 32 import android.content.pm.parsing.result.ParseTypeImpl; 33 import android.content.res.TypedArray; 34 import android.os.Build; 35 import android.os.ServiceManager; 36 import android.os.SystemClock; 37 import android.permission.PermissionManager; 38 import android.util.DisplayMetrics; 39 import android.util.Slog; 40 41 import com.android.internal.compat.IPlatformCompat; 42 import com.android.server.pm.PackageManagerService; 43 import com.android.server.pm.parsing.pkg.PackageImpl; 44 import com.android.server.pm.parsing.pkg.ParsedPackage; 45 46 import java.io.File; 47 import java.util.List; 48 49 /** 50 * The v2 of {@link PackageParser} for use when parsing is initiated in the server and must 51 * contain state contained by the server. 52 * 53 * The {@link AutoCloseable} helps signal that this class contains resources that must be freed. 54 * Although it is sufficient to release references to an instance of this class and let it get 55 * collected automatically. 56 */ 57 public class PackageParser2 implements AutoCloseable { 58 59 /** 60 * For parsing inside the system server but outside of {@link PackageManagerService}. 61 * Generally used for parsing information in an APK that hasn't been installed yet. 62 * 63 * This must be called inside the system process as it relies on {@link ServiceManager}. 64 */ 65 @NonNull forParsingFileWithDefaults()66 public static PackageParser2 forParsingFileWithDefaults() { 67 IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( 68 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); 69 return new PackageParser2(null /* separateProcesses */, false /* onlyCoreApps */, 70 null /* displayMetrics */, null /* cacheDir */, new Callback() { 71 @Override 72 public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { 73 try { 74 return platformCompat.isChangeEnabled(changeId, appInfo); 75 } catch (Exception e) { 76 // This shouldn't happen, but assume enforcement if it does 77 Slog.wtf(TAG, "IPlatformCompat query failed", e); 78 return true; 79 } 80 } 81 82 @Override 83 public boolean hasFeature(String feature) { 84 // Assume the device doesn't support anything. This will affect permission parsing 85 // and will force <uses-permission/> declarations to include all requiredNotFeature 86 // permissions and exclude all requiredFeature permissions. This mirrors the old 87 // behavior. 88 return false; 89 } 90 }); 91 } 92 93 private static final String TAG = ParsingUtils.TAG; 94 95 private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE; 96 private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100; 97 98 private ThreadLocal<ApplicationInfo> mSharedAppInfo = 99 ThreadLocal.withInitial(() -> { 100 ApplicationInfo appInfo = new ApplicationInfo(); 101 appInfo.uid = -1; // Not a valid UID since the app will not be installed yet 102 return appInfo; 103 }); 104 105 private ThreadLocal<ParseTypeImpl> mSharedResult; 106 107 @Nullable 108 protected PackageCacher mCacher; 109 110 private ParsingPackageUtils parsingUtils; 111 112 /** 113 * @param onlyCoreApps Flag indicating this parser should only consider apps with 114 * {@code coreApp} manifest attribute to be valid apps. This is useful when 115 * creating a minimalist boot environment. 116 */ 117 public PackageParser2(String[] separateProcesses, boolean onlyCoreApps, 118 DisplayMetrics displayMetrics, @Nullable File cacheDir, @NonNull Callback callback) { 119 if (displayMetrics == null) { 120 displayMetrics = new DisplayMetrics(); 121 displayMetrics.setToDefaults(); 122 } 123 124 PermissionManager permissionManager = ActivityThread.currentApplication() 125 .getSystemService(PermissionManager.class); 126 List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager 127 .getSplitPermissions(); 128 129 mCacher = cacheDir == null ? null : new PackageCacher(cacheDir); 130 131 parsingUtils = new ParsingPackageUtils(onlyCoreApps, separateProcesses, displayMetrics, 132 splitPermissions, callback); 133 134 ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> { 135 ApplicationInfo appInfo = mSharedAppInfo.get(); 136 //noinspection ConstantConditions 137 appInfo.packageName = packageName; 138 appInfo.targetSdkVersion = targetSdkVersion; 139 return callback.isChangeEnabled(changeId, appInfo); 140 }; 141 142 mSharedResult = ThreadLocal.withInitial(() -> new ParseTypeImpl(enforcementCallback)); 143 } 144 145 /** 146 * TODO(b/135203078): Document new package parsing 147 */ 148 @AnyThread 149 public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) 150 throws PackageParserException { 151 if (useCaches && mCacher != null) { 152 ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags); 153 if (parsed != null) { 154 return parsed; 155 } 156 } 157 158 long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; 159 ParseInput input = mSharedResult.get().reset(); 160 ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags); 161 if (result.isError()) { 162 throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(), 163 result.getException()); 164 } 165 166 ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed(); 167 168 long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; 169 if (mCacher != null) { 170 mCacher.cacheResult(packageFile, flags, parsed); 171 } 172 if (LOG_PARSE_TIMINGS) { 173 parseTime = cacheTime - parseTime; 174 cacheTime = SystemClock.uptimeMillis() - cacheTime; 175 if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) { 176 Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime 177 + "ms, update_cache=" + cacheTime + " ms"); 178 } 179 } 180 181 return parsed; 182 } 183 184 /** 185 * Removes the cached value for the thread the parser was created on. It is assumed that 186 * any threads created for parallel parsing will be created and released, so they don't 187 * need an explicit close call. 188 * 189 * Realistically an instance should never be retained, so when the enclosing class is released, 190 * the values will also be released, making this method unnecessary. 191 */ 192 @Override 193 public void close() { 194 mSharedResult.remove(); 195 mSharedAppInfo.remove(); 196 } 197 198 public static abstract class Callback implements ParsingPackageUtils.Callback { 199 200 @Override 201 public final ParsingPackage startParsingPackage(@NonNull String packageName, 202 @NonNull String baseCodePath, @NonNull String codePath, 203 @NonNull TypedArray manifestArray, boolean isCoreApp) { 204 return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, 205 isCoreApp); 206 } 207 208 /** 209 * An indirection from {@link ParseInput.Callback#isChangeEnabled(long, String, int)}, 210 * allowing the {@link ApplicationInfo} objects to be cached in {@link #mSharedAppInfo} 211 * and cleaned up with the parser instance, not the callback instance. 212 * 213 * @param appInfo will only have 3 fields filled in, {@link ApplicationInfo#packageName}, 214 * {@link ApplicationInfo#targetSdkVersion}, and {@link ApplicationInfo#uid} 215 */ 216 public abstract boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo); 217 } 218 } 219