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.internal.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.app.Application; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.parsing.PackageLite; 26 import android.content.pm.parsing.result.ParseInput; 27 import android.content.pm.parsing.result.ParseResult; 28 import android.content.pm.parsing.result.ParseTypeImpl; 29 import android.content.res.TypedArray; 30 import android.os.Build; 31 import android.os.SystemClock; 32 import android.permission.PermissionManager; 33 import android.util.DisplayMetrics; 34 import android.util.Slog; 35 36 import com.android.internal.pm.parsing.pkg.PackageImpl; 37 import com.android.internal.pm.parsing.pkg.ParsedPackage; 38 import com.android.internal.pm.pkg.parsing.ParsingPackage; 39 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; 40 import com.android.internal.pm.pkg.parsing.ParsingUtils; 41 import com.android.internal.util.ArrayUtils; 42 43 import java.io.File; 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * The v2 of package parsing for use when parsing is initiated in the server and must 49 * contain state contained by the server. 50 * 51 * The {@link AutoCloseable} helps signal that this class contains resources that must be freed. 52 * Although it is sufficient to release references to an instance of this class and let it get 53 * collected automatically. 54 */ 55 public class PackageParser2 implements AutoCloseable { 56 57 private static final String TAG = ParsingUtils.TAG; 58 59 private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE; 60 private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100; 61 62 private final ThreadLocal<ApplicationInfo> mSharedAppInfo = 63 ThreadLocal.withInitial(() -> { 64 ApplicationInfo appInfo = new ApplicationInfo(); 65 appInfo.uid = -1; // Not a valid UID since the app will not be installed yet 66 return appInfo; 67 }); 68 69 private final ThreadLocal<ParseTypeImpl> mSharedResult; 70 71 @Nullable 72 protected IPackageCacher mCacher; 73 74 private final ParsingPackageUtils mParsingUtils; 75 PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics, @Nullable IPackageCacher cacher, @NonNull Callback callback)76 public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics, 77 @Nullable IPackageCacher cacher, @NonNull Callback callback) { 78 if (displayMetrics == null) { 79 displayMetrics = new DisplayMetrics(); 80 displayMetrics.setToDefaults(); 81 } 82 83 List<PermissionManager.SplitPermissionInfo> splitPermissions = null; 84 85 final Application application = ActivityThread.currentApplication(); 86 if (application != null) { 87 final PermissionManager permissionManager = 88 application.getSystemService(PermissionManager.class); 89 if (permissionManager != null) { 90 splitPermissions = permissionManager.getSplitPermissions(); 91 } 92 } 93 if (splitPermissions == null) { 94 splitPermissions = new ArrayList<>(); 95 } 96 97 mCacher = cacher; 98 99 mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions, 100 callback); 101 102 ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> { 103 ApplicationInfo appInfo = mSharedAppInfo.get(); 104 //noinspection ConstantConditions 105 appInfo.packageName = packageName; 106 appInfo.targetSdkVersion = targetSdkVersion; 107 return callback.isChangeEnabled(changeId, appInfo); 108 }; 109 110 mSharedResult = ThreadLocal.withInitial(() -> new ParseTypeImpl(enforcementCallback)); 111 } 112 113 /** 114 * TODO(b/135203078): Document new package parsing 115 */ 116 @AnyThread parsePackage(File packageFile, int flags, boolean useCaches)117 public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) 118 throws PackageParserException { 119 var files = packageFile.listFiles(); 120 // Apk directory is directly nested under the current directory 121 if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) { 122 packageFile = files[0]; 123 } 124 125 if (useCaches && mCacher != null) { 126 ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags); 127 if (parsed != null) { 128 return parsed; 129 } 130 } 131 132 long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; 133 ParseInput input = mSharedResult.get().reset(); 134 ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags); 135 if (result.isError()) { 136 throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(), 137 result.getException()); 138 } 139 140 ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed(); 141 142 long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; 143 if (mCacher != null) { 144 mCacher.cacheResult(packageFile, flags, parsed); 145 } 146 if (LOG_PARSE_TIMINGS) { 147 parseTime = cacheTime - parseTime; 148 cacheTime = SystemClock.uptimeMillis() - cacheTime; 149 if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) { 150 Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime 151 + "ms, update_cache=" + cacheTime + " ms"); 152 } 153 } 154 155 return parsed; 156 } 157 158 /** 159 * Creates a ParsedPackage from PackageLite without any additional parsing or processing. 160 * Most fields will get reasonable default values, corresponding to "deleted-keep-data". 161 */ 162 @AnyThread parsePackageFromPackageLite(PackageLite packageLite, int flags)163 public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags) 164 throws PackageParserException { 165 ParseInput input = mSharedResult.get().reset(); 166 ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input, 167 packageLite, flags); 168 if (result.isError()) { 169 throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(), 170 result.getException()); 171 } 172 return result.getResult().hideAsParsed(); 173 } 174 175 /** 176 * Removes the cached value for the thread the parser was created on. It is assumed that 177 * any threads created for parallel parsing will be created and released, so they don't 178 * need an explicit close call. 179 * 180 * Realistically an instance should never be retained, so when the enclosing class is released, 181 * the values will also be released, making this method unnecessary. 182 */ 183 @Override close()184 public void close() { 185 mSharedResult.remove(); 186 mSharedAppInfo.remove(); 187 } 188 189 public abstract static class Callback implements ParsingPackageUtils.Callback { 190 191 @Override startParsingPackage(@onNull String packageName, @NonNull String baseCodePath, @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp)192 public final ParsingPackage startParsingPackage(@NonNull String packageName, 193 @NonNull String baseCodePath, @NonNull String codePath, 194 @NonNull TypedArray manifestArray, boolean isCoreApp) { 195 return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, 196 isCoreApp, Callback.this); 197 } 198 199 /** 200 * An indirection from {@link ParseInput.Callback#isChangeEnabled(long, String, int)}, 201 * allowing the {@link ApplicationInfo} objects to be cached in {@link #mSharedAppInfo} 202 * and cleaned up with the parser instance, not the callback instance. 203 * 204 * @param appInfo will only have 3 fields filled in, {@link ApplicationInfo#packageName}, 205 * {@link ApplicationInfo#targetSdkVersion}, and {@link ApplicationInfo#uid} 206 */ isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo)207 public abstract boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo); 208 } 209 } 210