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