1 /* 2 * Copyright (C) 2021 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 android.content.om; 18 19 import android.annotation.IntDef; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.os.FabricatedOverlayInternal; 24 import android.os.FabricatedOverlayInternalEntry; 25 import android.os.ParcelFileDescriptor; 26 import android.text.TextUtils; 27 import android.util.TypedValue; 28 29 import com.android.internal.content.om.OverlayManagerImpl; 30 import com.android.internal.util.Preconditions; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.ArrayList; 35 import java.util.Objects; 36 37 /** 38 * FabricatedOverlay describes the content of Fabricated Runtime Resource Overlay (FRRO) that is 39 * used to overlay the app's resources. The app should register the {@link FabricatedOverlay} 40 * instance in an {@link OverlayManagerTransaction} by calling {@link 41 * OverlayManagerTransaction#registerFabricatedOverlay(FabricatedOverlay)}. The FRRO is 42 * created once the transaction is committed successfully. 43 * 44 * <p>The app creates a FabricatedOverlay to describe the how to overlay string, integer, and file 45 * type resources. Before creating any frro, please define a target overlayable in {@code 46 * res/values/overlayable.xml} that describes what kind of resources can be overlaid, what kind of 47 * roles or applications can overlay the resources. Here is an example. 48 * 49 * <pre>{@code 50 * <overlayable name="SignatureOverlayable" actor="overlay://theme"> 51 * <!-- The app with the same signature can overlay the below resources --> 52 * <policy type="signature"> 53 * <item type="color" name="mycolor" /> 54 * <item type="string" name="mystring" /> 55 * </policy> 56 * </overlayable> 57 * }</pre> 58 * 59 * <p>The overlay must assign the target overlayable name just like the above example by calling 60 * {@link #setTargetOverlayable(String)}. Here is an example: 61 * 62 * <pre>{@code 63 * FabricatedOverlay fabricatedOverlay = new FabricatedOverlay("overlay_name", 64 * context.getPackageName()); 65 * fabricatedOverlay.setTargetOverlayable("SignatureOverlayable") 66 * fabricatedOverlay.setResourceValue("mycolor", TypedValue.TYPE_INT_COLOR_ARGB8, Color.White) 67 * fabricatedOverlay.setResourceValue("mystring", TypedValue.TYPE_STRING, "Hello") 68 * }</pre> 69 * 70 * <p>The app can create any {@link FabricatedOverlay} instance by calling the following APIs. 71 * 72 * <ul> 73 * <li>{@link #setTargetOverlayable(String)} 74 * <li>{@link #setResourceValue(String, int, int, String)} 75 * <li>{@link #setResourceValue(String, int, String, String)} 76 * <li>{@link #setResourceValue(String, ParcelFileDescriptor, String)} 77 * </ul> 78 * 79 * @see OverlayManager 80 * @see OverlayManagerTransaction 81 */ 82 public class FabricatedOverlay { 83 84 /** 85 * Retrieves the identifier for this fabricated overlay. 86 * @return the overlay identifier 87 */ 88 @NonNull getIdentifier()89 public OverlayIdentifier getIdentifier() { 90 return new OverlayIdentifier( 91 mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName)); 92 } 93 94 /** 95 * The builder of Fabricated Runtime Resource Overlays(FRROs). 96 * 97 * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The 98 * overlayable policies a fabricated overlay fulfills are the same policies the creator of the 99 * overlay fulfill. For example, a fabricated overlay created by a platform signed package on 100 * the system partition would fulfil the {@code system} and {@code signature} policies. 101 * 102 * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay 103 * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are 104 * wiped. 105 * 106 * Processes with {@code android.Manifest.permission#CHANGE_OVERLAY_PACKAGES} can manage normal 107 * overlays and fabricated overlays. 108 * 109 * @see FabricatedOverlay 110 * @see OverlayManagerTransaction.Builder#registerFabricatedOverlay(FabricatedOverlay) 111 * @hide 112 */ 113 public static final class Builder { 114 private final String mOwningPackage; 115 private final String mName; 116 private final String mTargetPackage; 117 private String mTargetOverlayable = ""; 118 private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>(); 119 120 /** 121 * Constructs a build for a fabricated overlay. 122 * 123 * @param owningPackage the name of the package that owns the fabricated overlay (must 124 * be a package name of this UID). 125 * @param name a name used to uniquely identify the fabricated overlay owned by 126 * {@param owningPackageName} 127 * @param targetPackage the name of the package to overlay 128 */ Builder(@onNull String owningPackage, @NonNull String name, @NonNull String targetPackage)129 public Builder(@NonNull String owningPackage, @NonNull String name, 130 @NonNull String targetPackage) { 131 Preconditions.checkStringNotEmpty(owningPackage, 132 "'owningPackage' must not be empty nor null"); 133 Preconditions.checkStringNotEmpty(name, 134 "'name'' must not be empty nor null"); 135 Preconditions.checkStringNotEmpty(targetPackage, 136 "'targetPackage' must not be empty nor null"); 137 138 mOwningPackage = owningPackage; 139 mName = name; 140 mTargetPackage = targetPackage; 141 } 142 143 /** 144 * Sets the name of the target overlayable to be overlaid. 145 * 146 * <p>The target package defines may define several overlayables. The 147 * {@link FabricatedOverlay} should specify which overlayable to be overlaid. 148 * 149 * <p>The target overlayable should be defined in {@code <overlayable>} and pass the value 150 * of its {@code name} attribute as the parameter. 151 * 152 * @param targetOverlayable is a name of the overlayable resources set 153 * @hide 154 */ 155 @NonNull setTargetOverlayable(@ullable String targetOverlayable)156 public Builder setTargetOverlayable(@Nullable String targetOverlayable) { 157 mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable); 158 return this; 159 } 160 161 /** 162 * Sets the value of the fabricated overlay for the integer-like types. 163 * 164 * @param resourceName name of the target resource to overlay (in the form 165 * [package]:type/entry) 166 * @param dataType the data type of the new value 167 * @param value the unsigned 32 bit integer representing the new value 168 * @return the builder itself 169 * @see #setResourceValue(String, int, int, String) 170 * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type 171 * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int, 172 int, String)} instead. 173 * @hide 174 */ 175 @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") 176 @NonNull setResourceValue( @onNull String resourceName, @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType, int value)177 public Builder setResourceValue( 178 @NonNull String resourceName, 179 @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) 180 int dataType, 181 int value) { 182 return setResourceValue(resourceName, dataType, value, null /* configuration */); 183 } 184 185 /** 186 * Sets the value of the fabricated overlay for the integer-like types with the 187 * configuration. 188 * 189 * @param resourceName name of the target resource to overlay (in the form 190 * [package]:type/entry) 191 * @param dataType the data type of the new value 192 * @param value the unsigned 32 bit integer representing the new value 193 * @param configuration The string representation of the config this overlay is enabled for 194 * @return the builder itself 195 * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type 196 * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int, 197 int, String)} instead. 198 * @hide 199 */ 200 @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") 201 @NonNull setResourceValue( @onNull String resourceName, @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType, int value, @Nullable String configuration)202 public Builder setResourceValue( 203 @NonNull String resourceName, 204 @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) 205 int dataType, 206 int value, 207 @Nullable String configuration) { 208 ensureValidResourceName(resourceName); 209 mEntries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value, 210 configuration)); 211 return this; 212 } 213 214 /** 215 * Sets the value of the fabricated overlay for the string-like type. 216 * 217 * @param resourceName name of the target resource to overlay (in the form 218 * [package]:type/entry) 219 * @param dataType the data type of the new value 220 * @param value the string representing the new value 221 * @return the builder itself 222 * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type 223 * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int, 224 String, String)} instead. 225 * @hide 226 */ 227 @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") 228 @NonNull setResourceValue( @onNull String resourceName, @StringTypeOverlayResource int dataType, @NonNull String value)229 public Builder setResourceValue( 230 @NonNull String resourceName, 231 @StringTypeOverlayResource int dataType, 232 @NonNull String value) { 233 return setResourceValue(resourceName, dataType, value, null /* configuration */); 234 } 235 236 /** 237 * Sets the value of the fabricated overlay for the string-like type with the configuration. 238 * 239 * @param resourceName name of the target resource to overlay (in the form 240 * [package]:type/entry) 241 * @param dataType the data type of the new value 242 * @param value the string representing the new value 243 * @param configuration The string representation of the config this overlay is enabled for 244 * @return the builder itself 245 * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type 246 * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int, 247 String, String)} instead. 248 * @hide 249 */ 250 @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") 251 @NonNull setResourceValue( @onNull String resourceName, @StringTypeOverlayResource int dataType, @NonNull String value, @Nullable String configuration)252 public Builder setResourceValue( 253 @NonNull String resourceName, 254 @StringTypeOverlayResource int dataType, 255 @NonNull String value, 256 @Nullable String configuration) { 257 ensureValidResourceName(resourceName); 258 mEntries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value, 259 configuration)); 260 return this; 261 } 262 263 /** 264 * Sets the value of the fabricated overlay for the file descriptor type. 265 * 266 * @param resourceName name of the target resource to overlay (in the form 267 * [package]:type/entry) 268 * @param value the file descriptor whose contents are the value of the frro 269 * @param configuration The string representation of the config this overlay is enabled for 270 * @return the builder itself 271 * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, 272 ParcelFileDescriptor, String)} instead. 273 * @hide 274 */ 275 @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") 276 @NonNull setResourceValue( @onNull String resourceName, @NonNull ParcelFileDescriptor value, @Nullable String configuration)277 public Builder setResourceValue( 278 @NonNull String resourceName, 279 @NonNull ParcelFileDescriptor value, 280 @Nullable String configuration) { 281 ensureValidResourceName(resourceName); 282 mEntries.add( 283 generateFabricatedOverlayInternalEntry(resourceName, value, configuration)); 284 return this; 285 } 286 287 /** 288 * Builds an immutable fabricated overlay. 289 * 290 * @return the fabricated overlay 291 * @hide 292 */ 293 @NonNull build()294 public FabricatedOverlay build() { 295 return new FabricatedOverlay( 296 generateFabricatedOverlayInternal(mOwningPackage, mName, mTargetPackage, 297 mTargetOverlayable, mEntries)); 298 } 299 } 300 generateFabricatedOverlayInternal( @onNull String owningPackage, @NonNull String overlayName, @NonNull String targetPackageName, @Nullable String targetOverlayable, @NonNull ArrayList<FabricatedOverlayInternalEntry> entries)301 private static FabricatedOverlayInternal generateFabricatedOverlayInternal( 302 @NonNull String owningPackage, @NonNull String overlayName, 303 @NonNull String targetPackageName, @Nullable String targetOverlayable, 304 @NonNull ArrayList<FabricatedOverlayInternalEntry> entries) { 305 final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal(); 306 overlay.packageName = owningPackage; 307 overlay.overlayName = overlayName; 308 overlay.targetPackageName = targetPackageName; 309 overlay.targetOverlayable = TextUtils.emptyIfNull(targetOverlayable); 310 overlay.entries = new ArrayList<>(); 311 overlay.entries.addAll(entries); 312 return overlay; 313 } 314 315 final FabricatedOverlayInternal mOverlay; FabricatedOverlay(FabricatedOverlayInternal overlay)316 private FabricatedOverlay(FabricatedOverlayInternal overlay) { 317 mOverlay = overlay; 318 } 319 320 /** 321 * Create a fabricated overlay to overlay on the specified package. 322 * 323 * @param overlayName a name used to uniquely identify the fabricated overlay owned by the 324 * caller itself. 325 * @param targetPackage the name of the package to be overlaid 326 */ FabricatedOverlay(@onNull String overlayName, @NonNull String targetPackage)327 public FabricatedOverlay(@NonNull String overlayName, @NonNull String targetPackage) { 328 this(generateFabricatedOverlayInternal( 329 "" /* owningPackage, The package name is filled commitment */, 330 OverlayManagerImpl.checkOverlayNameValid(overlayName), 331 Preconditions.checkStringNotEmpty(targetPackage, 332 "'targetPackage' must not be empty nor null"), 333 null /* targetOverlayable */, 334 new ArrayList<>())); 335 } 336 337 /** 338 * Set the target overlayable name of the overlay 339 * 340 * The target package defines may define several overlayables. The {@link FabricatedOverlay} 341 * should specify which overlayable to be overlaid. 342 * 343 * @param targetOverlayable the overlayable name defined in target package. 344 */ setTargetOverlayable(@ullable String targetOverlayable)345 public void setTargetOverlayable(@Nullable String targetOverlayable) { 346 mOverlay.targetOverlayable = TextUtils.emptyIfNull(targetOverlayable); 347 } 348 349 /** 350 * Return the target overlayable name of the overlay 351 * 352 * The target package defines may define several overlayables. The {@link FabricatedOverlay} 353 * should specify which overlayable to be overlaid. 354 * 355 * @return the target overlayable name. 356 * @hide 357 */ 358 @Nullable getTargetOverlayable()359 public String getTargetOverlayable() { 360 return mOverlay.targetOverlayable; 361 } 362 363 /** 364 * Ensure the resource name is in the form [package]:type/entry. 365 * 366 * @param name name of the target resource to overlay (in the form [package]:type/entry) 367 * @return the valid name 368 */ ensureValidResourceName(@onNull String name)369 private static String ensureValidResourceName(@NonNull String name) { 370 Objects.requireNonNull(name); 371 final int slashIndex = name.indexOf('/'); /* must contain '/' */ 372 final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */ 373 374 // The minimum length of resource type is "id". 375 Preconditions.checkArgument( 376 slashIndex >= 0 /* It must contain the type name */ 377 && colonIndex != 0 /* 0 means the package name is empty */ 378 && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */, 379 "\"%s\" is invalid resource name", 380 name); 381 return name; 382 } 383 384 @NonNull generateFabricatedOverlayInternalEntry( @onNull String resourceName, @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType, int value, @Nullable String configuration)385 private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry( 386 @NonNull String resourceName, 387 @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType, 388 int value, @Nullable String configuration) { 389 final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); 390 entry.resourceName = resourceName; 391 entry.dataType = 392 Preconditions.checkArgumentInRange( 393 dataType, 394 TypedValue.TYPE_FIRST_INT, 395 TypedValue.TYPE_LAST_INT, 396 "dataType"); 397 entry.data = value; 398 entry.configuration = configuration; 399 return entry; 400 } 401 402 @NonNull generateFabricatedOverlayInternalEntry( @onNull String resourceName, @StringTypeOverlayResource int dataType, @NonNull String value, @Nullable String configuration)403 private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry( 404 @NonNull String resourceName, @StringTypeOverlayResource int dataType, 405 @NonNull String value, @Nullable String configuration) { 406 final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); 407 entry.resourceName = resourceName; 408 entry.dataType = 409 Preconditions.checkArgumentInRange( 410 dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType"); 411 entry.stringData = Objects.requireNonNull(value); 412 entry.configuration = configuration; 413 return entry; 414 } 415 416 @NonNull generateFabricatedOverlayInternalEntry( @onNull String resourceName, @NonNull ParcelFileDescriptor parcelFileDescriptor, @Nullable String configuration)417 private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry( 418 @NonNull String resourceName, @NonNull ParcelFileDescriptor parcelFileDescriptor, 419 @Nullable String configuration) { 420 final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); 421 entry.resourceName = resourceName; 422 entry.binaryData = Objects.requireNonNull(parcelFileDescriptor); 423 entry.configuration = configuration; 424 return entry; 425 } 426 427 /** 428 * Sets the resource value in the fabricated overlay for the integer-like types with the 429 * configuration. 430 * 431 * @param resourceName name of the target resource to overlay (in the form 432 * [package]:type/entry) 433 * @param dataType the data type of the new value 434 * @param value the integer representing the new value 435 * @param configuration The string representation of the config this overlay is enabled for 436 * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type 437 */ 438 @NonNull setResourceValue( @onNull String resourceName, @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType, int value, @Nullable String configuration)439 public void setResourceValue( 440 @NonNull String resourceName, 441 @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType, 442 int value, 443 @Nullable String configuration) { 444 ensureValidResourceName(resourceName); 445 mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value, 446 configuration)); 447 } 448 449 /** @hide */ 450 @IntDef( 451 prefix = {"OVERLAY_TYPE"}, 452 value = { 453 TypedValue.TYPE_STRING, 454 }) 455 @Retention(RetentionPolicy.SOURCE) 456 public @interface StringTypeOverlayResource {} 457 458 /** 459 * Sets the resource value in the fabricated overlay for the string-like type with the 460 * configuration. 461 * 462 * @param resourceName name of the target resource to overlay (in the form 463 * [package]:type/entry) 464 * @param dataType the data type of the new value 465 * @param value the string representing the new value 466 * @param configuration The string representation of the config this overlay is enabled for 467 * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type 468 */ 469 @NonNull setResourceValue( @onNull String resourceName, @StringTypeOverlayResource int dataType, @NonNull String value, @Nullable String configuration)470 public void setResourceValue( 471 @NonNull String resourceName, 472 @StringTypeOverlayResource int dataType, 473 @NonNull String value, 474 @Nullable String configuration) { 475 ensureValidResourceName(resourceName); 476 mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value, 477 configuration)); 478 } 479 480 /** 481 * Sets the resource value in the fabricated overlay for the file descriptor type with the 482 * configuration. 483 * 484 * @param resourceName name of the target resource to overlay (in the form 485 * [package]:type/entry) 486 * @param value the file descriptor whose contents are the value of the frro 487 * @param configuration The string representation of the config this overlay is enabled for 488 */ 489 @NonNull setResourceValue( @onNull String resourceName, @NonNull ParcelFileDescriptor value, @Nullable String configuration)490 public void setResourceValue( 491 @NonNull String resourceName, 492 @NonNull ParcelFileDescriptor value, 493 @Nullable String configuration) { 494 ensureValidResourceName(resourceName); 495 mOverlay.entries.add( 496 generateFabricatedOverlayInternalEntry(resourceName, value, configuration)); 497 } 498 } 499