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