1 /* 2 * Copyright (C) 2022 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.content.om; 18 19 import static android.content.Context.MODE_PRIVATE; 20 import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY; 21 import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED; 22 import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED; 23 24 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 25 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 26 import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY; 27 28 import android.annotation.NonNull; 29 import android.annotation.NonUiContext; 30 import android.content.Context; 31 import android.content.om.OverlayIdentifier; 32 import android.content.om.OverlayInfo; 33 import android.content.om.OverlayManagerTransaction; 34 import android.content.om.OverlayManagerTransaction.Request; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.content.pm.parsing.FrameworkParsingPackageUtils; 38 import android.content.res.AssetManager; 39 import android.content.res.Flags; 40 import android.os.FabricatedOverlayInfo; 41 import android.os.FabricatedOverlayInternal; 42 import android.os.FabricatedOverlayInternalEntry; 43 import android.os.FileUtils; 44 import android.os.Process; 45 import android.os.UserHandle; 46 import android.text.TextUtils; 47 import android.util.Log; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 import com.android.internal.util.Preconditions; 51 52 import java.io.File; 53 import java.io.IOException; 54 import java.nio.file.FileVisitResult; 55 import java.nio.file.Files; 56 import java.nio.file.Path; 57 import java.nio.file.SimpleFileVisitor; 58 import java.nio.file.attribute.BasicFileAttributes; 59 import java.util.ArrayList; 60 import java.util.Iterator; 61 import java.util.List; 62 import java.util.Objects; 63 64 /** 65 * This class provides the functionalities for managing self-targeting overlays, including 66 * registering an overlay, unregistering an overlay, and getting the list of overlays information. 67 */ 68 public class OverlayManagerImpl { 69 private static final String TAG = "OverlayManagerImpl"; 70 private static final boolean DEBUG = false; 71 72 private static final String FRRO_EXTENSION = ".frro"; 73 74 private static final String IDMAP_EXTENSION = ".idmap"; 75 76 @VisibleForTesting(visibility = PRIVATE) 77 public static final String SELF_TARGET = ".self_target"; 78 79 @NonNull 80 private final Context mContext; 81 private Path mBasePath; 82 83 /** 84 * Init a OverlayManagerImpl by the context. 85 * 86 * @param context the context to create overlay environment 87 */ 88 @VisibleForTesting(visibility = PACKAGE) OverlayManagerImpl(@onNull Context context)89 public OverlayManagerImpl(@NonNull Context context) { 90 mContext = Objects.requireNonNull(context); 91 92 if (!Process.myUserHandle().equals(context.getUser())) { 93 throw new SecurityException("Self-Targeting doesn't support multiple user now!"); 94 } 95 } 96 cleanExpiredOverlays(Path selfTargetingBasePath, Path folderForCurrentBaseApk)97 private static void cleanExpiredOverlays(Path selfTargetingBasePath, 98 Path folderForCurrentBaseApk) { 99 try { 100 final String currentBaseFolder = folderForCurrentBaseApk.toString(); 101 final String selfTargetingDir = selfTargetingBasePath.getFileName().toString(); 102 Files.walkFileTree( 103 selfTargetingBasePath, 104 new SimpleFileVisitor<>() { 105 @Override 106 public FileVisitResult preVisitDirectory(Path dir, 107 BasicFileAttributes attrs) 108 throws IOException { 109 final String fileName = dir.getFileName().toString(); 110 return fileName.equals(currentBaseFolder) 111 ? FileVisitResult.SKIP_SUBTREE 112 : super.preVisitDirectory(dir, attrs); 113 } 114 115 @Override 116 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 117 throws IOException { 118 if (!file.toFile().delete()) { 119 Log.w(TAG, "Failed to delete file " + file); 120 } 121 return super.visitFile(file, attrs); 122 } 123 124 @Override 125 public FileVisitResult postVisitDirectory(Path dir, IOException exc) 126 throws IOException { 127 final String fileName = dir.getFileName().toString(); 128 if (!fileName.equals(currentBaseFolder) 129 && !fileName.equals(selfTargetingDir)) { 130 if (!dir.toFile().delete()) { 131 Log.w(TAG, "Failed to delete dir " + dir); 132 } 133 } 134 return super.postVisitDirectory(dir, exc); 135 } 136 }); 137 } catch (IOException e) { 138 Log.w(TAG, "Unknown fail " + e); 139 } 140 } 141 142 /** Ensure the base dir for self-targeting is valid. */ 143 @VisibleForTesting 144 @NonUiContext ensureBaseDir()145 public void ensureBaseDir() { 146 final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath(); 147 final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName(); 148 final File selfTargetingBaseFile = mContext.getDir(SELF_TARGET, MODE_PRIVATE); 149 Preconditions.checkArgument( 150 selfTargetingBaseFile.isDirectory() 151 && selfTargetingBaseFile.exists() 152 && selfTargetingBaseFile.canWrite() 153 && selfTargetingBaseFile.canRead() 154 && selfTargetingBaseFile.canExecute(), 155 "Can't work for this context"); 156 cleanExpiredOverlays(selfTargetingBaseFile.toPath(), baseApkFolderName); 157 158 final File baseFile = new File(selfTargetingBaseFile, baseApkFolderName.toString()); 159 if (!baseFile.exists()) { 160 if (!baseFile.mkdirs()) { 161 Log.w(TAG, "Failed to create directory " + baseFile); 162 } 163 164 // It fails to create frro and idmap files without this setting. 165 FileUtils.setPermissions( 166 baseFile, 167 FileUtils.S_IRWXU, 168 -1 /* uid unchanged */, 169 -1 /* gid unchanged */); 170 } 171 Preconditions.checkArgument( 172 baseFile.isDirectory() 173 && baseFile.exists() 174 && baseFile.canWrite() 175 && baseFile.canRead() 176 && baseFile.canExecute(), // 'list' capability 177 "Can't create a workspace for this context"); 178 179 mBasePath = baseFile.toPath(); 180 } 181 isSameWithTargetSignature(final String targetPackage)182 private boolean isSameWithTargetSignature(final String targetPackage) { 183 final PackageManager packageManager = mContext.getPackageManager(); 184 final String packageName = mContext.getPackageName(); 185 if (TextUtils.equals(packageName, targetPackage)) { 186 return true; 187 } 188 return packageManager.checkSignatures(packageName, targetPackage) 189 == PackageManager.SIGNATURE_MATCH; 190 } 191 192 /** 193 * Check if the overlay name is valid or not. 194 * 195 * @param name the non-check overlay name 196 * @return the valid overlay name 197 */ checkOverlayNameValid(@onNull String name)198 public static String checkOverlayNameValid(@NonNull String name) { 199 final String overlayName = 200 Preconditions.checkStringNotEmpty( 201 name, "overlayName should be neither empty nor null string"); 202 final String checkOverlayNameResult = 203 FrameworkParsingPackageUtils.validateName( 204 overlayName, false /* requireSeparator */, true /* requireFilename */); 205 Preconditions.checkArgument( 206 checkOverlayNameResult == null, 207 TextUtils.formatSimple( 208 "Invalid overlayName \"%s\". The check result is %s.", 209 overlayName, checkOverlayNameResult)); 210 return overlayName; 211 } 212 checkPackageName(@onNull String packageName)213 private void checkPackageName(@NonNull String packageName) { 214 Preconditions.checkStringNotEmpty(packageName); 215 Preconditions.checkArgument( 216 TextUtils.equals(mContext.getPackageName(), packageName), 217 TextUtils.formatSimple( 218 "UID %d doesn't own the package %s", Process.myUid(), packageName)); 219 } 220 221 /** 222 * Save FabricatedOverlay instance as frro and idmap files. 223 * 224 * <p>In order to fill the overlayable policy, it's necessary to collect the information from 225 * app. And then, the information is passed to native layer to fill the overlayable policy 226 * 227 * @param overlayInternal the FabricatedOverlayInternal to be saved. 228 */ 229 @NonUiContext registerFabricatedOverlay(@onNull FabricatedOverlayInternal overlayInternal)230 public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal) 231 throws IOException, PackageManager.NameNotFoundException { 232 ensureBaseDir(); 233 Objects.requireNonNull(overlayInternal); 234 final List<FabricatedOverlayInternalEntry> entryList = 235 Objects.requireNonNull(overlayInternal.entries); 236 Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty"); 237 final String overlayName = checkOverlayNameValid(overlayInternal.overlayName); 238 checkPackageName(overlayInternal.packageName); 239 if (Flags.selfTargetingAndroidResourceFrro()) { 240 Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName); 241 } else { 242 checkPackageName(overlayInternal.targetPackageName); 243 Preconditions.checkStringNotEmpty( 244 overlayInternal.targetOverlayable, 245 "Target overlayable should be neither null nor empty string."); 246 } 247 248 final ApplicationInfo applicationInfo = mContext.getApplicationInfo(); 249 String targetPackage = null; 250 if (Flags.selfTargetingAndroidResourceFrro() && TextUtils.equals( 251 overlayInternal.targetPackageName, "android")) { 252 targetPackage = AssetManager.FRAMEWORK_APK_PATH; 253 } else { 254 targetPackage = Preconditions.checkStringNotEmpty( 255 applicationInfo.getBaseCodePath()); 256 } 257 final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); 258 final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); 259 260 createFrroFile(frroPath.toString(), overlayInternal); 261 try { 262 createIdmapFile( 263 targetPackage, 264 frroPath.toString(), 265 idmapPath.toString(), 266 overlayName, 267 applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */, 268 applicationInfo.isVendor(), 269 applicationInfo.isProduct(), 270 isSameWithTargetSignature(overlayInternal.targetPackageName), 271 applicationInfo.isOdm(), 272 applicationInfo.isOem()); 273 } catch (IOException e) { 274 if (!frroPath.toFile().delete()) { 275 Log.w(TAG, "Failed to delete file " + frroPath); 276 } 277 throw e; 278 } 279 } 280 281 /** 282 * Remove the overlay with the specific name 283 * 284 * @param overlayName the specific name 285 */ 286 @NonUiContext unregisterFabricatedOverlay(@onNull String overlayName)287 public void unregisterFabricatedOverlay(@NonNull String overlayName) { 288 ensureBaseDir(); 289 checkOverlayNameValid(overlayName); 290 final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); 291 final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); 292 293 if (!frroPath.toFile().delete()) { 294 Log.w(TAG, "Failed to delete file " + frroPath); 295 } 296 if (!idmapPath.toFile().delete()) { 297 Log.w(TAG, "Failed to delete file " + idmapPath); 298 } 299 } 300 301 /** 302 * Commit the overlay manager transaction 303 * 304 * @param transaction the overlay manager transaction 305 */ 306 @NonUiContext commit(@onNull OverlayManagerTransaction transaction)307 public void commit(@NonNull OverlayManagerTransaction transaction) 308 throws PackageManager.NameNotFoundException, IOException { 309 Objects.requireNonNull(transaction); 310 311 for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) { 312 final Request request = it.next(); 313 if (request.type == TYPE_REGISTER_FABRICATED) { 314 final FabricatedOverlayInternal fabricatedOverlayInternal = 315 Objects.requireNonNull( 316 request.extras.getParcelable( 317 BUNDLE_FABRICATED_OVERLAY, 318 FabricatedOverlayInternal.class)); 319 320 // populate the mandatory data 321 if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) { 322 fabricatedOverlayInternal.packageName = mContext.getPackageName(); 323 } else { 324 if (!TextUtils.equals( 325 fabricatedOverlayInternal.packageName, mContext.getPackageName())) { 326 throw new IllegalArgumentException("Unknown package name in transaction"); 327 } 328 } 329 330 registerFabricatedOverlay(fabricatedOverlayInternal); 331 } else if (request.type == TYPE_UNREGISTER_FABRICATED) { 332 final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay); 333 unregisterFabricatedOverlay(overlayIdentifier.getOverlayName()); 334 } else { 335 throw new IllegalArgumentException("Unknown request in transaction " + request); 336 } 337 } 338 } 339 340 /** 341 * Get the list of overlays information for the target package name. 342 * 343 * @param targetPackage the target package name 344 * @return the list of overlays information. 345 */ 346 @NonNull getOverlayInfosForTarget(@onNull String targetPackage)347 public List<OverlayInfo> getOverlayInfosForTarget(@NonNull String targetPackage) { 348 ensureBaseDir(); 349 350 final File base = mBasePath.toFile(); 351 final File[] frroFiles = base.listFiles((dir, name) -> { 352 if (!name.endsWith(FRRO_EXTENSION)) { 353 return false; 354 } 355 356 final String idmapFileName = name.substring(0, name.length() - FRRO_EXTENSION.length()) 357 + IDMAP_EXTENSION; 358 final File idmapFile = new File(dir, idmapFileName); 359 return idmapFile.exists(); 360 }); 361 362 final ArrayList<OverlayInfo> overlayInfos = new ArrayList<>(); 363 for (File file : frroFiles) { 364 final FabricatedOverlayInfo fabricatedOverlayInfo; 365 try { 366 fabricatedOverlayInfo = getFabricatedOverlayInfo(file.getAbsolutePath()); 367 } catch (IOException e) { 368 Log.w(TAG, "can't load " + file); 369 continue; 370 } 371 if (!TextUtils.equals(targetPackage, fabricatedOverlayInfo.targetPackageName)) { 372 continue; 373 } 374 if (DEBUG) { 375 Log.i(TAG, "load " + file); 376 } 377 378 final OverlayInfo overlayInfo = 379 new OverlayInfo( 380 fabricatedOverlayInfo.packageName, 381 fabricatedOverlayInfo.overlayName, 382 fabricatedOverlayInfo.targetPackageName, 383 fabricatedOverlayInfo.targetOverlayable, 384 null, 385 file.getAbsolutePath(), 386 OverlayInfo.STATE_ENABLED, 387 UserHandle.myUserId(), 388 DEFAULT_PRIORITY, 389 true /* isMutable */, 390 true /* isFabricated */); 391 overlayInfos.add(overlayInfo); 392 } 393 return overlayInfos; 394 } 395 createFrroFile( @onNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal)396 private static native void createFrroFile( 397 @NonNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal) 398 throws IOException; 399 createIdmapFile( @onNull String targetPath, @NonNull String overlayPath, @NonNull String idmapPath, @NonNull String overlayName, boolean isSystem, boolean isVendor, boolean isProduct, boolean isSameWithTargetSignature, boolean isOdm, boolean isOem)400 private static native void createIdmapFile( 401 @NonNull String targetPath, 402 @NonNull String overlayPath, 403 @NonNull String idmapPath, 404 @NonNull String overlayName, 405 boolean isSystem, 406 boolean isVendor, 407 boolean isProduct, 408 boolean isSameWithTargetSignature, 409 boolean isOdm, 410 boolean isOem) 411 throws IOException; 412 getFabricatedOverlayInfo( @onNull String overlayPath)413 private static native FabricatedOverlayInfo getFabricatedOverlayInfo( 414 @NonNull String overlayPath) throws IOException; 415 } 416