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.parsing.result.ParseInput; 26 import android.content.pm.parsing.result.ParseResult; 27 import android.content.pm.parsing.result.ParseTypeImpl; 28 import android.content.res.TypedArray; 29 import android.os.Build; 30 import android.os.ServiceManager; 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.compat.IPlatformCompat; 37 import com.android.internal.util.ArrayUtils; 38 import com.android.server.pm.PackageManagerException; 39 import com.android.server.pm.PackageManagerService; 40 import com.android.server.pm.parsing.pkg.PackageImpl; 41 import com.android.server.pm.parsing.pkg.ParsedPackage; 42 import com.android.server.pm.pkg.parsing.ParsingPackage; 43 import com.android.server.pm.pkg.parsing.ParsingPackageUtils; 44 import com.android.server.pm.pkg.parsing.ParsingUtils; 45 46 import java.io.File; 47 import java.util.List; 48 49 /** 50 * The v2 of package parsing 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 */, null /* displayMetrics */, 70 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 public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics, 113 @Nullable File cacheDir, @NonNull Callback callback) { 114 if (displayMetrics == null) { 115 displayMetrics = new DisplayMetrics(); 116 displayMetrics.setToDefaults(); 117 } 118 119 PermissionManager permissionManager = ActivityThread.currentApplication() 120 .getSystemService(PermissionManager.class); 121 List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager 122 .getSplitPermissions(); 123 124 mCacher = cacheDir == null ? null : new PackageCacher(cacheDir); 125 126 parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions, 127 callback); 128 129 ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> { 130 ApplicationInfo appInfo = mSharedAppInfo.get(); 131 //noinspection ConstantConditions 132 appInfo.packageName = packageName; 133 appInfo.targetSdkVersion = targetSdkVersion; 134 return callback.isChangeEnabled(changeId, appInfo); 135 }; 136 137 mSharedResult = ThreadLocal.withInitial(() -> new ParseTypeImpl(enforcementCallback)); 138 } 139 140 /** 141 * TODO(b/135203078): Document new package parsing 142 */ 143 @AnyThread 144 public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) 145 throws PackageManagerException { 146 var files = packageFile.listFiles(); 147 // Apk directory is directly nested under the current directory 148 if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) { 149 packageFile = files[0]; 150 } 151 152 if (useCaches && mCacher != null) { 153 ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags); 154 if (parsed != null) { 155 return parsed; 156 } 157 } 158 159 long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; 160 ParseInput input = mSharedResult.get().reset(); 161 ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags); 162 if (result.isError()) { 163 throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), 164 result.getException()); 165 } 166 167 ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed(); 168 169 long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; 170 if (mCacher != null) { 171 mCacher.cacheResult(packageFile, flags, parsed); 172 } 173 if (LOG_PARSE_TIMINGS) { 174 parseTime = cacheTime - parseTime; 175 cacheTime = SystemClock.uptimeMillis() - cacheTime; 176 if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) { 177 Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime 178 + "ms, update_cache=" + cacheTime + " ms"); 179 } 180 } 181 182 return parsed; 183 } 184 185 /** 186 * Removes the cached value for the thread the parser was created on. It is assumed that 187 * any threads created for parallel parsing will be created and released, so they don't 188 * need an explicit close call. 189 * 190 * Realistically an instance should never be retained, so when the enclosing class is released, 191 * the values will also be released, making this method unnecessary. 192 */ 193 @Override 194 public void close() { 195 mSharedResult.remove(); 196 mSharedAppInfo.remove(); 197 } 198 199 public static abstract class Callback implements ParsingPackageUtils.Callback { 200 201 @Override 202 public final ParsingPackage startParsingPackage(@NonNull String packageName, 203 @NonNull String baseCodePath, @NonNull String codePath, 204 @NonNull TypedArray manifestArray, boolean isCoreApp) { 205 return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, 206 isCoreApp); 207 } 208 209 /** 210 * An indirection from {@link ParseInput.Callback#isChangeEnabled(long, String, int)}, 211 * allowing the {@link ApplicationInfo} objects to be cached in {@link #mSharedAppInfo} 212 * and cleaned up with the parser instance, not the callback instance. 213 * 214 * @param appInfo will only have 3 fields filled in, {@link ApplicationInfo#packageName}, 215 * {@link ApplicationInfo#targetSdkVersion}, and {@link ApplicationInfo#uid} 216 */ 217 public abstract boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo); 218 } 219 } 220