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.os.FabricatedOverlayInfo; 39 import android.os.FabricatedOverlayInternal; 40 import android.os.FabricatedOverlayInternalEntry; 41 import android.os.FileUtils; 42 import android.os.Process; 43 import android.os.UserHandle; 44 import android.text.TextUtils; 45 import android.util.Log; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.util.Preconditions; 49 50 import java.io.File; 51 import java.io.IOException; 52 import java.nio.file.FileVisitResult; 53 import java.nio.file.Files; 54 import java.nio.file.Path; 55 import java.nio.file.SimpleFileVisitor; 56 import java.nio.file.attribute.BasicFileAttributes; 57 import java.util.ArrayList; 58 import java.util.Iterator; 59 import java.util.List; 60 import java.util.Objects; 61 62 /** 63 * This class provides the functionalities of registering an overlay, unregistering an overlay, and 64 * getting the list of overlays information. 65 */ 66 public class OverlayManagerImpl { 67 private static final String TAG = "OverlayManagerImpl"; 68 private static final boolean DEBUG = false; 69 70 private static final String FRRO_EXTENSION = ".frro"; 71 72 private static final String IDMAP_EXTENSION = ".idmap"; 73 74 @VisibleForTesting(visibility = PRIVATE) 75 public static final String SELF_TARGET = ".self_target"; 76 77 @NonNull 78 private final Context mContext; 79 private Path mBasePath; 80 81 /** 82 * Init a OverlayManagerImpl by the context. 83 * 84 * @param context the context to create overlay environment 85 */ 86 @VisibleForTesting(visibility = PACKAGE) 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 checkPackageName(overlayInternal.targetPackageName); 238 Preconditions.checkStringNotEmpty( 239 overlayInternal.targetOverlayable, 240 "Target overlayable should be neither null nor empty string."); 241 242 final ApplicationInfo applicationInfo = mContext.getApplicationInfo(); 243 final String targetPackage = Preconditions.checkStringNotEmpty( 244 applicationInfo.getBaseCodePath()); 245 final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); 246 final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); 247 248 createFrroFile(frroPath.toString(), overlayInternal); 249 try { 250 createIdmapFile( 251 targetPackage, 252 frroPath.toString(), 253 idmapPath.toString(), 254 overlayName, 255 applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */, 256 applicationInfo.isVendor(), 257 applicationInfo.isProduct(), 258 isSameWithTargetSignature(overlayInternal.targetPackageName), 259 applicationInfo.isOdm(), 260 applicationInfo.isOem()); 261 } catch (IOException e) { 262 if (!frroPath.toFile().delete()) { 263 Log.w(TAG, "Failed to delete file " + frroPath); 264 } 265 throw e; 266 } 267 } 268 269 /** 270 * Remove the overlay with the specific name 271 * 272 * @param overlayName the specific name 273 */ 274 @NonUiContext unregisterFabricatedOverlay(@onNull String overlayName)275 public void unregisterFabricatedOverlay(@NonNull String overlayName) { 276 ensureBaseDir(); 277 checkOverlayNameValid(overlayName); 278 final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); 279 final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); 280 281 if (!frroPath.toFile().delete()) { 282 Log.w(TAG, "Failed to delete file " + frroPath); 283 } 284 if (!idmapPath.toFile().delete()) { 285 Log.w(TAG, "Failed to delete file " + idmapPath); 286 } 287 } 288 289 /** 290 * Commit the overlay manager transaction 291 * 292 * @param transaction the overlay manager transaction 293 */ 294 @NonUiContext commit(@onNull OverlayManagerTransaction transaction)295 public void commit(@NonNull OverlayManagerTransaction transaction) 296 throws PackageManager.NameNotFoundException, IOException { 297 Objects.requireNonNull(transaction); 298 299 for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) { 300 final Request request = it.next(); 301 if (request.type == TYPE_REGISTER_FABRICATED) { 302 final FabricatedOverlayInternal fabricatedOverlayInternal = 303 Objects.requireNonNull( 304 request.extras.getParcelable( 305 BUNDLE_FABRICATED_OVERLAY, 306 FabricatedOverlayInternal.class)); 307 308 // populate the mandatory data 309 if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) { 310 fabricatedOverlayInternal.packageName = mContext.getPackageName(); 311 } else { 312 if (!TextUtils.equals( 313 fabricatedOverlayInternal.packageName, mContext.getPackageName())) { 314 throw new IllegalArgumentException("Unknown package name in transaction"); 315 } 316 } 317 318 registerFabricatedOverlay(fabricatedOverlayInternal); 319 } else if (request.type == TYPE_UNREGISTER_FABRICATED) { 320 final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay); 321 unregisterFabricatedOverlay(overlayIdentifier.getOverlayName()); 322 } else { 323 throw new IllegalArgumentException("Unknown request in transaction " + request); 324 } 325 } 326 } 327 328 /** 329 * Get the list of overlays information for the target package name. 330 * 331 * @param targetPackage the target package name 332 * @return the list of overlays information. 333 */ 334 @NonNull getOverlayInfosForTarget(@onNull String targetPackage)335 public List<OverlayInfo> getOverlayInfosForTarget(@NonNull String targetPackage) { 336 ensureBaseDir(); 337 338 final File base = mBasePath.toFile(); 339 final File[] frroFiles = base.listFiles((dir, name) -> { 340 if (!name.endsWith(FRRO_EXTENSION)) { 341 return false; 342 } 343 344 final String idmapFileName = name.substring(0, name.length() - FRRO_EXTENSION.length()) 345 + IDMAP_EXTENSION; 346 final File idmapFile = new File(dir, idmapFileName); 347 return idmapFile.exists(); 348 }); 349 350 final ArrayList<OverlayInfo> overlayInfos = new ArrayList<>(); 351 for (File file : frroFiles) { 352 final FabricatedOverlayInfo fabricatedOverlayInfo; 353 try { 354 fabricatedOverlayInfo = getFabricatedOverlayInfo(file.getAbsolutePath()); 355 } catch (IOException e) { 356 Log.w(TAG, "can't load " + file); 357 continue; 358 } 359 if (!TextUtils.equals(targetPackage, fabricatedOverlayInfo.targetPackageName)) { 360 continue; 361 } 362 if (DEBUG) { 363 Log.i(TAG, "load " + file); 364 } 365 366 final OverlayInfo overlayInfo = 367 new OverlayInfo( 368 fabricatedOverlayInfo.packageName, 369 fabricatedOverlayInfo.overlayName, 370 fabricatedOverlayInfo.targetPackageName, 371 fabricatedOverlayInfo.targetOverlayable, 372 null, 373 file.getAbsolutePath(), 374 OverlayInfo.STATE_ENABLED, 375 UserHandle.myUserId(), 376 DEFAULT_PRIORITY, 377 true /* isMutable */, 378 true /* isFabricated */); 379 overlayInfos.add(overlayInfo); 380 } 381 return overlayInfos; 382 } 383 createFrroFile( @onNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal)384 private static native void createFrroFile( 385 @NonNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal) 386 throws IOException; 387 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)388 private static native void createIdmapFile( 389 @NonNull String targetPath, 390 @NonNull String overlayPath, 391 @NonNull String idmapPath, 392 @NonNull String overlayName, 393 boolean isSystem, 394 boolean isVendor, 395 boolean isProduct, 396 boolean isSameWithTargetSignature, 397 boolean isOdm, 398 boolean isOem) 399 throws IOException; 400 getFabricatedOverlayInfo( @onNull String overlayPath)401 private static native FabricatedOverlayInfo getFabricatedOverlayInfo( 402 @NonNull String overlayPath) throws IOException; 403 } 404