xref: /aosp_15_r20/external/robolectric/resources/src/main/java/org/robolectric/res/android/DynamicRefTable.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.res.android;
2 
3 // transliterated from
4 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h
5 
6 import static org.robolectric.res.android.Errors.BAD_TYPE;
7 import static org.robolectric.res.android.Errors.NO_ERROR;
8 import static org.robolectric.res.android.Errors.UNKNOWN_ERROR;
9 import static org.robolectric.res.android.ResTable.APP_PACKAGE_ID;
10 import static org.robolectric.res.android.ResTable.Res_GETPACKAGE;
11 import static org.robolectric.res.android.ResTable.SYS_PACKAGE_ID;
12 import static org.robolectric.res.android.Util.ALOGW;
13 
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.Map.Entry;
17 import java.util.Objects;
18 import org.robolectric.res.android.ResourceTypes.Res_value;
19 
20 /**
21  * Holds the shared library ID table. Shared libraries are assigned package IDs at build time, but
22  * they may be loaded in a different order, so we need to maintain a mapping of build-time package
23  * ID to run-time assigned package ID.
24  *
25  * <p>Dynamic references are not currently supported in overlays. Only the base package may have
26  * dynamic references.
27  */
28 public class DynamicRefTable {
DynamicRefTable(byte packageId, boolean appAsLib)29   DynamicRefTable(byte packageId, boolean appAsLib) {
30     this.mAssignedPackageId = packageId;
31     this.mAppAsLib = appAsLib;
32 
33     mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
34     mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
35   }
36 
37   //  // Loads an unmapped reference table from the package.
38   //  Errors load(final ResTable_lib_header header) {
39   //    return null;
40   //  }
41 
42   // Adds mappings from the other DynamicRefTable
addMappings(final DynamicRefTable other)43   int addMappings(final DynamicRefTable other) {
44     if (mAssignedPackageId != other.mAssignedPackageId) {
45       return UNKNOWN_ERROR;
46     }
47 
48     //    final int entryCount = other.mEntries.size();
49     //    for (size_t i = 0; i < entryCount; i++) {
50     //      ssize_t index = mEntries.indexOfKey(other.mEntries.keyAt(i));
51     //      if (index < 0) {
52     //        mEntries.add(other.mEntries.keyAt(i), other.mEntries[i]);
53     //      } else {
54     //        if (other.mEntries[i] != mEntries[index]) {
55     //          return UNKNOWN_ERROR;
56     //        }
57     //      }
58     //    }
59     for (Entry<String, Byte> otherEntry : other.mEntries.entrySet()) {
60       String key = otherEntry.getKey();
61       Byte curValue = mEntries.get(key);
62       if (curValue == null) {
63         mEntries.put(key, otherEntry.getValue());
64       } else {
65         if (!Objects.equals(otherEntry.getValue(), curValue)) {
66           return UNKNOWN_ERROR;
67         }
68       }
69     }
70 
71     // Merge the lookup table. No entry can conflict
72     // (value of 0 means not set).
73     for (int i = 0; i < 256; i++) {
74       if (mLookupTable[i] != other.mLookupTable[i]) {
75         if (mLookupTable[i] == 0) {
76           mLookupTable[i] = other.mLookupTable[i];
77         } else if (other.mLookupTable[i] != 0) {
78           return UNKNOWN_ERROR;
79         }
80       }
81     }
82     return NO_ERROR;
83   }
84 
85   // Creates a mapping from build-time package ID to run-time package ID for
86   // the given package.
addMapping(final String packageName, byte packageId)87   int addMapping(final String packageName, byte packageId) {
88     Byte index = mEntries.get(packageName);
89     if (index == null) {
90       return UNKNOWN_ERROR;
91     }
92     mLookupTable[index] = packageId;
93     return NO_ERROR;
94   }
95 
addAlias(int stagedId, int finalizedId)96   void addAlias(int stagedId, int finalizedId) {
97     mAliasId.put(stagedId, finalizedId);
98   }
99 
100   //  // Performs the actual conversion of build-time resource ID to run-time
101   //  // resource ID.
lookupResourceId(Ref<Integer> resId)102   int lookupResourceId(Ref<Integer> resId) {
103     int res = resId.get();
104     int packageId = Res_GETPACKAGE(res) + 1;
105 
106     Integer aliasId = mAliasId.get(res);
107     if (aliasId != null) {
108       // Rewrite the resource id to its alias resource id. Since the alias resource id is a
109       // compile-time id, it still needs to be resolved further.
110       res = aliasId;
111     }
112 
113     if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
114       // No lookup needs to be done, app and framework package IDs are absolute.
115       resId.set(res);
116       return NO_ERROR;
117     }
118 
119     if (packageId == 0 || (packageId == APP_PACKAGE_ID && mAppAsLib)) {
120       // The package ID is 0x00. That means that a shared library is accessing
121       // its own local resource.
122       // Or if app resource is loaded as shared library, the resource which has
123       // app package Id is local resources.
124       // so we fix up those resources with the calling package ID.
125       resId.set((0xFFFFFF & resId.get()) | (((int) mAssignedPackageId) << 24));
126       return NO_ERROR;
127     }
128 
129     // Do a proper lookup.
130     int translatedId = mLookupTable[packageId];
131     if (translatedId == 0) {
132       ALOGW(
133           "DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
134           mAssignedPackageId, packageId);
135       for (int i = 0; i < 256; i++) {
136         if (mLookupTable[i] != 0) {
137           ALOGW("e[0x%02x] . 0x%02x", i, mLookupTable[i]);
138         }
139       }
140       return UNKNOWN_ERROR;
141     }
142 
143     resId.set((res & 0x00ffffff) | (((int) translatedId) << 24));
144     return NO_ERROR;
145   }
146 
147   //
lookupResourceValue(Ref<Res_value> value)148   int lookupResourceValue(Ref<Res_value> value) {
149     byte resolvedType = DataType.REFERENCE.code();
150     Res_value inValue = value.get();
151 
152     DataType dataType;
153     try {
154       dataType = DataType.fromCode(inValue.dataType);
155     } catch (IllegalArgumentException e) {
156       return BAD_TYPE;
157     }
158 
159     switch (dataType) {
160       case ATTRIBUTE:
161         resolvedType = DataType.ATTRIBUTE.code();
162       // fallthrough
163       case REFERENCE:
164         if (!mAppAsLib) {
165           return NO_ERROR;
166         }
167 
168         // If the package is loaded as shared library, the resource reference
169         // also need to be fixed.
170         break;
171       case DYNAMIC_ATTRIBUTE:
172         resolvedType = DataType.ATTRIBUTE.code();
173       // fallthrough
174       case DYNAMIC_REFERENCE:
175         break;
176       default:
177         return NO_ERROR;
178     }
179 
180     final Ref<Integer> resIdRef = new Ref<>(inValue.data);
181     int err = lookupResourceId(resIdRef);
182     value.set(inValue.withData(resIdRef.get()));
183     if (err != NO_ERROR) {
184       return err;
185     }
186 
187     value.set(new Res_value(resolvedType, resIdRef.get()));
188     return NO_ERROR;
189   }
190 
entries()191   public Map<String, Byte> entries() {
192     return mEntries;
193   }
194 
195   //
196   //  final KeyedVector<String16, uint8_t>& entries() final {
197   //  return mEntries;
198   // }
199   //
200   //  private:
201   final byte mAssignedPackageId;
202   final byte[] mLookupTable = new byte[256];
203   final Map<String, Byte> mEntries = new HashMap<>();
204   boolean mAppAsLib;
205   final Map<Integer, Integer> mAliasId = new HashMap<>();
206 }
207