summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java18
-rw-r--r--apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java52
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java43
-rw-r--r--api/current.txt40
-rw-r--r--api/system-current.txt16
-rw-r--r--api/test-current.txt5
-rw-r--r--cmds/statsd/src/external/StatsPullerManager.cpp7
-rw-r--r--core/java/android/app/ResourcesManager.java693
-rw-r--r--core/java/android/content/Context.java7
-rw-r--r--core/java/android/content/res/ApkAssets.java122
-rw-r--r--core/java/android/content/res/AssetManager.java145
-rw-r--r--core/java/android/content/res/Resources.java177
-rw-r--r--core/java/android/content/res/ResourcesImpl.java47
-rw-r--r--core/java/android/content/res/StringBlock.java6
-rw-r--r--core/java/android/content/res/ThemedResourceCache.java16
-rw-r--r--core/java/android/content/res/loader/DirectoryResourceLoader.java74
-rw-r--r--core/java/android/content/res/loader/ResourceLoader.java116
-rw-r--r--core/java/android/content/res/loader/ResourceLoaderManager.java189
-rw-r--r--core/java/android/content/res/loader/ResourcesProvider.java139
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java11
-rw-r--r--core/java/android/provider/MediaStore.java23
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java46
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp74
-rw-r--r--core/jni/android_os_Debug.cpp4
-rw-r--r--core/jni/android_util_AssetManager.cpp38
-rw-r--r--core/res/res/layout/resolve_list_item.xml20
-rw-r--r--core/res/res/layout/resolver_different_item_header.xml16
-rw-r--r--core/res/res/layout/resolver_list.xml63
-rw-r--r--core/res/res/layout/resolver_list_with_default.xml76
-rw-r--r--core/res/res/values/dimens.xml12
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/ResourceLoaderTests/Android.bp63
-rw-r--r--core/tests/ResourceLoaderTests/AndroidManifest.xml42
-rw-r--r--core/tests/ResourceLoaderTests/AndroidTest.xml37
-rw-r--r--core/tests/ResourceLoaderTests/NonAsset.txt1
-rw-r--r--core/tests/ResourceLoaderTests/SplitOne/Android.bp19
-rw-r--r--core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml27
-rw-r--r--core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml21
-rw-r--r--core/tests/ResourceLoaderTests/assets/Asset.txt1
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jarbin0 -> 689094 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-reflect.jarbin0 -> 2796397 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jarbin0 -> 2453 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jarbin0 -> 3125 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jarbin0 -> 5568 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jarbin0 -> 13827 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jarbin0 -> 415274 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jarbin0 -> 1290543 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jarbin0 -> 3136 bytes
-rw-r--r--core/tests/ResourceLoaderTests/lib/kotlin-test.jarbin0 -> 30966 bytes
-rw-r--r--core/tests/ResourceLoaderTests/overlay/Android.bp20
-rw-r--r--core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml27
-rw-r--r--core/tests/ResourceLoaderTests/overlay/res/values/strings.xml22
-rw-r--r--core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.pngbin0 -> 146 bytes
-rw-r--r--core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml21
-rw-r--r--core/tests/ResourceLoaderTests/res/layout/layout.xml23
-rw-r--r--core/tests/ResourceLoaderTests/res/values/strings.xml23
-rw-r--r--core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml26
-rw-r--r--core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml27
-rwxr-xr-xcore/tests/ResourceLoaderTests/resources/compileAndLink.sh108
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.pngbin0 -> 1445 bytes
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.pngbin0 -> 146 bytes
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.pngbin0 -> 146 bytes
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml21
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml21
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml23
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml23
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml23
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml20
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml21
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml21
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml20
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml20
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml20
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml20
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/string_one.xml21
-rw-r--r--core/tests/ResourceLoaderTests/resources/res/values/string_two.xml21
-rw-r--r--core/tests/ResourceLoaderTests/splits/Android.bp19
-rw-r--r--core/tests/ResourceLoaderTests/splits/AndroidManifest.xml27
-rw-r--r--core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml21
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt101
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt169
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt236
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt183
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt153
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt226
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt354
-rw-r--r--core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt72
-rw-r--r--data/etc/car/com.google.android.car.kitchensink.xml13
-rw-r--r--libs/androidfw/Android.bp5
-rw-r--r--libs/androidfw/ApkAssets.cpp93
-rw-r--r--libs/androidfw/Asset.cpp20
-rw-r--r--libs/androidfw/AssetManager2.cpp72
-rw-r--r--libs/androidfw/LoadedArsc.cpp29
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h38
-rw-r--r--libs/androidfw/include/androidfw/Asset.h5
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h8
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h26
-rw-r--r--libs/androidfw/tests/LoadedArsc_test.cpp34
-rw-r--r--libs/androidfw/tests/data/loader/resources.arscbin0 -> 620 bytes
-rw-r--r--libs/androidfw/tests/data/system/R.h6
-rw-r--r--location/java/android/location/Location.java8
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java6
-rw-r--r--media/java/android/media/ThumbnailUtils.java18
-rw-r--r--packages/PackageInstaller/res/values-television/themes.xml31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java8
-rw-r--r--services/core/java/com/android/server/integrity/engine/RuleEvaluator.java7
-rw-r--r--services/core/java/com/android/server/integrity/model/Rule.java4
-rw-r--r--telephony/java/android/telephony/ICellInfoCallback.aidl3
-rw-r--r--telephony/java/android/telephony/SmsManager.java30
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java27
-rw-r--r--telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java144
112 files changed, 4760 insertions, 539 deletions
diff --git a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
index 9039f921b3ba..e27670c34fb2 100644
--- a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
+++ b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
@@ -17,10 +17,13 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import java.util.List;
+
/**
* Access to the service that keeps track of device idleness and drives low power mode based on
* that.
@@ -66,4 +69,19 @@ public class DeviceIdleManager {
return new String[0];
}
}
+
+ /**
+ * Add the specified packages to the power save whitelist.
+ *
+ * @return the number of packages that were successfully added to the whitelist
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public int addPowerSaveWhitelistApps(@NonNull List<String> packageNames) {
+ try {
+ return mService.addPowerSaveWhitelistApps(packageNames);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return 0;
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 9d5becbf77cd..20fb000b36d3 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -21,6 +21,7 @@ import android.os.UserHandle;
/** @hide */
interface IDeviceIdleController {
void addPowerSaveWhitelistApp(String name);
+ int addPowerSaveWhitelistApps(in List<String> packageNames);
void removePowerSaveWhitelistApp(String name);
/* Removes an app from the system whitelist. Calling restoreSystemPowerWhitelistApp will add
the app back into the system whitelist */
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 20ee06405a86..4ee46f453bca 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -105,6 +105,8 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.stream.Collectors;
/**
@@ -1549,11 +1551,20 @@ public class DeviceIdleController extends SystemService
if (DEBUG) {
Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
}
+ addPowerSaveWhitelistApps(Collections.singletonList(name));
+ }
+
+ @Override
+ public int addPowerSaveWhitelistApps(List<String> packageNames) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "addPowerSaveWhitelistApps(name = " + packageNames + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
try {
- addPowerSaveWhitelistAppInternal(name);
+ return addPowerSaveWhitelistAppsInternal(packageNames);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2188,21 +2199,35 @@ public class DeviceIdleController extends SystemService
}
}
- public boolean addPowerSaveWhitelistAppInternal(String name) {
+ private int addPowerSaveWhitelistAppsInternal(List<String> pkgNames) {
+ int numAdded = 0;
+ int numErrors = 0;
synchronized (this) {
- try {
- ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
- PackageManager.MATCH_ANY_USER);
- if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid)) == null) {
- reportPowerSaveWhitelistChangedLocked();
- updateWhitelistAppIdsLocked();
- writeConfigFileLocked();
+ for (int i = pkgNames.size() - 1; i >= 0; --i) {
+ final String name = pkgNames.get(i);
+ if (name == null) {
+ numErrors++;
+ continue;
}
- return true;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
+ try {
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
+ PackageManager.MATCH_ANY_USER);
+ if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid))
+ == null) {
+ numAdded++;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Tried to add unknown package to power save whitelist: " + name);
+ numErrors++;
+ }
+ }
+ if (numAdded > 0) {
+ reportPowerSaveWhitelistChangedLocked();
+ updateWhitelistAppIdsLocked();
+ writeConfigFileLocked();
}
}
+ return pkgNames.size() - numErrors;
}
public boolean removePowerSaveWhitelistAppInternal(String name) {
@@ -4070,7 +4095,8 @@ public class DeviceIdleController extends SystemService
char op = arg.charAt(0);
String pkg = arg.substring(1);
if (op == '+') {
- if (addPowerSaveWhitelistAppInternal(pkg)) {
+ if (addPowerSaveWhitelistAppsInternal(Collections.singletonList(pkg))
+ == 1) {
pw.println("Added: " + pkg);
} else {
pw.println("Unknown package: " + pkg);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index b97da59f8d17..aa7696df6dbd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -17,13 +17,8 @@
package com.android.server.job.restrictions;
import android.app.job.JobParameters;
-import android.content.Context;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Temperature;
-import android.util.Slog;
+import android.os.PowerManager;
+import android.os.PowerManager.OnThermalStatusChangedListener;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.IndentingPrintWriter;
@@ -36,31 +31,29 @@ public class ThermalStatusRestriction extends JobRestriction {
private volatile boolean mIsThermalRestricted = false;
+ private PowerManager mPowerManager;
+
public ThermalStatusRestriction(JobSchedulerService service) {
super(service, JobParameters.REASON_DEVICE_THERMAL);
}
@Override
public void onSystemServicesReady() {
- final IThermalService thermalService = IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
- if (thermalService != null) {
- try {
- thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() {
- @Override
- public void onStatusChange(int status) {
- final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE;
- if (mIsThermalRestricted == shouldBeActive) {
- return;
- }
- mIsThermalRestricted = shouldBeActive;
- mService.onControllerStateChanged();
- }
- });
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register thermal callback.", e);
+ mPowerManager = mService.getContext().getSystemService(PowerManager.class);
+ // Use MainExecutor
+ mPowerManager.addThermalStatusListener(new OnThermalStatusChangedListener() {
+ @Override
+ public void onThermalStatusChanged(int status) {
+ // This is called on the main thread. Do not do any slow operations in it.
+ // mService.onControllerStateChanged() will just post a message, which is okay.
+ final boolean shouldBeActive = status >= PowerManager.THERMAL_STATUS_SEVERE;
+ if (mIsThermalRestricted == shouldBeActive) {
+ return;
+ }
+ mIsThermalRestricted = shouldBeActive;
+ mService.onControllerStateChanged();
}
- }
+ });
}
@Override
diff --git a/api/current.txt b/api/current.txt
index 2598dee83318..603dec283c66 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9805,7 +9805,7 @@ package android.content {
method public abstract java.io.File[] getExternalCacheDirs();
method @Nullable public abstract java.io.File getExternalFilesDir(@Nullable String);
method public abstract java.io.File[] getExternalFilesDirs(String);
- method public abstract java.io.File[] getExternalMediaDirs();
+ method @Deprecated public abstract java.io.File[] getExternalMediaDirs();
method public abstract java.io.File getFileStreamPath(String);
method public abstract java.io.File getFilesDir();
method public java.util.concurrent.Executor getMainExecutor();
@@ -12402,6 +12402,8 @@ package android.content.res {
public class Resources {
ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
+ method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
+ method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
method public final void finishPreloading();
method public final void flushLayoutCache();
method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimatorRes @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -12428,6 +12430,7 @@ package android.content.res {
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
+ method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12455,6 +12458,8 @@ package android.content.res {
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
+ method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
}
@@ -12522,6 +12527,33 @@ package android.content.res {
}
+package android.content.res.loader {
+
+ public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
+ ctor public DirectoryResourceLoader(@NonNull java.io.File);
+ method @Nullable public java.io.File findFile(@NonNull String);
+ method @NonNull public java.io.File getDirectory();
+ }
+
+ public interface ResourceLoader {
+ method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
+ method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
+ method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
+ method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
+ }
+
+ public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
+ method public void close();
+ method @NonNull public static android.content.res.loader.ResourcesProvider empty();
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
+ }
+
+}
+
package android.database {
public abstract class AbstractCursor implements android.database.CrossProcessCursor {
@@ -25415,6 +25447,9 @@ package android.media {
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
+ field public static final int METADATA_KEY_COLOR_RANGE = 37; // 0x25
+ field public static final int METADATA_KEY_COLOR_STANDARD = 35; // 0x23
+ field public static final int METADATA_KEY_COLOR_TRANSFER = 36; // 0x24
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
field public static final int METADATA_KEY_DATE = 5; // 0x5
@@ -38752,6 +38787,9 @@ package android.provider {
field public static final String ARTIST = "artist";
field public static final String BOOKMARK = "bookmark";
field public static final String CATEGORY = "category";
+ field public static final String COLOR_RANGE = "color_range";
+ field public static final String COLOR_STANDARD = "color_standard";
+ field public static final String COLOR_TRANSFER = "color_transfer";
field public static final String DESCRIPTION = "description";
field public static final String IS_PRIVATE = "isprivate";
field public static final String LANGUAGE = "language";
diff --git a/api/system-current.txt b/api/system-current.txt
index 447ba30382a8..92315368d182 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3420,7 +3420,9 @@ package android.location {
public class Location implements android.os.Parcelable {
method public boolean isComplete();
method public void makeComplete();
+ method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
method public void setIsFromMockProvider(boolean);
+ field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
@@ -9576,17 +9578,17 @@ package android.telephony.ims.stub {
public class ImsSmsImplBase {
ctor public ImsSmsImplBase();
- method public void acknowledgeSms(int, int, int);
- method public void acknowledgeSmsReport(int, int, int);
+ method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
+ method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
method public String getSmsFormat();
method public void onReady();
- method @Deprecated public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
- method public final void onSendSmsResultError(int, int, int, int, int) throws java.lang.RuntimeException;
- method public final void onSendSmsResultSuccess(int, int) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
+ method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
+ method public final void onSendSmsResultSuccess(int, @IntRange(from=0, to=65535) int) throws java.lang.RuntimeException;
method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSmsStatusReportReceived(int, @IntRange(from=0, to=65535) int, String, byte[]) throws java.lang.RuntimeException;
method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method public void sendSms(int, int, String, String, boolean, byte[]);
+ method public void sendSms(int, @IntRange(from=0, to=65535) int, String, String, boolean, byte[]);
field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4
diff --git a/api/test-current.txt b/api/test-current.txt
index 700be90a206e..d292e0173761 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -788,7 +788,9 @@ package android.content.res {
public final class AssetManager implements java.lang.AutoCloseable {
method @NonNull public String[] getApkPaths();
+ method @Nullable public String getLastResourceResolution();
method @Nullable public String getOverlayablesToString(String);
+ method public void setResourceResolutionLoggingEnabled(boolean);
}
public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
@@ -1089,6 +1091,8 @@ package android.location {
public class Location implements android.os.Parcelable {
method public void makeComplete();
+ method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
+ field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
@@ -1741,6 +1745,7 @@ package android.os {
}
public class DeviceIdleManager {
+ method @RequiresPermission("android.permission.DEVICE_POWER") public int addPowerSaveWhitelistApps(@NonNull java.util.List<java.lang.String>);
method @NonNull public String[] getSystemPowerWhitelist();
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 43e33f59f612..5a76d1f9c80d 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -122,9 +122,10 @@ std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
{.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
// system_elapsed_realtime
{android::util::SYSTEM_ELAPSED_REALTIME,
- {.pullTimeoutNs = NS_PER_SEC / 2,
- .coolDownNs = NS_PER_SEC,
- .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
+ {.coolDownNs = NS_PER_SEC,
+ .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
+ .pullTimeoutNs = NS_PER_SEC / 2,
+ }},
// system_uptime
{android::util::SYSTEM_UPTIME,
{.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index cb9ebac728ec..9e6054c715d6 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -32,6 +32,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.ResourcesKey;
+import android.content.res.loader.ResourceLoader;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.os.Process;
@@ -45,6 +46,7 @@ import android.util.Slog;
import android.view.Display;
import android.view.DisplayAdjustments;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -53,7 +55,11 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -92,6 +98,52 @@ public class ResourcesManager {
new ArrayMap<>();
/**
+ * A list of {@link Resources} that contain unique {@link ResourcesImpl}s.
+ *
+ * These are isolated so that {@link ResourceLoader}s can be added and removed without
+ * affecting other instances.
+ *
+ * When a reference is added here, it is guaranteed that the {@link ResourcesImpl}
+ * it contains is unique to itself and will never be set to a shared reference.
+ */
+ @GuardedBy("this")
+ private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList();
+
+ private static class ResourcesWithLoaders {
+
+ private WeakReference<Resources> mResources;
+ private ResourcesKey mResourcesKey;
+
+ @Nullable
+ private WeakReference<IBinder> mActivityToken;
+
+ ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey,
+ IBinder activityToken) {
+ this.mResources = new WeakReference<>(resources);
+ this.mResourcesKey = resourcesKey;
+ this.mActivityToken = new WeakReference<>(activityToken);
+ }
+
+ @Nullable
+ Resources resources() {
+ return mResources.get();
+ }
+
+ @Nullable
+ IBinder activityToken() {
+ return mActivityToken == null ? null : mActivityToken.get();
+ }
+
+ ResourcesKey resourcesKey() {
+ return mResourcesKey;
+ }
+
+ void updateKey(ResourcesKey newKey) {
+ mResourcesKey = newKey;
+ }
+ }
+
+ /**
* A list of Resource references that can be reused.
*/
@UnsupportedAppUsage
@@ -182,15 +234,36 @@ public class ResourcesManager {
public void invalidatePath(String path) {
synchronized (this) {
int count = 0;
- for (int i = 0; i < mResourceImpls.size();) {
+
+ for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
final ResourcesKey key = mResourceImpls.keyAt(i);
if (key.isPathReferenced(path)) {
- cleanupResourceImpl(key);
+ ResourcesImpl impl = mResourceImpls.removeAt(i).get();
+ if (impl != null) {
+ impl.flushLayoutCache();
+ }
+ count++;
+ }
+ }
+
+ for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ final ResourcesKey key = resourcesWithLoaders.resourcesKey();
+ if (key.isPathReferenced(path)) {
+ mResourcesWithLoaders.remove(i);
+ ResourcesImpl impl = resources.getImpl();
+ if (impl != null) {
+ impl.flushLayoutCache();
+ }
count++;
- } else {
- i++;
}
}
+
Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
@@ -317,15 +390,6 @@ public class ResourcesManager {
}
}
- private void cleanupResourceImpl(ResourcesKey removedKey) {
- // Remove resource key to resource impl mapping and flush cache
- final ResourcesImpl res = mResourceImpls.remove(removedKey).get();
-
- if (res != null) {
- res.flushLayoutCache();
- }
- }
-
private static String overlayPathToIdmapPath(String path) {
return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
}
@@ -499,6 +563,16 @@ public class ResourcesManager {
pw.print("resource impls: ");
pw.println(countLiveReferences(mResourceImpls.values()));
+
+ int resourcesWithLoadersCount = 0;
+ for (int index = 0; index < mResourcesWithLoaders.size(); index++) {
+ if (mResourcesWithLoaders.get(index).resources() != null) {
+ resourcesWithLoadersCount++;
+ }
+ }
+
+ pw.print("resources with loaders: ");
+ pw.println(resourcesWithLoadersCount);
}
}
@@ -579,11 +653,24 @@ public class ResourcesManager {
*/
private @Nullable ResourcesKey findKeyForResourceImplLocked(
@NonNull ResourcesImpl resourceImpl) {
- final int refCount = mResourceImpls.size();
+ int size = mResourcesWithLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ if (resourceImpl == resources.getImpl()) {
+ return resourcesWithLoaders.resourcesKey();
+ }
+ }
+
+ int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
- if (impl != null && resourceImpl == impl) {
+ if (resourceImpl == impl) {
return mResourceImpls.keyAt(i);
}
}
@@ -625,31 +712,55 @@ public class ResourcesManager {
return activityResources;
}
- /**
- * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
- * or the class loader is different.
- */
- private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
- @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
- @NonNull CompatibilityInfo compatInfo) {
- final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
- activityToken);
+ @Nullable
+ private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
+ @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
+ int size = mResourcesWithLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
- final int refCount = activityResources.activityResources.size();
- for (int i = 0; i < refCount; i++) {
- WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
- Resources resources = weakResourceRef.get();
+ IBinder activityToken = resourcesWithLoaders.activityToken();
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
- if (resources != null
- && Objects.equals(resources.getClassLoader(), classLoader)
- && resources.getImpl() == impl) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
+ ClassLoader classLoader = resources.getClassLoader();
+
+ if (Objects.equals(activityToken, targetActivityToken)
+ && Objects.equals(key, targetKey)
+ && Objects.equals(classLoader, targetClassLoader)) {
+ return resources;
+ }
+ }
+
+ ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ targetActivityToken);
+
+ size = activityResources.activityResources.size();
+ for (int index = 0; index < size; index++) {
+ WeakReference<Resources> ref = activityResources.activityResources.get(index);
+ Resources resources = ref.get();
+ ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
+ resources.getImpl());
+
+ if (key != null
+ && Objects.equals(resources.getClassLoader(), targetClassLoader)
+ && Objects.equals(key, targetKey)) {
return resources;
}
}
+ return null;
+ }
+
+ private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
+ @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
+ @NonNull CompatibilityInfo compatInfo) {
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
@@ -661,28 +772,8 @@ public class ResourcesManager {
return resources;
}
- /**
- * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
- * otherwise creates a new Resources object.
- */
- private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
+ private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
- // Find an existing Resources that has this ResourcesImpl set.
- final int refCount = mResourceReferences.size();
- for (int i = 0; i < refCount; i++) {
- WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
- Resources resources = weakResourceRef.get();
- if (resources != null &&
- Objects.equals(resources.getClassLoader(), classLoader) &&
- resources.getImpl() == impl) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
- return resources;
- }
- }
-
- // Create a new Resources reference and use the existing ResourcesImpl object.
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
@@ -750,16 +841,70 @@ public class ResourcesManager {
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
+ cleanupReferences(activityToken);
+ rebaseKeyForActivity(activityToken, key);
+
+ synchronized (this) {
+ Resources resources = findResourcesForActivityLocked(activityToken, key,
+ classLoader);
+ if (resources != null) {
+ return resources;
+ }
+ }
+
// Now request an actual Resources object.
- return getOrCreateResources(activityToken, key, classLoader);
+ return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
- * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
- * or creates one if it doesn't exist.
+ * Rebases a key's override config on top of the Activity's base override.
+ */
+ private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
+ final ActivityResources activityResources =
+ getOrCreateActivityResourcesStructLocked(activityToken);
+
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+
+ // Rebase the key's override config on top of the Activity's base override.
+ if (key.hasOverrideConfiguration()
+ && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
+ final Configuration temp = new Configuration(activityResources.overrideConfig);
+ temp.updateFrom(key.mOverrideConfiguration);
+ key.mOverrideConfiguration.setTo(temp);
+ }
+ }
+
+ /**
+ * Check WeakReferences and remove any dead references so they don't pile up.
+ * @param activityToken optional token to clean up Activity resources
+ */
+ private void cleanupReferences(IBinder activityToken) {
+ if (activityToken != null) {
+ ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
+ if (activityResources != null) {
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+ }
+ } else {
+ ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
+ }
+
+ for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ mResourcesWithLoaders.remove(index);
+ }
+ }
+ }
+
+ /**
+ * Creates a Resources object set with a ResourcesImpl object matching the given key.
*
* @param activityToken The Activity this Resources object should be associated with.
* @param key The key describing the parameters of the ResourcesImpl object.
@@ -769,7 +914,7 @@ public class ResourcesManager {
* {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
* is called.
*/
- private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
+ private @Nullable Resources createResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (DEBUG) {
@@ -778,66 +923,17 @@ public class ResourcesManager {
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
- if (activityToken != null) {
- final ActivityResources activityResources =
- getOrCreateActivityResourcesStructLocked(activityToken);
-
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(activityResources.activityResources,
- sEmptyReferencePredicate);
-
- // Rebase the key's override config on top of the Activity's base override.
- if (key.hasOverrideConfiguration()
- && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
- final Configuration temp = new Configuration(activityResources.overrideConfig);
- temp.updateFrom(key.mOverrideConfiguration);
- key.mOverrideConfiguration.setTo(temp);
- }
-
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl, key.mCompatInfo);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
-
- } else {
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
-
- // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
- }
-
- // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
- ResourcesImpl resourcesImpl = createResourcesImpl(key);
+ ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
if (resourcesImpl == null) {
return null;
}
- // Add this ResourcesImpl to the cache.
- mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
-
- final Resources resources;
if (activityToken != null) {
- resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ return createResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
- resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
+ return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
- return resources;
}
}
@@ -868,7 +964,8 @@ public class ResourcesManager {
* {@link ClassLoader#getSystemClassLoader()} is used.
* @return a Resources object from which to access resources.
*/
- public @Nullable Resources getResources(@Nullable IBinder activityToken,
+ public @Nullable Resources getResources(
+ @Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@@ -888,7 +985,14 @@ public class ResourcesManager {
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
- return getOrCreateResources(activityToken, key, classLoader);
+
+ cleanupReferences(activityToken);
+
+ if (activityToken != null) {
+ rebaseKeyForActivity(activityToken, key);
+ }
+
+ return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
@@ -944,67 +1048,40 @@ public class ResourcesManager {
here);
}
- final boolean activityHasOverrideConfig =
- !activityResources.overrideConfig.equals(Configuration.EMPTY);
// Rebase each Resources associated with this Activity.
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResRef = activityResources.activityResources.get(
i);
+
Resources resources = weakResRef.get();
if (resources == null) {
continue;
}
- // Extract the ResourcesKey that was last used to create the Resources for this
- // activity.
- final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
- if (oldKey == null) {
- Slog.e(TAG, "can't find ResourcesKey for resources impl="
- + resources.getImpl());
- continue;
- }
-
- // Build the new override configuration for this ResourcesKey.
- final Configuration rebasedOverrideConfig = new Configuration();
- if (overrideConfig != null) {
- rebasedOverrideConfig.setTo(overrideConfig);
- }
+ ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+ overrideConfig, displayId);
+ updateActivityResources(resources, newKey, false);
+ }
- if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
- // Generate a delta between the old base Activity override configuration and
- // the actual final override configuration that was used to figure out the
- // real delta this Resources object wanted.
- Configuration overrideOverrideConfig = Configuration.generateDelta(
- oldConfig, oldKey.mOverrideConfiguration);
- rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+ // Also find loaders that are associated with an Activity
+ final int loaderCount = mResourcesWithLoaders.size();
+ for (int index = loaderCount - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(
+ index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null
+ || resourcesWithLoaders.activityToken() != activityToken) {
+ continue;
}
- // Create the new ResourcesKey with the rebased override config.
- final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
- oldKey.mSplitResDirs,
- oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
- rebasedOverrideConfig, oldKey.mCompatInfo);
-
- if (DEBUG) {
- Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
- + " to newKey=" + newKey + ", displayId=" + displayId);
- }
+ ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+ overrideConfig, displayId);
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
- if (resourcesImpl == null) {
- resourcesImpl = createResourcesImpl(newKey);
- if (resourcesImpl != null) {
- mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
- }
- }
+ updateActivityResources(resources, newKey, true);
- if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
- // Set the ResourcesImpl, updating it for all users of this Resources
- // object.
- resources.setImpl(resourcesImpl);
- }
+ resourcesWithLoaders.updateKey(newKey);
}
}
} finally {
@@ -1012,6 +1089,70 @@ public class ResourcesManager {
}
}
+ /**
+ * Rebases an updated override config over any old override config and returns the new one
+ * that an Activity's Resources should be set to.
+ */
+ private ResourcesKey rebaseActivityOverrideConfig(Resources resources,
+ Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
+ int displayId) {
+ // Extract the ResourcesKey that was last used to create the Resources for this
+ // activity.
+ final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+ if (oldKey == null) {
+ Slog.e(TAG, "can't find ResourcesKey for resources impl="
+ + resources.getImpl());
+ return null;
+ }
+
+ // Build the new override configuration for this ResourcesKey.
+ final Configuration rebasedOverrideConfig = new Configuration();
+ if (newOverrideConfig != null) {
+ rebasedOverrideConfig.setTo(newOverrideConfig);
+ }
+
+ final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
+ if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
+ // Generate a delta between the old base Activity override configuration and
+ // the actual final override configuration that was used to figure out the
+ // real delta this Resources object wanted.
+ Configuration overrideOverrideConfig = Configuration.generateDelta(
+ oldOverrideConfig, oldKey.mOverrideConfiguration);
+ rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+ }
+
+ // Create the new ResourcesKey with the rebased override config.
+ final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
+ oldKey.mSplitResDirs,
+ oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
+ rebasedOverrideConfig, oldKey.mCompatInfo);
+
+ if (DEBUG) {
+ Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
+ + " to newKey=" + newKey + ", displayId=" + displayId);
+ }
+
+ return newKey;
+ }
+
+ private void updateActivityResources(Resources resources, ResourcesKey newKey,
+ boolean hasLoader) {
+ final ResourcesImpl resourcesImpl;
+
+ if (hasLoader) {
+ // Loaders always get new Impls because they cannot be shared
+ resourcesImpl = createResourcesImpl(newKey);
+ } else {
+ resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
+ }
+
+ if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+ // Set the ResourcesImpl, updating it for all users of this Resources
+ // object.
+ resources.setImpl(resourcesImpl);
+ }
+ }
+
@TestApi
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
@@ -1050,61 +1191,77 @@ public class ResourcesManager {
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
- Configuration tmpConfig = null;
+ Configuration tmpConfig = new Configuration();
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
ResourcesKey key = mResourceImpls.keyAt(i);
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
if (r != null) {
- if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
- + r + " config to: " + config);
- int displayId = key.mDisplayId;
- boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- DisplayMetrics dm = defaultDisplayMetrics;
- final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
- if (!isDefaultDisplay || hasOverrideConfiguration) {
- if (tmpConfig == null) {
- tmpConfig = new Configuration();
- }
- tmpConfig.setTo(config);
-
- // Get new DisplayMetrics based on the DisplayAdjustments given
- // to the ResourcesImpl. Update a copy if the CompatibilityInfo
- // changed, because the ResourcesImpl object will handle the
- // update internally.
- DisplayAdjustments daj = r.getDisplayAdjustments();
- if (compat != null) {
- daj = new DisplayAdjustments(daj);
- daj.setCompatibilityInfo(compat);
- }
- dm = getDisplayMetrics(displayId, daj);
-
- if (!isDefaultDisplay) {
- applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
- }
-
- if (hasOverrideConfiguration) {
- tmpConfig.updateFrom(key.mOverrideConfiguration);
- }
- r.updateConfiguration(tmpConfig, dm, compat);
- } else {
- r.updateConfiguration(config, dm, compat);
- }
- //Slog.i(TAG, "Updated app resources " + v.getKey()
- // + " " + r + ": " + r.getConfiguration());
+ applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+ defaultDisplayMetrics, key, r);
} else {
- //Slog.i(TAG, "Removing old resources " + v.getKey());
mResourceImpls.removeAt(i);
}
}
+ for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ mResourcesWithLoaders.remove(index);
+ continue;
+ }
+
+ applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+ defaultDisplayMetrics, resourcesWithLoaders.resourcesKey(),
+ resources.getImpl());
+ }
+
return changes != 0;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
+ private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
+ @Nullable CompatibilityInfo compat, Configuration tmpConfig,
+ DisplayMetrics defaultDisplayMetrics, ResourcesKey key, ResourcesImpl resourcesImpl) {
+ if (DEBUG || DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Changing resources "
+ + resourcesImpl + " config to: " + config);
+ }
+ int displayId = key.mDisplayId;
+ boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ DisplayMetrics dm = defaultDisplayMetrics;
+ final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
+ if (!isDefaultDisplay || hasOverrideConfiguration) {
+ tmpConfig.setTo(config);
+
+ // Get new DisplayMetrics based on the DisplayAdjustments given
+ // to the ResourcesImpl. Update a copy if the CompatibilityInfo
+ // changed, because the ResourcesImpl object will handle the
+ // update internally.
+ DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
+ if (compat != null) {
+ daj = new DisplayAdjustments(daj);
+ daj.setCompatibilityInfo(compat);
+ }
+ dm = getDisplayMetrics(displayId, daj);
+
+ if (!isDefaultDisplay) {
+ applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
+ }
+
+ if (hasOverrideConfiguration) {
+ tmpConfig.updateFrom(key.mOverrideConfiguration);
+ }
+ resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
+ } else {
+ resourcesImpl.updateConfiguration(config, dm, compat);
+ }
+ }
+
/**
* Appends the library asset path to any ResourcesImpl object that contains the main
* assetPath.
@@ -1140,7 +1297,7 @@ public class ResourcesManager {
ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
}
- if (newLibAssets != key.mLibDirs) {
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
updatedResourceKeys.put(impl, new ResourcesKey(
key.mResDir,
key.mSplitResDirs,
@@ -1153,10 +1310,106 @@ public class ResourcesManager {
}
}
+ final int count = mResourcesWithLoaders.size();
+ for (int index = 0; index < count; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+ if (Objects.equals(key.mResDir, assetPath)) {
+ String[] newLibAssets = key.mLibDirs;
+ for (String libAsset : libAssets) {
+ newLibAssets =
+ ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
+ }
+
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+ updatedResourceKeys.put(resources.getImpl(),
+ new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ key.mOverlayDirs,
+ newLibAssets,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo));
+ }
+ }
+ }
+
redirectResourcesToNewImplLocked(updatedResourceKeys);
}
}
+ /**
+ * Mark a {@link Resources} as containing a {@link ResourceLoader}.
+ *
+ * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is
+ * then tracked separately, kept unique, and restored properly across {@link Activity}
+ * recreations.
+ */
+ public void registerForLoaders(Resources resources) {
+ synchronized (this) {
+ boolean found = false;
+ IBinder activityToken = null;
+
+ // Remove the Resources from the reference list as it's now tracked separately
+ int size = mResourceReferences.size();
+ for (int index = 0; index < size; index++) {
+ WeakReference<Resources> reference = mResourceReferences.get(index);
+ if (reference.get() == resources) {
+ mResourceReferences.remove(index);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Do the same removal for any Activity Resources
+ for (Map.Entry<IBinder, ActivityResources> entry :
+ mActivityResourceReferences.entrySet()) {
+ ArrayList<WeakReference<Resources>> activityResourcesList =
+ entry.getValue().activityResources;
+ final int resCount = activityResourcesList.size();
+ for (int index = 0; index < resCount; index++) {
+ WeakReference<Resources> reference = activityResourcesList.get(index);
+ if (reference.get() == resources) {
+ activityToken = entry.getKey();
+ activityResourcesList.remove(index);
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ throw new IllegalArgumentException("Resources " + resources
+ + " registered for loaders but was not previously tracked by"
+ + " ResourcesManager");
+ }
+
+ ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl());
+ ResourcesImpl impl = createResourcesImpl(key);
+
+ if (mResourcesWithLoaders == Collections.EMPTY_LIST) {
+ mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>());
+ }
+
+ mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken));
+
+ // Set the new Impl, which is now guaranteed to be unique per Resources object
+ resources.setImpl(impl);
+ }
+ }
+
// TODO(adamlesinski): Make this accept more than just overlay directories.
final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
@Nullable final String[] oldPaths) {
@@ -1201,6 +1454,32 @@ public class ResourcesManager {
}
}
+ final int count = mResourcesWithLoaders.size();
+ for (int index = 0; index < count; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+ if (key.mResDir == null
+ || key.mResDir.equals(baseCodePath)
+ || ArrayUtils.contains(oldPaths, key.mResDir)) {
+ updatedResourceKeys.put(resources.getImpl(),
+ new ResourcesKey(
+ baseCodePath,
+ copiedSplitDirs,
+ copiedResourceDirs,
+ key.mLibDirs,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo
+ ));
+ }
+ }
+
redirectResourcesToNewImplLocked(updatedResourceKeys);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1250,5 +1529,25 @@ public class ResourcesManager {
}
}
}
+
+ // Update any references that need to be re-built with loaders. These are intentionally not
+ // part of either of the above lists.
+ final int loaderCount = mResourcesWithLoaders.size();
+ for (int index = loaderCount - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl());
+ if (newKey == null) {
+ continue;
+ }
+
+ resourcesWithLoaders.updateKey(newKey);
+ resourcesWithLoaders.resources()
+ .setImpl(createResourcesImpl(newKey));
+ }
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2dde3ae5909c..227684b1bd14 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1559,7 +1559,14 @@ public abstract class Context {
* @see Environment#getExternalStorageState(File)
* @see Environment#isExternalStorageEmulated(File)
* @see Environment#isExternalStorageRemovable(File)
+ * @deprecated These directories still exist and are scanned, but developers
+ * are encouraged to migrate to inserting content into a
+ * {@link MediaStore} collection directly, as any app can
+ * contribute new media to {@link MediaStore} with no
+ * permissions required, starting in
+ * {@link android.os.Build.VERSION_CODES#Q}.
*/
+ @Deprecated
public abstract File[] getExternalMediaDirs();
/**
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index a35ad567ed81..de1d514d0a5b 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -16,7 +16,10 @@
package android.content.res;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesProvider;
+import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -36,10 +39,14 @@ import java.io.IOException;
*/
public final class ApkAssets {
@GuardedBy("this") private final long mNativePtr;
+
+ @Nullable
@GuardedBy("this") private final StringBlock mStringBlock;
@GuardedBy("this") private boolean mOpen = true;
+ private final boolean mForLoader;
+
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -48,7 +55,8 @@ public final class ApkAssets {
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
- return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -61,7 +69,8 @@ public final class ApkAssets {
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
throws IOException {
- return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+ return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -76,7 +85,8 @@ public final class ApkAssets {
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
boolean forceSharedLibrary) throws IOException {
- return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+ return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -96,7 +106,8 @@ public final class ApkAssets {
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
@NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
throws IOException {
- return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+ return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/,
+ false /*forLoader*/);
}
/**
@@ -110,21 +121,90 @@ public final class ApkAssets {
*/
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
throws IOException {
- return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+ return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk for use with a
+ * {@link ResourcesProvider}.
+ *
+ * @param path The path to an APK on disk.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadApkForLoader(@NonNull String path)
+ throws IOException {
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/,
+ false /*overlay*/, false /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor for use with a
+ * {@link ResourcesProvider}.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable APK.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ @NonNull
+ public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException {
+ return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+ false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/);
}
- private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor representing an ARSC
+ * for use with a {@link ResourcesProvider}.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable .arsc.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd)
throws IOException {
+ return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+ false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
+ * is required for a lot of APIs, and it's easier to have a non-null reference rather than
+ * tracking a separate identifier.
+ */
+ @NonNull
+ public static ApkAssets loadEmptyForLoader() {
+ return new ApkAssets(true);
+ }
+
+ private ApkAssets(boolean forLoader) {
+ mForLoader = forLoader;
+ mNativePtr = nativeLoadEmpty(forLoader);
+ mStringBlock = null;
+ }
+
+ private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay,
+ boolean arscOnly, boolean forLoader) throws IOException {
+ mForLoader = forLoader;
Preconditions.checkNotNull(path, "path");
- mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+ mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader)
+ : nativeLoad(path, system, forceSharedLib, overlay, forLoader);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
- boolean forceSharedLib) throws IOException {
+ boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException {
+ mForLoader = forLoader;
Preconditions.checkNotNull(fd, "fd");
Preconditions.checkNotNull(friendlyName, "friendlyName");
- mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+ mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader)
+ : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
@@ -136,11 +216,19 @@ public final class ApkAssets {
}
CharSequence getStringFromPool(int idx) {
+ if (mStringBlock == null) {
+ return null;
+ }
+
synchronized (this) {
return mStringBlock.get(idx);
}
}
+ public boolean isForLoader() {
+ return mForLoader;
+ }
+
/**
* Retrieve a parser for a compiled XML file. This is associated with a single APK and
* <em>NOT</em> a full AssetManager. This means that shared-library references will not be
@@ -192,18 +280,26 @@ public final class ApkAssets {
synchronized (this) {
if (mOpen) {
mOpen = false;
- mStringBlock.close();
+ if (mStringBlock != null) {
+ mStringBlock.close();
+ }
nativeDestroy(mNativePtr);
}
}
}
- private static native long nativeLoad(
- @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ private static native long nativeLoad(@NonNull String path, boolean system,
+ boolean forceSharedLib, boolean overlay, boolean forLoader)
throws IOException;
private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+ @NonNull String friendlyName, boolean system, boolean forceSharedLib,
+ boolean forLoader)
+ throws IOException;
+ private static native long nativeLoadArsc(@NonNull String path, boolean forLoader)
throws IOException;
+ private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean forLoader) throws IOException;
+ private static native long nativeLoadEmpty(boolean forLoader);
private static native void nativeDestroy(long ptr);
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native long nativeGetStringBlock(long ptr);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7d6dc97e82cb..23e772075ad6 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,9 +27,13 @@ import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -39,15 +43,19 @@ import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
import java.io.BufferedReader;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.FileLock;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -110,6 +118,13 @@ public final class AssetManager implements AutoCloseable {
@GuardedBy("this") private int mNumRefs = 1;
@GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
+ private ResourceLoaderManager mResourceLoaderManager;
+
+ /** @hide */
+ public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
+ mResourceLoaderManager = resourceLoaderManager;
+ }
+
/**
* A Builder class that helps create an AssetManager with only a single invocation of
* {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder,
@@ -507,7 +522,7 @@ public final class AssetManager implements AutoCloseable {
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
@@ -554,7 +569,7 @@ public final class AssetManager implements AutoCloseable {
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ return getPooledStringForCookie(cookie, outValue.data);
}
return outValue.coerceToString();
}
@@ -632,7 +647,7 @@ public final class AssetManager implements AutoCloseable {
int cookie = rawInfoArray[i];
int index = rawInfoArray[i + 1];
retArray[j] = (index >= 0 && cookie > 0)
- ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
+ ? getPooledStringForCookie(cookie, index) : null;
}
return retArray;
}
@@ -688,7 +703,7 @@ public final class AssetManager implements AutoCloseable {
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
@@ -753,6 +768,7 @@ public final class AssetManager implements AutoCloseable {
*
* @hide
*/
+ @TestApi
public void setResourceResolutionLoggingEnabled(boolean enabled) {
synchronized (this) {
ensureValidLocked();
@@ -768,6 +784,7 @@ public final class AssetManager implements AutoCloseable {
*
* @hide
*/
+ @TestApi
public @Nullable String getLastResourceResolution() {
synchronized (this) {
ensureValidLocked();
@@ -814,6 +831,13 @@ public final class AssetManager implements AutoCloseable {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ String path = Paths.get("assets", fileName).toString();
+ InputStream inputStream = searchLoaders(0, path, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -838,6 +862,13 @@ public final class AssetManager implements AutoCloseable {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ String path = Paths.get("assets", fileName).toString();
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path);
+ if (fileDescriptor != null) {
+ return fileDescriptor;
+ }
+
final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
if (pfd == null) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -931,6 +962,12 @@ public final class AssetManager implements AutoCloseable {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ InputStream inputStream = searchLoaders(cookie, fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+
final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -970,6 +1007,12 @@ public final class AssetManager implements AutoCloseable {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+ if (fileDescriptor != null) {
+ return fileDescriptor;
+ }
+
final ParcelFileDescriptor pfd =
nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
if (pfd == null) {
@@ -1031,7 +1074,16 @@ public final class AssetManager implements AutoCloseable {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
- final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+
+ final long xmlBlock;
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+ if (fileDescriptor != null) {
+ xmlBlock = nativeOpenXmlAssetFd(mObject, cookie,
+ fileDescriptor.getFileDescriptor());
+ } else {
+ xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+ }
+
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
@@ -1041,6 +1093,85 @@ public final class AssetManager implements AutoCloseable {
}
}
+ private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ if (mResourceLoaderManager == null) {
+ return null;
+ }
+
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+ mResourceLoaderManager.getInternalList();
+
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ if (cookie == 0) {
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ try {
+ InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
+ }
+
+ return null;
+ }
+
+ ApkAssets apkAssets = mApkAssets[cookie - 1];
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ return pair.first.loadAsset(fileName, accessMode);
+ }
+ }
+
+ return null;
+ }
+
+ private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
+ throws IOException {
+ if (mResourceLoaderManager == null) {
+ return null;
+ }
+
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+ mResourceLoaderManager.getInternalList();
+
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ if (cookie == 0) {
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ try {
+ ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
+ }
+
+ return null;
+ }
+
+ ApkAssets apkAssets = mApkAssets[cookie - 1];
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ return null;
+ }
+ }
+ return null;
+ }
+
void xmlBlockGone(int id) {
synchronized (this) {
decRefsLocked(id);
@@ -1296,7 +1427,7 @@ public final class AssetManager implements AutoCloseable {
*
* <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
- * parsed using {@link java.util.Locale#forLanguageTag(String)}.
+ * parsed using {@link Locale#forLanguageTag(String)}.
*
* <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
@@ -1439,6 +1570,8 @@ public final class AssetManager implements AutoCloseable {
private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
@NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+ private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
+ @NonNull FileDescriptor fileDescriptor);
// Primitive resource native methods.
private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index d7e4e1452cfe..2698c2de4c61 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -30,6 +30,7 @@ import android.annotation.DimenRes;
import android.annotation.DrawableRes;
import android.annotation.FontRes;
import android.annotation.FractionRes;
+import android.annotation.IntRange;
import android.annotation.IntegerRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
@@ -41,8 +42,12 @@ import android.annotation.StyleRes;
import android.annotation.StyleableRes;
import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
+import android.app.ResourcesManager;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
import android.graphics.Movie;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -54,13 +59,16 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import android.util.TypedValue;
import android.view.DisplayAdjustments;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils;
@@ -71,6 +79,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -133,6 +143,11 @@ public class Resources {
@UnsupportedAppUsage
final ClassLoader mClassLoader;
+ private final Object mResourceLoaderLock = new Object();
+
+ @GuardedBy("mResourceLoaderLock")
+ private ResourceLoaderManager mResourceLoaderManager;
+
/**
* WeakReferences to Themes that were constructed from this Resources object.
* We keep track of these in case our underlying implementation is changed, in which case
@@ -148,6 +163,8 @@ public class Resources {
private static final int MIN_THEME_REFS_FLUSH_SIZE = 32;
private int mThemeRefsNextFlushSize = MIN_THEME_REFS_FLUSH_SIZE;
+ private int mBaseApkAssetsSize;
+
/**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
@@ -283,8 +300,15 @@ public class Resources {
return;
}
+ mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
mResourcesImpl = impl;
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager != null) {
+ mResourceLoaderManager.onImplUpdate(mResourcesImpl);
+ }
+ }
+
// Create new ThemeImpls that are identical to the ones we have.
synchronized (mThemeRefs) {
final int count = mThemeRefs.size();
@@ -903,7 +927,7 @@ public class Resources {
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValueForDensity(id, density, value, true);
- return impl.loadDrawable(this, value, id, density, theme);
+ return loadDrawable(value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
@@ -913,6 +937,14 @@ public class Resources {
@UnsupportedAppUsage
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
+ ResourceLoader loader = findLoader(value.assetCookie);
+ if (loader != null) {
+ Drawable drawable = loader.loadDrawable(value, id, density, theme);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
@@ -2280,7 +2312,7 @@ public class Resources {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
- return impl.loadXmlResourceParser(value.string.toString(), id,
+ return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
@@ -2304,6 +2336,14 @@ public class Resources {
@UnsupportedAppUsage
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
+ ResourceLoader loader = findLoader(assetCookie);
+ if (loader != null) {
+ XmlResourceParser xml = loader.loadXmlResourceParser(file, id);
+ if (xml != null) {
+ return xml;
+ }
+ }
+
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
@@ -2329,4 +2369,137 @@ public class Resources {
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
+
+ private ResourceLoader findLoader(int assetCookie) {
+ ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets();
+ int apkAssetsIndex = assetCookie - 1;
+ if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) {
+ ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex];
+ if (apkAssets.isForLoader()) {
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders;
+ // Since we don't lock the entire resolution path anyways,
+ // only lock here instead of entire method. The list is copied
+ // and effectively a snapshot is used.
+ synchronized (mResourceLoaderLock) {
+ loaders = mResourceLoaderManager.getInternalList();
+ }
+
+ if (!ArrayUtils.isEmpty(loaders)) {
+ int size = loaders.size();
+ for (int index = 0; index < size; index++) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ return pair.first;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return copied list of loaders and providers previously added
+ */
+ @NonNull
+ public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+ synchronized (mResourceLoaderLock) {
+ return mResourceLoaderManager == null
+ ? Collections.emptyList()
+ : mResourceLoaderManager.getLoaders();
+ }
+ }
+
+ /**
+ * Add a custom {@link ResourceLoader} which is added to the paths searched by
+ * {@link AssetManager} when resolving a resource.
+ *
+ * Resources are resolved as if the loader was a resource overlay, meaning the latest
+ * in the list, of equal or better config, is returned.
+ *
+ * {@link ResourcesProvider}s passed in here are not managed and a reference should be held
+ * to remove, re-use, or close them when necessary.
+ *
+ * @param resourceLoader an interface used to resolve file paths for drawables/XML files;
+ * a reference should be kept to remove the loader if necessary
+ * @param resourcesProvider an .apk or .arsc file representation
+ * @param index where to add the loader in the list
+ * @throws IllegalArgumentException if the resourceLoader is already added
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
+ public void addLoader(@NonNull ResourceLoader resourceLoader,
+ @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ ResourcesManager.getInstance().registerForLoaders(this);
+ mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ }
+
+ mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index);
+ }
+ }
+
+ /**
+ * @see #addLoader(ResourceLoader, ResourcesProvider, int).
+ *
+ * Adds to the end of the list.
+ *
+ * @return index the loader was added at
+ */
+ public int addLoader(@NonNull ResourceLoader resourceLoader,
+ @NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mResourceLoaderLock) {
+ int index = getLoaders().size();
+ addLoader(resourceLoader, resourcesProvider, index);
+ return index;
+ }
+ }
+
+ /**
+ * Remove a loader previously added by
+ * {@link #addLoader(ResourceLoader, ResourcesProvider, int)}
+ *
+ * The caller maintains responsibility for holding a reference to the matching
+ * {@link ResourcesProvider} and closing it after this method has been called.
+ *
+ * @param resourceLoader the same reference passed into [addLoader
+ * @return the index the loader was at in the list, or -1 if the loader was not found
+ */
+ public int removeLoader(@NonNull ResourceLoader resourceLoader) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ return -1;
+ }
+
+ return mResourceLoaderManager.removeLoader(resourceLoader);
+ }
+ }
+
+ /**
+ * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't
+ * update the resource data structures after each modification.
+ *
+ * Set to null or an empty list to clear the set of loaders.
+ *
+ * The caller maintains responsibility for holding references to the added
+ * {@link ResourcesProvider}s and closing them after this method has been called.
+ *
+ * @param resourceLoadersAndProviders a list of pairs to add
+ */
+ public void setLoaders(
+ @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) {
+ return;
+ }
+
+ ResourcesManager.getInstance().registerForLoaders(this);
+ mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ }
+
+ mResourceLoaderManager.setLoaders(resourceLoadersAndProviders);
+ }
+ }
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index b72544c02d6a..84489cfb768c 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -57,6 +57,8 @@ import android.view.DisplayAdjustments;
import com.android.internal.util.GrowingArrayUtils;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -376,7 +378,7 @@ public class ResourcesImpl {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
try {
synchronized (mAccessLock) {
- if (false) {
+ if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
@@ -572,6 +574,20 @@ public class ResourcesImpl {
}
}
+ /**
+ * Wipe all caches that might be read and return an outdated object when resolving a resource.
+ */
+ public void clearAllCaches() {
+ synchronized (mAccessLock) {
+ mDrawableCache.clear();
+ mColorDrawableCache.clear();
+ mComplexColorCache.clear();
+ mAnimatorCache.clear();
+ mStateListAnimatorCache.clear();
+ flushLayoutCache();
+ }
+ }
+
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
@@ -802,6 +818,27 @@ public class ResourcesImpl {
}
/**
+ * Loads a Drawable from an encoded image stream, or null.
+ *
+ * This call will handle closing the {@link InputStream}.
+ */
+ @Nullable
+ private Drawable decodeImageDrawable(@NonNull InputStream inputStream,
+ @NonNull Resources wrapper, @NonNull TypedValue value) {
+ ImageDecoder.Source src = ImageDecoder.createSource(wrapper, inputStream, value.density);
+ try {
+ return ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
+ } catch (IOException ignored) {
+ // This is okay. This may be something that ImageDecoder does not
+ // support, like SVG.
+ return null;
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+ }
+
+ /**
* Loads a drawable from XML or resources stream.
*
* @return Drawable, or null if Drawable cannot be decoded.
@@ -865,8 +902,12 @@ public class ResourcesImpl {
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- AssetInputStream ais = (AssetInputStream) is;
- dr = decodeImageDrawable(ais, wrapper, value);
+ if (is instanceof AssetInputStream) {
+ AssetInputStream ais = (AssetInputStream) is;
+ dr = decodeImageDrawable(ais, wrapper, value);
+ } else {
+ dr = decodeImageDrawable(is, wrapper, value);
+ }
}
} finally {
stack.pop();
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 2ae1932c3437..d43bd36b4c74 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -47,6 +47,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import java.io.Closeable;
import java.util.Arrays;
/**
@@ -54,7 +55,7 @@ import java.util.Arrays;
*
* {@hide}
*/
-final class StringBlock {
+public final class StringBlock implements Closeable {
private static final String TAG = "AssetManager";
private static final boolean localLOGV = false;
@@ -175,6 +176,7 @@ final class StringBlock {
}
}
+ @Override
public void close() {
synchronized (this) {
if (mOpen) {
@@ -517,7 +519,7 @@ final class StringBlock {
* of this newly creating StringBlock.
*/
@UnsupportedAppUsage
- StringBlock(long obj, boolean useSparse) {
+ public StringBlock(long obj, boolean useSparse) {
mNative = obj;
mUseSparse = useSparse;
mOwnsNative = false;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index 06cafdb2bb91..968ab401ccba 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -22,8 +22,8 @@ import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources.Theme;
import android.content.res.Resources.ThemeKey;
-import android.util.LongSparseArray;
import android.util.ArrayMap;
+import android.util.LongSparseArray;
import java.lang.ref.WeakReference;
@@ -234,4 +234,18 @@ abstract class ThemedResourceCache<T> {
return entry == null || (configChanges != 0
&& shouldInvalidateEntry(entry, configChanges));
}
+
+ public synchronized void clear() {
+ if (mThemedEntries != null) {
+ mThemedEntries.clear();
+ }
+
+ if (mUnthemedEntries != null) {
+ mUnthemedEntries.clear();
+ }
+
+ if (mNullThemedEntries != null) {
+ mNullThemedEntries.clear();
+ }
+ }
}
diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryResourceLoader.java
new file mode 100644
index 000000000000..7d90e72ab07e
--- /dev/null
+++ b/core/java/android/content/res/loader/DirectoryResourceLoader.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link ResourceLoader} that searches a directory for assets.
+ *
+ * Assumes that resource paths are resolvable child paths of the directory passed in.
+ */
+public class DirectoryResourceLoader implements ResourceLoader {
+
+ @NonNull
+ private final File mDirectory;
+
+ public DirectoryResourceLoader(@NonNull File directory) {
+ this.mDirectory = directory;
+ }
+
+ @Nullable
+ @Override
+ public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ File file = findFile(path);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ return new FileInputStream(file);
+ }
+
+ @Nullable
+ @Override
+ public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+ File file = findFile(path);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ /**
+ * Find the file for the given path encoded into the resource table.
+ */
+ @Nullable
+ public File findFile(@NonNull String path) {
+ return mDirectory.toPath().resolve(path).toFile();
+ }
+
+ @NonNull
+ public File getDirectory() {
+ return mDirectory;
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java
new file mode 100644
index 000000000000..af32aa2c6875
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoader.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Exposes methods for overriding file-based resource loading from a {@link Resources}.
+ *
+ * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related
+ * methods to override resource loading.
+ *
+ * Note that this class doesn't actually contain any resource data. Non-file-based resources are
+ * loaded directly from the {@link ResourcesProvider}'s .arsc representation.
+ *
+ * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s
+ * resources table contains an entry for the resource ID being resolved,
+ * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and
+ * openNonAsset.
+ *
+ * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided
+ * by the application or system.
+ *
+ * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader}
+ * wants to point R.drawable.some_id to a different file on disk.
+ */
+public interface ResourceLoader {
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a
+ * {@link Drawable} which should be returned by the parent
+ * {@link Resources#getDrawable(int, Resources.Theme)}.
+ *
+ * @param value the resolved {@link TypedValue} before it has been converted to a Drawable
+ * object
+ * @param id the R.drawable ID this resolution is for
+ * @param density the requested density
+ * @param theme the {@link Resources.Theme} resolved under
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+ * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)}
+ */
+ @Nullable
+ default Drawable loadDrawable(@NonNull TypedValue value, int id, int density,
+ @Nullable Resources.Theme theme) {
+ return null;
+ }
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+ * {@link XmlResourceParser} which should be returned by the parent
+ * {@link Resources#getDrawable(int, Resources.Theme)}.
+ *
+ * @param path the string that was found in the string pool
+ * @param id the XML ID this resolution is for, can be R.anim, R.layout, or R.xml
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+ * including calling through to {@link #loadAssetFd(String)} (String, int)}
+ */
+ @Nullable
+ default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) {
+ return null;
+ }
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+ * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}.
+ * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed
+ * with "assets/".
+ *
+ * @param path the asset path to load
+ * @param accessMode {@link AssetManager} access mode; does not have to be respected
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+ */
+ @Nullable
+ default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ return null;
+ }
+
+ /**
+ * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
+ *
+ * @param path the asset path to load
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+ */
+ @Nullable
+ default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+ return null;
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java
new file mode 100644
index 000000000000..ddbfa81390e4
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoaderManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.Nullable;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.ResourcesImpl;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ResourceLoaderManager {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders =
+ new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private ResourcesImpl mResourcesImpl;
+
+ public ResourceLoaderManager(ResourcesImpl resourcesImpl) {
+ this.mResourcesImpl = resourcesImpl;
+ this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
+ }
+
+ /**
+ * Copies the list to ensure that ongoing mutations don't affect the list if it's being used
+ * as a search set.
+ *
+ * @see Resources#getLoaders()
+ */
+ public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+ synchronized (mLock) {
+ return new ArrayList<>(mResourceLoaders);
+ }
+ }
+
+ /**
+ * Returns a list for searching for a loader. Locks and copies the list to ensure that
+ * ongoing mutations don't affect the search set.
+ */
+ public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() {
+ synchronized (mLock) {
+ return new ArrayList<>(mResourceLoaders);
+ }
+ }
+
+ /**
+ * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader
+ * to override every configuration in the target package
+ *
+ * @see Resources#addLoader(ResourceLoader, ResourcesProvider)
+ */
+ public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider,
+ int index) {
+ synchronized (mLock) {
+ for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) {
+ if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) {
+ throw new IllegalArgumentException("Cannot add the same ResourceLoader twice");
+ }
+ }
+
+ mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider));
+ updateLoaders();
+ }
+ }
+
+ /**
+ * @see Resources#removeLoader(ResourceLoader)
+ */
+ public int removeLoader(ResourceLoader resourceLoader) {
+ synchronized (mLock) {
+ int indexOfLoader = -1;
+
+ for (int index = 0; index < mResourceLoaders.size(); index++) {
+ if (mResourceLoaders.get(index).first == resourceLoader) {
+ indexOfLoader = index;
+ break;
+ }
+ }
+
+ if (indexOfLoader < 0) {
+ return indexOfLoader;
+ }
+
+ mResourceLoaders.remove(indexOfLoader);
+ updateLoaders();
+ return indexOfLoader;
+ }
+ }
+
+ /**
+ * @see Resources#setLoaders(List)
+ */
+ public void setLoaders(
+ @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) {
+ synchronized (mLock) {
+ if (ArrayUtils.isEmpty(newLoadersAndProviders)) {
+ mResourceLoaders.clear();
+ updateLoaders();
+ return;
+ }
+
+ int size = newLoadersAndProviders.size();
+ for (int newIndex = 0; newIndex < size; newIndex++) {
+ ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first;
+ for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) {
+ if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) {
+ throw new IllegalArgumentException(
+ "Cannot add the same ResourceLoader twice");
+ }
+ }
+ }
+
+ mResourceLoaders.clear();
+ mResourceLoaders.addAll(newLoadersAndProviders);
+
+ updateLoaders();
+ }
+ }
+
+ /**
+ * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it.
+ */
+ public void onImplUpdate(ResourcesImpl resourcesImpl) {
+ synchronized (mLock) {
+ this.mResourcesImpl = resourcesImpl;
+ updateLoaders();
+ }
+ }
+
+ private void updateLoaders() {
+ synchronized (mLock) {
+ AssetManager assetManager = mResourcesImpl.getAssets();
+ ApkAssets[] existingApkAssets = assetManager.getApkAssets();
+ int baseApkAssetsSize = 0;
+ for (int index = existingApkAssets.length - 1; index >= 0; index--) {
+ // Loaders are always last, so the first non-loader is the end of the base assets
+ if (!existingApkAssets[index].isForLoader()) {
+ baseApkAssetsSize = index + 1;
+ break;
+ }
+ }
+
+ List<ApkAssets> newAssets = new ArrayList<>();
+ for (int index = 0; index < baseApkAssetsSize; index++) {
+ newAssets.add(existingApkAssets[index]);
+ }
+
+ int size = mResourceLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets();
+ newAssets.add(apkAssets);
+ }
+
+ assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true);
+
+ // Short of resolving every resource, it's too difficult to determine what has changed
+ // when a resource loader is changed, so just clear everything.
+ mResourcesImpl.clearAllCaches();
+ }
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
new file mode 100644
index 000000000000..050aeb7c5fda
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Provides methods to load resources from an .apk or .arsc file to pass to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}.
+ *
+ * It is the responsibility of the app to close any instances.
+ */
+public final class ResourcesProvider implements AutoCloseable, Closeable {
+
+ /**
+ * Contains no data, assuming that any resource loading behavior will be handled in the
+ * corresponding {@link ResourceLoader}.
+ */
+ @NonNull
+ public static ResourcesProvider empty() {
+ return new ResourcesProvider(ApkAssets.loadEmptyForLoader());
+ }
+
+ /**
+ * Read from an .apk file descriptor.
+ *
+ * The file descriptor is duplicated and the one passed in may be closed by the application
+ * at any time.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .apk file representation in memory.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .arsc file descriptor.
+ *
+ * The file descriptor is duplicated and the one passed in may be closed by the application
+ * at any time.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .arsc file representation in memory.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()));
+ }
+
+ /**
+ * Read from a split installed alongside the application, which may not have been
+ * loaded initially because the application requested isolated split loading.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromSplit(@NonNull Context context,
+ @NonNull String splitName) throws IOException {
+ ApplicationInfo appInfo = context.getApplicationInfo();
+ int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
+ if (splitIndex < 0) {
+ throw new IllegalArgumentException("Split " + splitName + " not found");
+ }
+
+ String splitPath = appInfo.getSplitCodePaths()[splitIndex];
+ return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath));
+ }
+
+
+ @NonNull
+ private final ApkAssets mApkAssets;
+
+ private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+ this.mApkAssets = apkAssets;
+ }
+
+ /** @hide */
+ @NonNull
+ public ApkAssets getApkAssets() {
+ return mApkAssets;
+ }
+
+ @Override
+ public void close() {
+ try {
+ mApkAssets.close();
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index bcb94ce2d2d5..fdb44e7050e1 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -35,12 +35,15 @@ import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.util.Log;
+import android.util.Size;
import dalvik.system.CloseGuard;
import dalvik.system.VMRuntime;
@@ -204,6 +207,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
/**
* Create a new ParcelFileDescriptor accessing a given file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
*
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
@@ -226,6 +233,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
/**
* Create a new ParcelFileDescriptor accessing a given file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
*
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 079a42ddaa6c..493f9a2ce123 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -47,6 +47,7 @@ import android.graphics.Point;
import android.graphics.PostProcessor;
import android.media.ExifInterface;
import android.media.MediaFile;
+import android.media.MediaFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -3007,22 +3008,32 @@ public final class MediaStore {
public static final String BOOKMARK = "bookmark";
/**
- * The standard of color aspects
- * @hide
+ * The color standard of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_STANDARD_BT709
+ * @see MediaFormat#COLOR_STANDARD_BT601_PAL
+ * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
+ * @see MediaFormat#COLOR_STANDARD_BT2020
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_STANDARD = "color_standard";
/**
- * The transfer of color aspects
- * @hide
+ * The color transfer of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_TRANSFER_LINEAR
+ * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
+ * @see MediaFormat#COLOR_TRANSFER_ST2084
+ * @see MediaFormat#COLOR_TRANSFER_HLG
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_TRANSFER = "color_transfer";
/**
- * The range of color aspects
- * @hide
+ * The color range of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_RANGE_LIMITED
+ * @see MediaFormat#COLOR_RANGE_FULL
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_RANGE = "color_range";
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 407a85f1bb05..068056f091d7 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -388,21 +388,24 @@ public class ResolverActivity extends Activity {
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
mSystemWindowInsets.right, 0);
- View emptyView = findViewById(R.id.empty);
- if (emptyView != null) {
- emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
- + getResources().getDimensionPixelSize(
- R.dimen.chooser_edge_margin_normal) * 2);
- }
-
- if (mFooterSpacer == null) {
- mFooterSpacer = new Space(getApplicationContext());
+ // Need extra padding so the list can fully scroll up
+ if (useLayoutWithDefault()) {
+ if (mFooterSpacer == null) {
+ mFooterSpacer = new Space(getApplicationContext());
+ } else {
+ ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ }
+ mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
+ mSystemWindowInsets.bottom));
+ ((ListView) mAdapterView).addFooterView(mFooterSpacer);
} else {
- ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ View emptyView = findViewById(R.id.empty);
+ if (emptyView != null) {
+ emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+ + getResources().getDimensionPixelSize(
+ R.dimen.chooser_edge_margin_normal) * 2);
+ }
}
- mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
- mSystemWindowInsets.bottom));
- ((ListView) mAdapterView).addFooterView(mFooterSpacer);
resetButtonBar();
@@ -561,7 +564,7 @@ public class ResolverActivity extends Activity {
intent.getData().getHost(),
mAdapter.getFilteredItem().getDisplayLabel());
} else if (mAdapter.areAllTargetsBrowsers()) {
- dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
+ dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
} else {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
intent.getData().getHost());
@@ -1304,6 +1307,7 @@ public class ResolverActivity extends Activity {
// In case this method is called again (due to activity recreation), avoid adding a new
// header if one is already present.
if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) {
+ listView.setHeaderDividersEnabled(true);
listView.addHeaderView(LayoutInflater.from(this).inflate(
R.layout.resolver_different_item_header, listView, false));
}
@@ -1346,11 +1350,13 @@ public class ResolverActivity extends Activity {
final ViewGroup buttonLayout = findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
- int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
- R.dimen.resolver_button_bar_spacing) + inset);
+ if (!useLayoutWithDefault()) {
+ int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+ buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+ buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+ R.dimen.resolver_button_bar_spacing) + inset);
+ }
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
@@ -2057,7 +2063,9 @@ public class ResolverActivity extends Activity {
CharSequence subLabel = info.getExtendedInfo();
if (TextUtils.equals(label, subLabel)) subLabel = null;
- if (!TextUtils.equals(holder.text2.getText(), subLabel)) {
+ if (!TextUtils.equals(holder.text2.getText(), subLabel)
+ && !TextUtils.isEmpty(subLabel)) {
+ holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(subLabel);
}
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index bd4862dfb08d..637025329e37 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -16,6 +16,7 @@
#define ATRACE_TAG ATRACE_TAG_RESOURCES
+#include "android-base/logging.h"
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
#include "android-base/unique_fd.h"
@@ -32,7 +33,7 @@ using ::android::base::unique_fd;
namespace android {
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
- jboolean force_shared_lib, jboolean overlay) {
+ jboolean force_shared_lib, jboolean overlay, jboolean for_loader) {
ScopedUtfChars path(env, java_path);
if (path.c_str() == nullptr) {
return 0;
@@ -46,7 +47,7 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboole
} else if (force_shared_lib) {
apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
} else {
- apk_assets = ApkAssets::Load(path.c_str(), system);
+ apk_assets = ApkAssets::Load(path.c_str(), system, for_loader);
}
if (apk_assets == nullptr) {
@@ -58,7 +59,8 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboole
}
static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
- jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+ jstring friendly_name, jboolean system, jboolean force_shared_lib,
+ jboolean for_loader) {
ScopedUtfChars friendly_name_utf8(env, friendly_name);
if (friendly_name_utf8.c_str() == nullptr) {
return 0;
@@ -80,7 +82,9 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri
std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
friendly_name_utf8.c_str(),
- system, force_shared_lib);
+ system, force_shared_lib,
+ for_loader);
+
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
friendly_name_utf8.c_str(), dup_fd.get());
@@ -90,6 +94,60 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri
return reinterpret_cast<jlong>(apk_assets.release());
}
+static jlong NativeLoadArsc(JNIEnv* env, jclass /*clazz*/, jstring java_path,
+ jboolean for_loader) {
+ ScopedUtfChars path(env, java_path);
+ if (path.c_str() == nullptr) {
+ return 0;
+ }
+
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsArsc(%s)", path.c_str()).c_str());
+
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader);
+
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+ jstring friendly_name, jboolean for_loader) {
+ ScopedUtfChars friendly_name_utf8(env, friendly_name);
+ if (friendly_name_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str());
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets =
+ ApkAssets::LoadArsc(std::move(dup_fd), friendly_name_utf8.c_str(), for_loader);
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path from fd %d", fd);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jboolean for_loader) {
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadEmpty(for_loader);
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
delete reinterpret_cast<ApkAssets*>(ptr);
}
@@ -138,9 +196,13 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil
// JNI registration.
static const JNINativeMethod gApkAssetsMethods[] = {
- {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
- {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+ {"nativeLoad", "(Ljava/lang/String;ZZZZ)J", (void*)NativeLoad},
+ {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZ)J",
(void*)NativeLoadFromFd},
+ {"nativeLoadArsc", "(Ljava/lang/String;Z)J", (void*)NativeLoadArsc},
+ {"nativeLoadArscFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)J",
+ (void*)NativeLoadArscFromFd},
+ {"nativeLoadEmpty", "(Z)J", (void*)NativeLoadEmpty},
{"nativeDestroy", "(J)V", (void*)NativeDestroy},
{"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 9445319e47ec..d62d2d967d85 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -139,8 +139,8 @@ static stat_field_names stat_field_names[_NUM_CORE_HEAP] = {
"nativePrivateClean", "nativeSharedClean", "nativeSwappedOut", "nativeSwappedOutPss" }
};
-jfieldID otherStats_field;
-jfieldID hasSwappedOutPss_field;
+static jfieldID otherStats_field;
+static jfieldID hasSwappedOutPss_field;
struct stats_t {
int pss;
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index daf33f61105c..c7b36d0f8fc9 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -108,7 +108,7 @@ static struct arraymap_offsets_t {
jmethodID put;
} gArrayMapOffsets;
-jclass g_stringClass = nullptr;
+static jclass g_stringClass = nullptr;
// ----------------------------------------------------------------------------
@@ -763,6 +763,41 @@ static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint
return reinterpret_cast<jlong>(xml_tree.release());
}
+static jlong NativeOpenXmlAssetFd(JNIEnv* env, jobject /*clazz*/, jlong ptr, int jcookie,
+ jobject file_descriptor) {
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ ATRACE_NAME(base::StringPrintf("AssetManager::OpenXmlAssetFd(%d)", fd).c_str());
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ base::unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<Asset>
+ asset(Asset::createFromFd(dup_fd.release(), nullptr, Asset::AccessMode::ACCESS_BUFFER));
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+
+ // May be nullptr.
+ const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
@@ -1564,6 +1599,7 @@ static const JNINativeMethod gAssetManagerMethods[] = {
{"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
(void*)NativeOpenNonAssetFd},
{"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+ {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
// AssetManager resource methods.
{"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 0bdb25a8d307..485709523e66 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -22,8 +22,6 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?attr/listPreferredItemHeightSmall"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
android:background="?attr/activatedBackgroundIndicator">
<!-- Activity icon when presenting dialog
@@ -32,7 +30,8 @@
android:layout_width="@dimen/resolver_icon_size"
android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|center_vertical"
- android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:layout_marginEnd="@dimen/resolver_icon_margin"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:scaleType="fitCenter" />
@@ -40,8 +39,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="start|center_vertical"
android:orientation="vertical"
- android:paddingStart="?attr/listPreferredItemPaddingStart"
- android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+ android:paddingEnd="@dimen/resolver_edge_margin"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="start|center_vertical">
@@ -49,14 +47,20 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/textAppearanceMedium"
- android:textColor="?attr/textColorPrimary"
+ android:layout_gravity="start|center_vertical"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:textSize="16sp"
android:minLines="1"
android:maxLines="1"
android:ellipsize="marquee" />
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@android:id/text2"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:layout_gravity="start|center_vertical"
+ android:textSize="14sp"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minLines="1"
diff --git a/core/res/res/layout/resolver_different_item_header.xml b/core/res/res/layout/resolver_different_item_header.xml
index 7d9ffd72870d..0a35edc42329 100644
--- a/core/res/res/layout/resolver_different_item_header.xml
+++ b/core/res/res/layout/resolver_different_item_header.xml
@@ -22,12 +22,12 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:text="@string/use_a_different_app"
- android:minHeight="56dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:elevation="8dp"
- />
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:elevation="1dp" />
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 1dd420746e8a..6e45e7a4c509 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -29,16 +29,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
- android:elevation="8dp"
- android:background="?attr/colorBackgroundFloating">
+ android:elevation="@dimen/resolver_elevation"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:background="@drawable/bottomsheet_background">
<TextView
android:id="@+id/profile_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
android:visibility="gone"
style="?attr/borderlessButtonStyle"
android:textAppearance="?attr/textAppearanceButton"
@@ -50,36 +52,49 @@
<TextView
android:id="@+id/title"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="56dp"
- android:textAppearance="?attr/textAppearanceMedium"
- android:gravity="start|center_vertical"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="8dp"
android:layout_below="@id/profile_button"
android:layout_alignParentStart="true"
- android:paddingBottom="8dp" />
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
+ android:gravity="start|center_vertical" />
</RelativeLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
+ android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
- android:divider="@null" />
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
+
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:layout_alwaysShow="true"
android:text="@string/noApplications"
android:padding="32dp"
@@ -102,18 +117,19 @@
android:background="?attr/colorBackgroundFloating"
android:paddingTop="@dimen/resolver_button_bar_spacing"
android:paddingBottom="@dimen/resolver_button_bar_spacing"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
+ android:textAllCaps="false"
android:enabled="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -123,8 +139,9 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/activity_resolver_use_always"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 740a7eb9374e..dbba0b7bcc25 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -29,22 +29,22 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp">
+ android:background="@drawable/bottomsheet_background"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal">
-
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin">
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/resolver_icon_size"
+ android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|top"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
android:src="@drawable/resolver_icon_placeholder"
android:scaleType="fitCenter" />
@@ -52,9 +52,11 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_weight="1"
- android:layout_height="?attr/listPreferredItemHeight"
- android:layout_marginStart="16dp"
- android:textAppearance="?attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
android:paddingEnd="16dp" />
@@ -107,21 +109,22 @@
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -130,29 +133,40 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_always"
android:onClick="onButtonClick" />
</LinearLayout>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/dividerVertical" />
</LinearLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
- android:divider="@null" />
-
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollIndicators="top|bottom"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 609659b62948..a01bbe38f296 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -750,7 +750,7 @@
<dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
- <!-- chooser (sharesheet) spacing -->
+ <!-- chooser/resolver (sharesheet) spacing -->
<dimen name="chooser_corner_radius">8dp</dimen>
<dimen name="chooser_row_text_option_translate">25dp</dimen>
<dimen name="chooser_view_spacing">18dp</dimen>
@@ -759,11 +759,15 @@
<dimen name="chooser_preview_image_font_size">20sp</dimen>
<dimen name="chooser_preview_image_border">1dp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
- <dimen name="resolver_icon_size">42dp</dimen>
- <dimen name="resolver_button_bar_spacing">8dp</dimen>
- <dimen name="resolver_badge_size">18dp</dimen>
<dimen name="chooser_target_width">90dp</dimen>
<dimen name="chooser_header_scroll_elevation">4dp</dimen>
<dimen name="chooser_max_collapsed_height">288dp</dimen>
<dimen name="chooser_direct_share_label_placeholder_max_width">72dp</dimen>
+ <dimen name="resolver_icon_size">32dp</dimen>
+ <dimen name="resolver_button_bar_spacing">8dp</dimen>
+ <dimen name="resolver_badge_size">18dp</dimen>
+ <dimen name="resolver_icon_margin">16dp</dimen>
+ <dimen name="resolver_small_margin">18dp</dimen>
+ <dimen name="resolver_edge_margin">24dp</dimen>
+ <dimen name="resolver_elevation">1dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 363bc9ddd75c..c5a0dfca4bd1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3819,6 +3819,10 @@
<java-symbol type="dimen" name="resolver_icon_size"/>
<java-symbol type="dimen" name="resolver_badge_size"/>
<java-symbol type="dimen" name="resolver_button_bar_spacing"/>
+ <java-symbol type="dimen" name="resolver_icon_margin"/>
+ <java-symbol type="dimen" name="resolver_small_margin"/>
+ <java-symbol type="dimen" name="resolver_edge_margin"/>
+ <java-symbol type="dimen" name="resolver_elevation"/>
<!-- For DropBox -->
<java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
new file mode 100644
index 000000000000..53db8322f7b8
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/Android.bp
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+ name: "FrameworksResourceLoaderTests",
+ srcs: [
+ "src/**/*.kt"
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.espresso.core",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+ resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ],
+ test_suites: ["device-tests"],
+ sdk_version: "test_current",
+ aaptflags: [
+ "--no-compress",
+ ],
+ data: [
+ ":FrameworksResourceLoaderTestsOverlay",
+ ":FrameworksResourceLoaderTestsSplitOne",
+ ":FrameworksResourceLoaderTestsSplitTwo",
+ ],
+ java_resources: [ "NonAsset.txt" ]
+}
+
+filegroup {
+ name: "FrameworksResourceLoaderTestsResources",
+ srcs: ["resources"],
+}
+
+genrule {
+ name: "FrameworksResourceLoaderTestsAssets",
+ srcs: [
+ ":framework-res",
+ ":FrameworksResourceLoaderTestsResources",
+ ],
+ tools: [ ":aapt2", ":soong_zip" ],
+ tool_files: [ "resources/compileAndLink.sh" ],
+ cmd: "$(location resources/compileAndLink.sh) $(location :aapt2) $(location :soong_zip) $(genDir) $(in) $(in)",
+ out: [ "out.zip" ]
+}
diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml
new file mode 100644
index 000000000000..00b4ccbd8030
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Split loading is tested separately, so this must be marked isolated -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ android:isolatedSplits="true"
+ >
+
+ <uses-sdk android:minSdkVersion="29"/>
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+
+ <activity
+ android:name=".TestActivity"
+ android:configChanges="orientation"
+ />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="ResourceLoaderTests"
+ android:targetPackage="android.content.res.loader.test"
+ />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
new file mode 100644
index 000000000000..702151d01110
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Test module config for ResourceLoaderTests">
+ <option name="test-tag" value="ResourceLoaderTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
+ <option name="split-apk-file-names"
+ value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" />
+ <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="cmd overlay disable android.content.res.loader.test.overlay" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.content.res.loader.test" />
+ </test>
+</configuration>
diff --git a/core/tests/ResourceLoaderTests/NonAsset.txt b/core/tests/ResourceLoaderTests/NonAsset.txt
new file mode 100644
index 000000000000..5c0b2cc98d64
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/NonAsset.txt
@@ -0,0 +1 @@
+Outside assets directory
diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
new file mode 100644
index 000000000000..897897fbf254
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+ name: "FrameworksResourceLoaderTestsSplitOne"
+}
diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
new file mode 100644
index 000000000000..b14bd8600f31
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ split="split_one"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
new file mode 100644
index 000000000000..3c215ebc287c
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="string" name="split_overlaid" id="0x7f040001" />
+ <string name="split_overlaid">Split ONE Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/assets/Asset.txt b/core/tests/ResourceLoaderTests/assets/Asset.txt
new file mode 100644
index 000000000000..03f9a0fd146a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/assets/Asset.txt
@@ -0,0 +1 @@
+In assets directory
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
new file mode 100644
index 000000000000..a12e33a34aee
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
new file mode 100644
index 000000000000..182cbabadfe6
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
new file mode 100644
index 000000000000..e6b5f15b8a57
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
new file mode 100644
index 000000000000..e9c743c60289
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
new file mode 100644
index 000000000000..cd0536042662
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
new file mode 100644
index 000000000000..dc8aa90385fd
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
new file mode 100644
index 000000000000..8a672bac4685
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
new file mode 100644
index 000000000000..56f3d1e385e4
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
new file mode 100644
index 000000000000..663d3128dd54
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
new file mode 100644
index 000000000000..5f6e4b8cc988
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/overlay/Android.bp
new file mode 100644
index 000000000000..63e7e61d797a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "FrameworksResourceLoaderTestsOverlay",
+ sdk_version: "current",
+
+ aaptflags: ["--no-resource-removal"],
+}
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
new file mode 100644
index 000000000000..942f7da9aa27
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test.overlay"
+ >
+
+ <application android:hasCode="false" />
+
+ <overlay android:targetPackage="android.content.res.loader.test" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
new file mode 100644
index 000000000000..348bb353611a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="loader_path_change_test">Overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
new file mode 100644
index 000000000000..efd71ee039e2
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
new file mode 100644
index 000000000000..d1211c50a203
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#B2D2F2"
+ />
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
new file mode 100644
index 000000000000..d59059b453d6
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/res/values/strings.xml b/core/tests/ResourceLoaderTests/res/values/strings.xml
new file mode 100644
index 000000000000..28b8f73d45a6
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="loader_path_change_test">Not overlaid</string>
+ <string name="split_overlaid">Not overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
new file mode 100644
index 000000000000..5dd8a966e2b7
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
new file mode 100644
index 000000000000..5a92ae9e662b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
new file mode 100755
index 000000000000..885f681f4261
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+aapt2=$1
+soong_zip=$2
+genDir=$3
+FRAMEWORK_RES_APK=$4
+inDir=$5
+
+# (String name, boolean retainFiles = false, String... files)
+function compileAndLink {
+ moduleName=$1
+ mkdir "$genDir"/out/"$moduleName"
+
+ args=""
+ for arg in "${@:4}"; do
+ if [[ $arg == res* ]]; then
+ args="$args $inDir/$arg"
+ else
+ args="$args $arg"
+ fi
+ done
+
+ $aapt2 compile -o "$genDir"/out/"$moduleName" $args
+
+ $aapt2 link \
+ -I "$FRAMEWORK_RES_APK" \
+ --manifest "$inDir"/"$3" \
+ -o "$genDir"/out/"$moduleName"/apk.apk \
+ "$genDir"/out/"$moduleName"/*.flat \
+ --no-compress
+
+ unzip -qq "$genDir"/out/"$moduleName"/apk.apk -d "$genDir"/out/"$moduleName"/unzip
+
+ if [[ "$2" == "APK_WITHOUT_FILE" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+ zip -q -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+ cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+ elif [[ "$2" == "APK" || "$2" == "BOTH" ]]; then
+ cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+ fi
+
+ if [[ "$2" == "ARSC" || "$2" == "BOTH" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+ zip -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+ cp "$genDir"/out/"$moduleName"/unzip/resources.arsc "$genDir"/output/raw/"$moduleName"Arsc.arsc
+ fi
+}
+
+rm -r "$genDir"/out
+rm -r "$genDir"/output
+rm -r "$genDir"/temp
+
+mkdir "$genDir"/out
+mkdir -p "$genDir"/output/raw
+mkdir -p "$genDir"/temp/res/drawable-nodpi
+mkdir -p "$genDir"/temp/res/layout
+
+compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml
+compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml
+
+compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml
+compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml
+
+compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+
+compileAndLink layoutWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+compileAndLink layoutWithFile APK AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+
+cp -f "$inDir"/res/layout/layout_one.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutOne/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutOne.xml
+
+cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml
+
+drawableNoDpi="/res/drawable-nodpi"
+inDirDrawableNoDpi="$inDir$drawableNoDpi"
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableOne.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableOne/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableOne.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png
+
+$soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
new file mode 100644
index 000000000000..f3e53d7596c1
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
new file mode 100644
index 000000000000..5231d175569e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
new file mode 100644
index 000000000000..671d6d00be31
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
new file mode 100644
index 000000000000..f1a93d2d2f21
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#A3C3E3"
+ />
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
new file mode 100644
index 000000000000..7c455a57fb0b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#3A3C3E"
+ />
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
new file mode 100644
index 000000000000..d59059b453d6
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
new file mode 100644
index 000000000000..ede3838be8de
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
new file mode 100644
index 000000000000..d8bff90d56d8
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
new file mode 100644
index 000000000000..a552431e23be
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="layout" name="activity_list_item" id="0x01090000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
new file mode 100644
index 000000000000..69ecf2316284
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="dimen" name="app_icon_size" id="0x01050000" />
+ <dimen name="app_icon_size">564716dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
new file mode 100644
index 000000000000..4d55deffbd2a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="dimen" name="app_icon_size" id="0x01050000" />
+ <dimen name="app_icon_size">565717dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
new file mode 100644
index 000000000000..b5b4dfd22231
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="drawable" name="ic_delete" id="0x0108001d" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
new file mode 100644
index 000000000000..4962a07bc8c7
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="layout" name="layout" id="0x7f020000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
new file mode 100644
index 000000000000..38b152beb76f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="drawable" name="non_asset_bitmap" id="0x7f010000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
new file mode 100644
index 000000000000..bdd6f58e5824
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="drawable" name="non_asset_drawable" id="0x7f010001" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
new file mode 100644
index 000000000000..4fc52723946e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringOne</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
new file mode 100644
index 000000000000..3604d7b21cf5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringTwo</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/Android.bp
new file mode 100644
index 000000000000..4582808934df
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+ name: "FrameworksResourceLoaderTestsSplitTwo"
+}
diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
new file mode 100644
index 000000000000..aad8c27a1a3b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ split="split_two"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
new file mode 100644
index 000000000000..a367063dd43e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="string" name="split_overlaid" id="0x7f040001" />
+ <string name="split_overlaid">Split TWO Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
new file mode 100644
index 000000000000..b1bdc967e68f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import java.io.File
+
+class DirectoryResourceLoaderTest : ResourceLoaderTestBase() {
+
+ @get:Rule
+ val testName = TestName()
+
+ private lateinit var testDir: File
+ private lateinit var loader: ResourceLoader
+
+ @Before
+ fun setUpTestDir() {
+ testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+ loader = DirectoryResourceLoader(testDir)
+ }
+
+ @After
+ fun deleteTestFiles() {
+ testDir.deleteRecursively()
+ }
+
+ @Test
+ fun loadDrawableXml() {
+ "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml"
+ val provider = openArsc("nonAssetDrawableOne")
+
+ fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable)
+ .color
+
+ assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2"))
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3"))
+ }
+
+ @Test
+ fun loadDrawableBitmap() {
+ "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png"
+ val provider = openArsc("nonAssetBitmapGreen")
+
+ fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+ .bitmap.getColor(0, 0).toArgb()
+
+ assertThat(getValue()).isEqualTo(Color.RED)
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo(Color.GREEN)
+ }
+
+ @Test
+ fun loadXml() {
+ "layoutOne" writeTo "res/layout/layout.xml"
+ val provider = openArsc("layoutOne")
+
+ fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name
+
+ assertThat(getValue()).isEqualTo("FrameLayout")
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo("RelativeLayout")
+ }
+
+ private infix fun String.writeTo(path: String) {
+ val testFile = testDir.resolve(path)
+ testFile.parentFile!!.mkdirs()
+ resources.openRawResource(rawFile(this))
+ .copyTo(testFile.outputStream())
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
new file mode 100644
index 000000000000..a6a83789c082
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.AssetManager
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.nio.file.Paths
+
+@RunWith(Parameterized::class)
+class ResourceLoaderAssetTest : ResourceLoaderTestBase() {
+
+ companion object {
+ private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
+ private const val TEST_TEXT = "some text"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun parameters(): Array<Array<out Any?>> {
+ val fromInputStream: ResourceLoader.(String) -> Any? = {
+ loadAsset(eq(it), anyInt())
+ }
+
+ val fromFileDescriptor: ResourceLoader.(String) -> Any? = {
+ loadAssetFd(eq(it))
+ }
+
+ val openAsset: AssetManager.() -> String? = {
+ open(BASE_TEST_PATH).reader().readText()
+ }
+
+ val openNonAsset: AssetManager.() -> String? = {
+ openNonAssetFd(BASE_TEST_PATH).readText()
+ }
+
+ return arrayOf(
+ arrayOf("assets", fromInputStream, openAsset),
+ arrayOf("", fromFileDescriptor, openNonAsset)
+ )
+ }
+ }
+
+ @get:Rule
+ val testName = TestName()
+
+ @JvmField
+ @field:Parameterized.Parameter(0)
+ var prefix: String? = null
+
+ @field:Parameterized.Parameter(1)
+ lateinit var loadAssetFunction: ResourceLoader.(String) -> Any?
+
+ @field:Parameterized.Parameter(2)
+ lateinit var openAssetFunction: AssetManager.() -> String?
+
+ private val testPath: String
+ get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
+
+ private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath)
+
+ private fun AssetManager.openAsset() = openAssetFunction()
+
+ private lateinit var testDir: File
+
+ @Before
+ fun setUpTestDir() {
+ testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+ testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
+ }
+
+ @Test
+ fun multipleLoadersSearchesBackwards() {
+ // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
+ val loader = DirectoryResourceLoader(testDir)
+ val loaderWrapper = mock(ResourceLoader::class.java).apply {
+ doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
+ .`when`(this).loadAsset(anyString(), anyInt())
+ doAnswer { loader.loadAssetFd(it.arguments[0] as String) }
+ .`when`(this).loadAssetFd(anyString())
+ }
+
+ val one = loaderWrapper to ResourcesProvider.empty()
+ val two = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ inOrder(two.first, one.first).apply {
+ verify(two.first).loadAsset()
+ verify(one.first).loadAsset()
+ }
+ }
+
+ @Test(expected = FileNotFoundException::class)
+ fun failToFindThrowsFileNotFound() {
+ val one = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+ val two = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ @Test
+ fun throwingIOExceptionIsSkipped() {
+ val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+ val two = mockLoader {
+ doAnswer { throw IOException() }.`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun throwingNonIOExceptionCausesFailure() {
+ val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+ val two = mockLoader {
+ doAnswer { throw IllegalStateException() }.`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ private fun assertOpenedAsset() {
+ assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
new file mode 100644
index 000000000000..e01e254b1f16
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Bundle
+import android.os.ParcelFileDescriptor
+import android.widget.FrameLayout
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
+import androidx.test.runner.lifecycle.Stage
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.Arrays
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+import java.util.concurrent.TimeUnit
+
+// @Ignore("UiAutomation is crashing with not connected, not sure why")
+@RunWith(Parameterized::class)
+class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
+
+ companion object {
+ private const val TIMEOUT = 30L
+ private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @get:Rule
+ val activityRule: ActivityTestRule<TestActivity> =
+ ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
+
+ // Redirect to the Activity's resources
+ override val resources: Resources
+ get() = activityRule.getActivity().resources
+
+ private val activity: TestActivity
+ get() = activityRule.getActivity()
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @Before
+ @After
+ fun disableOverlay() {
+// enableOverlay(OVERLAY_PACKAGE, false)
+ }
+
+ @Test
+ fun activityRecreate() = verifySameBeforeAndAfter {
+ val oldActivity = activity
+ var newActivity: Activity? = null
+ instrumentation.runOnMainSync { oldActivity.recreate() }
+ instrumentation.waitForIdleSync()
+ instrumentation.runOnMainSync {
+ newActivity = ActivityLifecycleMonitorRegistry.getInstance()
+ .getActivitiesInStage(Stage.RESUMED)
+ .single()
+ }
+
+ assertThat(newActivity).isNotNull()
+ assertThat(newActivity).isNotSameAs(oldActivity)
+
+ // Return the new resources to assert on
+ return@verifySameBeforeAndAfter newActivity!!.resources
+ }
+
+ @Test
+ fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
+ val latch = CountDownLatch(1)
+ val oldConfig = Configuration().apply { setTo(resources.configuration) }
+ var changedConfig: Configuration? = null
+
+ activity.callback = object : TestActivity.Callback {
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ changedConfig = newConfig
+ latch.countDown()
+ }
+ }
+
+ val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
+ val newRotation = if (isPortrait) {
+ UiAutomation.ROTATION_FREEZE_90
+ } else {
+ UiAutomation.ROTATION_FREEZE_0
+ }
+
+ instrumentation.uiAutomation.setRotation(newRotation)
+
+ assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+ assertThat(changedConfig).isNotEqualTo(oldConfig)
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ @Test
+ fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
+ assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
+
+ enableOverlay(OVERLAY_PACKAGE, true)
+
+ assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
+
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ @Test
+ fun enableOverlayChildContextUnaffected() {
+ val childContext = activity.createConfigurationContext(Configuration())
+ val childResources = childContext.resources
+ val originalValue = childResources.getString(android.R.string.cancel)
+ assertThat(childResources.getString(R.string.loader_path_change_test))
+ .isEqualTo("Not overlaid")
+
+ verifySameBeforeAndAfter {
+ enableOverlay(OVERLAY_PACKAGE, true)
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ // Loader not applied, but overlay change propagated
+ assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
+ assertThat(childResources.getString(R.string.loader_path_change_test))
+ .isEqualTo("Overlaid")
+ }
+
+ // All these tests assert for the exact same loaders/values, so extract that logic out
+ private fun verifySameBeforeAndAfter(block: () -> Resources) {
+ // TODO(chiuwinson): atest doesn't work with @Ignore, UiAutomation not connected error
+ Assume.assumeFalse(true)
+
+ val originalValue = resources.getString(android.R.string.cancel)
+
+ val loader = "stringOne".openLoader()
+ addLoader(loader)
+
+ val oldLoaders = resources.loaders
+ val oldValue = resources.getString(android.R.string.cancel)
+
+ assertThat(oldValue).isNotEqualTo(originalValue)
+
+ val newResources = block()
+
+ val newLoaders = newResources.loaders
+ val newValue = newResources.getString(android.R.string.cancel)
+
+ assertThat(newValue).isEqualTo(oldValue)
+ assertThat(newLoaders).isEqualTo(oldLoaders)
+ }
+
+ // Copied from overlaytests LocalOverlayManager
+ private fun enableOverlay(packageName: String, enable: Boolean) {
+ val executor = Executor { Thread(it).start() }
+ val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
+ if (executeShellCommand("cmd overlay list").contains(pattern)) {
+ // nothing to do, overlay already in the requested state
+ return
+ }
+
+ val oldApkPaths = resources.assets.apkPaths
+ val task = FutureTask {
+ while (true) {
+ if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
+ return@FutureTask true
+ }
+ Thread.sleep(10)
+ }
+
+ @Suppress("UNREACHABLE_CODE")
+ return@FutureTask false
+ }
+
+ val command = if (enable) "enable" else "disable"
+ executeShellCommand("cmd overlay $command $packageName")
+ executor.execute(task)
+ assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+ }
+
+ private fun executeShellCommand(command: String): String {
+ val uiAutomation = instrumentation.uiAutomation
+ val pfd = uiAutomation.executeShellCommand(command)
+ return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
+ }
+}
+
+class TestActivity : Activity() {
+
+ var callback: Callback? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(FrameLayout(this).apply {
+ setBackgroundColor(Color.BLUE)
+ })
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ callback?.onConfigurationChanged(newConfig)
+ }
+
+ interface Callback {
+ fun onConfigurationChanged(newConfig: Configuration)
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
new file mode 100644
index 000000000000..09fd27e02b59
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderDrawableTest : ResourceLoaderTestBase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @Test
+ fun matchingConfig() {
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+
+ addLoader(loader)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+
+ loader.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotEqualTo(original)
+ assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+ assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+ }
+
+ @Test
+ fun worseConfig() {
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ addLoader(loader)
+
+ updateConfiguration { densityDpi = 480 /* xhdpi */ }
+
+ getDrawable(android.R.drawable.ic_delete)
+
+ verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any())
+ }
+
+ @Test
+ fun multipleLoaders() {
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loaderOne = "drawableMdpiWithoutFile".openLoader()
+ val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+ `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotEqualTo(original)
+ assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+ assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+ }
+
+ @Test(expected = Resources.NotFoundException::class)
+ fun multipleLoadersNoReturnWithoutFile() {
+ val loaderOne = "drawableMdpiWithoutFile".openLoader()
+ val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ try {
+ getDrawable(android.R.drawable.ic_delete)
+ } finally {
+ // We expect the call to fail because at least the loader won't resolve the overridden
+ // drawable, but we should still verify that both loaders were called before allowing
+ // the exception to propagate.
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+ }
+ }
+
+ @Test
+ fun multipleLoadersReturnWithFile() {
+ // Can't return a file if an ARSC
+ assumeThat(dataType, not(DataType.ARSC))
+
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loaderOne = "drawableMdpiWithFile".openLoader()
+ val loaderTwo = "drawableMdpiWithFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotNull()
+ assertThat(drawable).isInstanceOf(original.javaClass)
+ }
+
+ @Test
+ fun unhandledResourceIgnoresLoaders() {
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+ addLoader(loader)
+
+ getDrawable(android.R.drawable.ic_menu_add)
+
+ loader.verifyLoadDrawableNotCalled()
+
+ getDrawable(android.R.drawable.ic_delete)
+
+ loader.verifyLoadDrawableCalled()
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() {
+ verify(first).loadDrawable(
+ argThat {
+ it.density == 160 &&
+ it.resourceId == android.R.drawable.ic_delete &&
+ it.string == "res/drawable-mdpi-v4/ic_delete.png"
+ },
+ eq(android.R.drawable.ic_delete),
+ eq(0),
+ any()
+ )
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() {
+ verify(first, never()).loadDrawable(
+ any(),
+ anyInt(),
+ anyInt(),
+ any()
+ )
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
new file mode 100644
index 000000000000..1ec209486c18
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderLayoutTest : ResourceLoaderTestBase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @Test
+ fun singleLoader() {
+ val original = getLayout(android.R.layout.activity_list_item)
+ val mockXml = mock(XmlResourceParser::class.java)
+ val loader = "layoutWithoutFile".openLoader()
+ `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+
+ addLoader(loader)
+
+ val layout = getLayout(android.R.layout.activity_list_item)
+ loader.verifyLoadLayoutCalled()
+
+ assertThat(layout).isNotEqualTo(original)
+ assertThat(layout).isSameAs(mockXml)
+ }
+
+ @Test
+ fun multipleLoaders() {
+ val original = getLayout(android.R.layout.activity_list_item)
+ val loaderOne = "layoutWithoutFile".openLoader()
+ val loaderTwo = "layoutWithoutFile".openLoader()
+
+ val mockXml = mock(XmlResourceParser::class.java)
+ `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+
+ addLoader(loaderOne, loaderTwo)
+
+ val layout = getLayout(android.R.layout.activity_list_item)
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+
+ assertThat(layout).isNotEqualTo(original)
+ assertThat(layout).isSameAs(mockXml)
+ }
+
+ @Test(expected = Resources.NotFoundException::class)
+ fun multipleLoadersNoReturnWithoutFile() {
+ val loaderOne = "layoutWithoutFile".openLoader()
+ val loaderTwo = "layoutWithoutFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ try {
+ getLayout(android.R.layout.activity_list_item)
+ } finally {
+ // We expect the call to fail because at least one loader must resolve the overridden
+ // layout, but we should still verify that both loaders were called before allowing
+ // the exception to propagate.
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+ }
+ }
+
+ @Test
+ fun multipleLoadersReturnWithFile() {
+ // Can't return a file if an ARSC
+ assumeThat(dataType, not(DataType.ARSC))
+
+ val loaderOne = "layoutWithFile".openLoader()
+ val loaderTwo = "layoutWithFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ val xml = getLayout(android.R.layout.activity_list_item)
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+
+ assertThat(xml).isNotNull()
+ }
+
+ @Test
+ fun unhandledResourceIgnoresLoaders() {
+ val loader = "layoutWithoutFile".openLoader()
+ val mockXml = mock(XmlResourceParser::class.java)
+ `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+ addLoader(loader)
+
+ getLayout(android.R.layout.preference_category)
+
+ verify(loader.first, never())
+ .loadXmlResourceParser(anyString(), anyInt())
+
+ getLayout(android.R.layout.activity_list_item)
+
+ loader.verifyLoadLayoutCalled()
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() {
+ verify(first).loadXmlResourceParser(
+ "res/layout/activity_list_item.xml",
+ android.R.layout.activity_list_item
+ )
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() {
+ verify(first, never()).loadXmlResourceParser(
+ anyString(),
+ anyInt()
+ )
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
new file mode 100644
index 000000000000..5af453d526e4
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetManager
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Before
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.io.Closeable
+
+abstract class ResourceLoaderTestBase {
+
+ open lateinit var dataType: DataType
+
+ protected lateinit var context: Context
+ protected open val resources: Resources
+ get() = context.resources
+ protected open val assets: AssetManager
+ get() = resources.assets
+
+ // Track opened streams and ResourcesProviders to close them after testing
+ private val openedObjects = mutableListOf<Closeable>()
+
+ @Before
+ fun setUpBase() {
+ context = InstrumentationRegistry.getTargetContext()
+ }
+
+ @After
+ fun removeAllLoaders() {
+ resources.setLoaders(null)
+ openedObjects.forEach {
+ try {
+ it.close()
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getString(stringRes) }
+
+ protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getDrawable(drawableRes) }
+
+ protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getLayout(layoutRes) }
+
+ protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getDimensionPixelSize(dimenRes) }
+
+ private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T {
+ if (debugLog) {
+ resources.assets.setResourceResolutionLoggingEnabled(true)
+ }
+
+ var thrown = false
+
+ try {
+ return resources.block()
+ } catch (t: Throwable) {
+ // No good way to log to test output other than throwing an exception
+ if (debugLog) {
+ thrown = true
+ throw IllegalStateException(resources.assets.lastResourceResolution, t)
+ } else {
+ throw t
+ }
+ } finally {
+ if (!thrown && debugLog) {
+ throw IllegalStateException(resources.assets.lastResourceResolution)
+ }
+ }
+ }
+
+ protected fun updateConfiguration(block: Configuration.() -> Unit) {
+ val configuration = Configuration().apply {
+ setTo(resources.configuration)
+ block()
+ }
+
+ resources.updateConfiguration(configuration, resources.displayMetrics)
+ }
+
+ protected fun String.openLoader(
+ dataType: DataType = this@ResourceLoaderTestBase.dataType
+ ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) {
+ DataType.APK -> {
+ mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use {
+ ResourcesProvider.loadFromApk(it)
+ }.also { openedObjects += it }
+ }
+ DataType.ARSC -> {
+ mock(ResourceLoader::class.java) to openArsc(this)
+ }
+ DataType.SPLIT -> {
+ mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this)
+ }
+ DataType.ASSET -> mockLoader {
+ doAnswer { byteInputStream() }.`when`(it)
+ .loadAsset(eq("assets/Asset.txt"), anyInt())
+ }
+ DataType.ASSET_FD -> mockLoader {
+ doAnswer {
+ val file = context.filesDir.resolve("Asset.txt")
+ file.writeText(this)
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ }.`when`(it).loadAssetFd("assets/Asset.txt")
+ }
+ DataType.NON_ASSET -> mockLoader {
+ doAnswer {
+ val file = context.filesDir.resolve("NonAsset.txt")
+ file.writeText(this)
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ }.`when`(it).loadAssetFd("NonAsset.txt")
+ }
+ DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it).loadDrawable(argThat { value ->
+ value.type == TypedValue.TYPE_STRING &&
+ value.resourceId == 0x7f010001 &&
+ value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml"
+ }, eq(0x7f010001), anyInt(), ArgumentMatchers.any())
+
+ doAnswer { context.copiedRawFile(this) }.`when`(it)
+ .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
+ }
+ DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it).loadDrawable(argThat { value ->
+ value.type == TypedValue.TYPE_STRING &&
+ value.resourceId == 0x7f010000 &&
+ value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png"
+ }, eq(0x7f010000), anyInt(), ArgumentMatchers.any())
+
+ doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() }
+ .`when`(it)
+ .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt())
+ }
+ DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it)
+ .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000)
+
+ doAnswer { context.copiedRawFile(this) }.`when`(it)
+ .loadAssetFd("res/layout/layout.xml")
+ }
+ }
+
+ protected fun mockLoader(
+ provider: ResourcesProvider = ResourcesProvider.empty(),
+ block: (ResourceLoader) -> Unit = {}
+ ): Pair<ResourceLoader, ResourcesProvider> {
+ return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS)
+ .apply(block) to provider
+ }
+
+ protected fun openArsc(rawName: String): ResourcesProvider {
+ return context.copiedRawFile("${rawName}Arsc")
+ .use { ResourcesProvider.loadFromArsc(it) }
+ .also { openedObjects += it }
+ }
+
+ // This specifically uses addLoader so both behaviors are tested
+ protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ pairs.forEach { resources.addLoader(it.first, it.second) }
+ }
+
+ protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) })
+ }
+
+ protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) {
+ resources.addLoader(pair.first, pair.second, index)
+ }
+
+ protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ pairs.forEach { resources.removeLoader(it.first) }
+ }
+
+ protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> {
+ // Cast instead of toMutableList to maintain the same object
+ return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>>
+ }
+
+ enum class DataType {
+ APK,
+ ARSC,
+ SPLIT,
+ ASSET,
+ ASSET_FD,
+ NON_ASSET,
+ NON_ASSET_DRAWABLE,
+ NON_ASSET_BITMAP,
+ NON_ASSET_LAYOUT,
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
new file mode 100644
index 000000000000..017552a02152
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
+ * the behavior being verified isn't specific to any resource type. As long as it can pass an
+ * equals check.
+ *
+ * Currently tests strings and dimens since String and any Number seemed most relevant to verify.
+ */
+@RunWith(Parameterized::class)
+class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
+
+ companion object {
+ @Parameterized.Parameters(name = "{1} {0}")
+ @JvmStatic
+ fun parameters(): Array<Any> {
+ val parameters = mutableListOf<Parameter>()
+
+ // R.string
+ parameters += Parameter(
+ { getString(android.R.string.cancel) },
+ "stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
+ "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
+ listOf(DataType.APK, DataType.ARSC)
+ )
+
+ // R.dimen
+ parameters += Parameter(
+ { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) },
+ "dimenOne", { 564716.dpToPx(resources) },
+ "dimenTwo", { 565717.dpToPx(resources) },
+ listOf(DataType.APK, DataType.ARSC)
+ )
+
+ // File in the assets directory
+ parameters += Parameter(
+ { assets.open("Asset.txt").reader().readText() },
+ "assetOne", { "assetOne" },
+ "assetTwo", { "assetTwo" },
+ listOf(DataType.ASSET)
+ )
+
+ // From assets directory returning file descriptor
+ parameters += Parameter(
+ { assets.openFd("Asset.txt").readText() },
+ "assetOne", { "assetOne" },
+ "assetTwo", { "assetTwo" },
+ listOf(DataType.ASSET_FD)
+ )
+
+ // From root directory returning file descriptor
+ parameters += Parameter(
+ { assets.openNonAssetFd("NonAsset.txt").readText() },
+ "NonAssetOne", { "NonAssetOne" },
+ "NonAssetTwo", { "NonAssetTwo" },
+ listOf(DataType.NON_ASSET)
+ )
+
+ // Asset as compiled XML drawable
+ parameters += Parameter(
+ { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
+ "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") },
+ "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") },
+ listOf(DataType.NON_ASSET_DRAWABLE)
+ )
+
+ // Asset as compiled bitmap drawable
+ parameters += Parameter(
+ {
+ (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+ .bitmap.getColor(0, 0).toArgb()
+ },
+ "nonAssetBitmapGreen", { Color.GREEN },
+ "nonAssetBitmapBlue", { Color.BLUE },
+ listOf(DataType.NON_ASSET_BITMAP)
+ )
+
+ // Asset as compiled XML layout
+ parameters += Parameter(
+ { getLayout(R.layout.layout).advanceToRoot().name },
+ "layoutOne", { "RelativeLayout" },
+ "layoutTwo", { "LinearLayout" },
+ listOf(DataType.NON_ASSET_LAYOUT)
+ )
+
+ // Isolated resource split
+ parameters += Parameter(
+ { getString(R.string.split_overlaid) },
+ "split_one", { "Split ONE Overlaid" },
+ "split_two", { "Split TWO Overlaid" },
+ listOf(DataType.SPLIT)
+ )
+
+ return parameters.flatMap { parameter ->
+ parameter.dataTypes.map { dataType ->
+ arrayOf(dataType, parameter)
+ }
+ }.toTypedArray()
+ }
+ }
+
+ @Suppress("LateinitVarOverridesLateinitVar")
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @field:Parameterized.Parameter(1)
+ lateinit var parameter: Parameter
+
+ private val valueOne by lazy { parameter.valueOne(this) }
+ private val valueTwo by lazy { parameter.valueTwo(this) }
+
+ private fun openOne() = parameter.loaderOne.openLoader()
+ private fun openTwo() = parameter.loaderTwo.openLoader()
+
+ // Class method for syntax highlighting purposes
+ private fun getValue() = parameter.getValue(this)
+
+ @Test
+ fun verifyValueUniqueness() {
+ // Ensure the parameters are valid in case of coding errors
+ assertNotEquals(valueOne, getValue())
+ assertNotEquals(valueTwo, getValue())
+ assertNotEquals(valueOne, valueTwo)
+ }
+
+ @Test
+ fun addMultipleLoaders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun setMultipleLoaders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ setLoaders(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ setLoaders()
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun getLoadersContainsAll() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertThat(getLoaders()).containsAllOf(testOne, testTwo)
+ }
+
+ @Test
+ fun getLoadersDoesNotLeakMutability() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ val loaders = getLoaders()
+ loaders += testTwo
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun alreadyAddedThrows() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+ addLoader(testTwo)
+ addLoader(testOne)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun alreadyAddedAndSetThrows() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+ addLoader(testTwo)
+ setLoaders(testTwo)
+ }
+
+ @Test
+ fun repeatedRemoveSucceeds() {
+ val originalValue = getValue()
+ val testOne = openOne()
+
+ addLoader(testOne)
+
+ assertNotEquals(originalValue, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun addToFront() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 0)
+
+ assertEquals(valueOne, getValue())
+
+ // Remove top loader, so previously added to front should now resolve
+ removeLoader(testOne)
+ assertEquals(valueTwo, getValue())
+ }
+
+ @Test
+ fun addToEnd() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 1)
+
+ assertEquals(valueTwo, getValue())
+ }
+
+ @Test(expected = IndexOutOfBoundsException::class)
+ fun addPastEnd() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 2)
+ }
+
+ @Test(expected = IndexOutOfBoundsException::class)
+ fun addBeforeFront() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, -1)
+ }
+
+ @Test
+ fun reorder() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(valueTwo, getValue())
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ data class Parameter(
+ val getValue: ResourceLoaderValuesTest.() -> Any,
+ val loaderOne: String,
+ val valueOne: ResourceLoaderValuesTest.() -> Any,
+ val loaderTwo: String,
+ val valueTwo: ResourceLoaderValuesTest.() -> Any,
+ val dataTypes: List<DataType>
+ ) {
+ override fun toString(): String {
+ val prefix = loaderOne.commonPrefixWith(loaderTwo)
+ return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}"
+ }
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
new file mode 100644
index 000000000000..df2d09adf503
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetFileDescriptor
+import android.content.res.Resources
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import org.mockito.Answers
+import org.mockito.stubbing.Answer
+import org.xmlpull.v1.XmlPullParser
+import java.io.File
+
+// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs
+typealias Pair<F, S> = android.util.Pair<F, S>
+infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!!
+
+object Utils {
+ val ANSWER_THROWS = Answer<Any> {
+ when (val name = it.method.name) {
+ "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ else -> throw UnsupportedOperationException("$name with " +
+ "${it.arguments?.joinToString()} should not be called")
+ }
+ }
+}
+
+fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this.toFloat(),
+ resources.displayMetrics
+).toInt()
+
+fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
+
+fun rawFile(fileName: String) = R.raw::class.java.getDeclaredField(fileName).getInt(null)
+
+fun XmlPullParser.advanceToRoot() = apply {
+ while (next() != XmlPullParser.START_TAG) {
+ // Empty
+ }
+}
+
+fun Context.copiedRawFile(fileName: String): ParcelFileDescriptor {
+ return resources.openRawResourceFd(rawFile(fileName)).use { asset ->
+ // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
+ // an individual file so one can be created manually.
+ val copiedFile = File(filesDir, fileName)
+ asset.createInputStream().use { input ->
+ copiedFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
+ }
+}
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index d36a82684e9e..61281eea7134 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -16,17 +16,30 @@
-->
<permissions>
<privapp-permissions package="com.google.android.car.kitchensink">
+ <permission name="android.permission.ACCESS_NETWORK_STATE"/>
+ <permission name="android.permission.ACCESS_WIFI_STATE"/>
+ <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+ <permission name="android.permission.INJECT_EVENTS"/>
+ <!-- use for CarServiceUnitTest and CarServiceTest -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <!-- use for CarServiceUnitTest -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.LOCATION_HARDWARE"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <!-- use for CarServiceTest -->
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.PROVIDE_TRUST_AGENT"/>
+ <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
+ <!-- use for CarServiceTest -->
+ <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eeaefc5b157c..a34a6c0b3724 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -166,7 +166,10 @@ cc_test {
static_libs: common_test_libs + ["liblog", "libz"],
},
},
- data: ["tests/data/**/*.apk"],
+ data: [
+ "tests/data/**/*.apk",
+ "tests/data/**/*.arsc",
+ ],
test_suites: ["device-tests"],
}
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index cf2ef3070385..b309621435b5 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -42,12 +42,16 @@ static const std::string kResourcesArsc("resources.arsc");
ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
const std::string& path,
- time_t last_mod_time)
- : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) {
+ time_t last_mod_time,
+ bool for_loader)
+ : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time),
+ for_loader(for_loader) {
}
-std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
- return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
+std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system,
+ bool for_loader) {
+ return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/,
+ for_loader);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
@@ -76,9 +80,21 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap
std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
const std::string& friendly_name,
- bool system, bool force_shared_lib) {
+ bool system, bool force_shared_lib,
+ bool for_loader) {
return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
- system, force_shared_lib);
+ system, force_shared_lib, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path,
+ bool for_loader) {
+ return LoadArscImpl({} /*fd*/, path, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd,
+ const std::string& friendly_name,
+ bool for_loader) {
+ return LoadArscImpl(std::move(fd), friendly_name, for_loader);
}
std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
@@ -104,7 +120,8 @@ std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
+ std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library,
+ bool for_loader) {
::ZipArchiveHandle unmanaged_handle;
int32_t result;
if (fd >= 0) {
@@ -123,7 +140,8 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
time_t last_mod_time = getFileModDate(path.c_str());
// Wrap the handle in a unique_ptr so it gets automatically closed.
- std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time));
+ std::unique_ptr<ApkAssets>
+ loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, for_loader));
// Find the resource table.
::ZipEntry entry;
@@ -152,7 +170,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
loaded_apk->resources_asset_->getLength());
loaded_apk->loaded_arsc_ =
- LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
+ LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library, for_loader);
if (loaded_apk->loaded_arsc_ == nullptr) {
LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
@@ -162,8 +180,53 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
return std::move(loaded_apk);
}
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd,
+ const std::string& path,
+ bool for_loader) {
+ std::unique_ptr<Asset> resources_asset;
+
+ if (fd >= 0) {
+ resources_asset = std::unique_ptr<Asset>(Asset::createFromFd(fd.release(), nullptr,
+ Asset::AccessMode::ACCESS_BUFFER));
+ } else {
+ resources_asset = CreateAssetFromFile(path);
+ }
+
+ if (resources_asset == nullptr) {
+ LOG(ERROR) << "Failed to open ARSC '" << path;
+ return {};
+ }
+
+ time_t last_mod_time = getFileModDate(path.c_str());
+
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, path, last_mod_time, for_loader));
+ loaded_apk->resources_asset_ = std::move(resources_asset);
+
+ const StringPiece data(
+ reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
+ loaded_apk->resources_asset_->getLength());
+ loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, nullptr, false, false, for_loader);
+ if (loaded_apk->loaded_arsc_ == nullptr) {
+ LOG(ERROR) << "Failed to load '" << kResourcesArsc << path;
+ return {};
+ }
+
+ // Need to force a move for mingw32.
+ return std::move(loaded_apk);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(bool for_loader) {
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "", -1, for_loader));
+ loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
+ // Need to force a move for mingw32.
+ return std::move(loaded_apk);
+}
+
std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
- CHECK(zip_handle_ != nullptr);
+ // If this is a resource loader from an .arsc, there will be no zip handle
+ if (zip_handle_ == nullptr) {
+ return {};
+ }
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
@@ -205,7 +268,10 @@ std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMod
bool ApkAssets::ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f) const {
- CHECK(zip_handle_ != nullptr);
+ // If this is a resource loader from an .arsc, there will be no zip handle
+ if (zip_handle_ == nullptr) {
+ return false;
+ }
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
@@ -252,6 +318,11 @@ bool ApkAssets::ForEachFile(const std::string& root_path,
}
bool ApkAssets::IsUpToDate() const {
+ // Loaders are invalidated by the app, not the system, so assume up to date
+ if (for_loader) {
+ return true;
+ }
+
return last_mod_time_ == getFileModDate(path_.c_str());
}
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 92125c9da8bb..c132f343713f 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -133,14 +133,24 @@ Asset::Asset(void)
*/
/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
{
+ return createFromFd(open(fileName, O_RDONLY | O_BINARY), fileName, mode);
+}
+
+/*
+ * Create a new Asset from a file on disk. There is a fair chance that
+ * the file doesn't actually exist.
+ *
+ * We can use "mode" to decide how we want to go about it.
+ */
+/*static*/ Asset* Asset::createFromFd(const int fd, const char* fileName, AccessMode mode)
+{
+ if (fd < 0) {
+ return NULL;
+ }
+
_FileAsset* pAsset;
status_t result;
off64_t length;
- int fd;
-
- fd = open(fileName, O_RDONLY | O_BINARY);
- if (fd < 0)
- return NULL;
/*
* Under Linux, the lseek fails if we actually opened a directory. To
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index eec49df79630..e914f37bcac4 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -493,8 +493,12 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
- // If the package is an overlay, then even configurations that are the same MUST be chosen.
+
+ // If the package is an overlay or custom loader,
+ // then even configurations that are the same MUST be chosen.
const bool package_is_overlay = loaded_package->IsOverlay();
+ const bool package_is_loader = loaded_package->IsCustomLoader();
+ const bool should_overlay = package_is_overlay || package_is_loader;
if (use_fast_path) {
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
@@ -508,10 +512,28 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
if (best_config == nullptr) {
resolution_type = Resolution::Step::Type::INITIAL;
} else if (this_config.isBetterThan(*best_config, desired_config)) {
- resolution_type = Resolution::Step::Type::BETTER_MATCH;
- } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
- resolution_type = Resolution::Step::Type::OVERLAID;
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH;
+ }
+ } else if (should_overlay && this_config.compare(*best_config) == 0) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+ } else if (package_is_overlay) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ }
} else {
+ if (resource_resolution_logging_enabled_) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::SKIPPED_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::SKIPPED;
+ }
+ resolution_steps.push_back(Resolution::Step{resolution_type,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
+ }
continue;
}
@@ -520,6 +542,16 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
const ResTable_type* type = filtered_group.types[i];
const uint32_t offset = LoadedPackage::GetEntryOffset(type, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
+ if (resource_resolution_logging_enabled_) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::NO_ENTRY_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::NO_ENTRY;
+ }
+ resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
+ }
continue;
}
@@ -554,9 +586,17 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
if (best_config == nullptr) {
resolution_type = Resolution::Step::Type::INITIAL;
} else if (this_config.isBetterThan(*best_config, desired_config)) {
- resolution_type = Resolution::Step::Type::BETTER_MATCH;
- } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
- resolution_type = Resolution::Step::Type::OVERLAID;
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH;
+ }
+ } else if (should_overlay && this_config.compare(*best_config) == 0) {
+ if (package_is_overlay) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ } else if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+ }
} else {
continue;
}
@@ -678,9 +718,27 @@ std::string AssetManager2::GetLastResourceResolution() const {
case Resolution::Step::Type::BETTER_MATCH:
prefix = "Found better";
break;
+ case Resolution::Step::Type::BETTER_MATCH_LOADER:
+ prefix = "Found better in loader";
+ break;
case Resolution::Step::Type::OVERLAID:
prefix = "Overlaid";
break;
+ case Resolution::Step::Type::OVERLAID_LOADER:
+ prefix = "Overlaid by loader";
+ break;
+ case Resolution::Step::Type::SKIPPED:
+ prefix = "Skipped";
+ break;
+ case Resolution::Step::Type::SKIPPED_LOADER:
+ prefix = "Skipped loader";
+ break;
+ case Resolution::Step::Type::NO_ENTRY:
+ prefix = "No entry";
+ break;
+ case Resolution::Step::Type::NO_ENTRY_LOADER:
+ prefix = "No entry for loader";
+ break;
}
if (!prefix.empty()) {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 72873abc6a42..882dc0d71759 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -401,7 +401,9 @@ const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap,
- bool system, bool load_as_shared_library) {
+ bool system,
+ bool load_as_shared_library,
+ bool for_loader) {
ATRACE_NAME("LoadedPackage::Load");
std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
@@ -430,6 +432,10 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
loaded_package->overlay_ = true;
}
+ if (for_loader) {
+ loaded_package->custom_loader_ = true;
+ }
+
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -696,7 +702,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
- bool load_as_shared_library) {
+ bool load_as_shared_library, bool for_loader) {
const ResTable_header* header = chunk.header<ResTable_header>();
if (header == nullptr) {
LOG(ERROR) << "RES_TABLE_TYPE too small.";
@@ -735,7 +741,11 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
packages_seen++;
std::unique_ptr<const LoadedPackage> loaded_package =
- LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
+ LoadedPackage::Load(child_chunk,
+ loaded_idmap,
+ system_,
+ load_as_shared_library,
+ for_loader);
if (!loaded_package) {
return false;
}
@@ -758,9 +768,11 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
}
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
- const LoadedIdmap* loaded_idmap, bool system,
- bool load_as_shared_library) {
- ATRACE_NAME("LoadedArsc::LoadTable");
+ const LoadedIdmap* loaded_idmap,
+ bool system,
+ bool load_as_shared_library,
+ bool for_loader) {
+ ATRACE_NAME("LoadedArsc::Load");
// Not using make_unique because the constructor is private.
std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
@@ -771,7 +783,10 @@ std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
const Chunk chunk = iter.Next();
switch (chunk.type()) {
case RES_TABLE_TYPE:
- if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
+ if (!loaded_arsc->LoadTable(chunk,
+ loaded_idmap,
+ load_as_shared_library,
+ for_loader)) {
return {};
}
break;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 49fc82bff11e..625b68207d83 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -40,7 +40,8 @@ class ApkAssets {
// Creates an ApkAssets.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
- static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
+ static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false,
+ bool for_loader = false);
// Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
// If `system` is true, the package is marked as a system package, and allows some functions to
@@ -63,7 +64,21 @@ class ApkAssets {
// If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd,
const std::string& friendly_name, bool system,
- bool force_shared_lib);
+ bool force_shared_lib,
+ bool for_loader = false);
+
+ // Creates an empty wrapper ApkAssets from the given path which points to an .arsc.
+ static std::unique_ptr<const ApkAssets> LoadArsc(const std::string& path,
+ bool for_loader = false);
+
+ // Creates an empty wrapper ApkAssets from the given file descriptor which points to an .arsc,
+ // Takes ownership of the file descriptor.
+ static std::unique_ptr<const ApkAssets> LoadArsc(base::unique_fd fd,
+ const std::string& friendly_name,
+ bool resource_loader = false);
+
+ // Creates a totally empty ApkAssets with no resources table and no file entries.
+ static std::unique_ptr<const ApkAssets> LoadEmpty(bool resource_loader = false);
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
@@ -86,24 +101,33 @@ class ApkAssets {
bool IsUpToDate() const;
+ // Creates an Asset from any file on the file system.
+ static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap,
- bool system, bool load_as_shared_library);
+ bool system, bool load_as_shared_library,
+ bool resource_loader = false);
- // Creates an Asset from any file on the file system.
- static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+ static std::unique_ptr<const ApkAssets> LoadArscImpl(base::unique_fd fd,
+ const std::string& path,
+ bool resource_loader = false);
- ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time);
+ ApkAssets(ZipArchiveHandle unmanaged_handle,
+ const std::string& path,
+ time_t last_mod_time,
+ bool for_loader = false);
- using ZipArchivePtr = std::unique_ptr<ZipArchive, void(*)(ZipArchiveHandle)>;
+ using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
ZipArchivePtr zip_handle_;
const std::string path_;
time_t last_mod_time_;
+ bool for_loader;
std::unique_ptr<Asset> resources_asset_;
std::unique_ptr<Asset> idmap_asset_;
std::unique_ptr<const LoadedArsc> loaded_arsc_;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 9d12a35395c9..053dbb7864c6 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -121,6 +121,11 @@ public:
*/
const char* getAssetSource(void) const { return mAssetSource.string(); }
+ /*
+ * Create the asset from a file descriptor.
+ */
+ static Asset* createFromFd(const int fd, const char* fileName, AccessMode mode);
+
protected:
/*
* Adds this Asset to the global Asset list for debugging and
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index de46081a6aa3..c7348b180648 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -382,7 +382,13 @@ class AssetManager2 {
enum class Type {
INITIAL,
BETTER_MATCH,
- OVERLAID
+ BETTER_MATCH_LOADER,
+ OVERLAID,
+ OVERLAID_LOADER,
+ SKIPPED,
+ SKIPPED_LOADER,
+ NO_ENTRY,
+ NO_ENTRY_LOADER,
};
// Marks what kind of override this step was.
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 950f5413f550..1a56876b9686 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -137,7 +137,8 @@ class LoadedPackage {
static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap, bool system,
- bool load_as_shared_library);
+ bool load_as_shared_library,
+ bool load_as_custom_loader);
~LoadedPackage();
@@ -187,6 +188,11 @@ class LoadedPackage {
return overlay_;
}
+ // Returns true if this package is a custom loader and should behave like an overlay
+ inline bool IsCustomLoader() const {
+ return custom_loader_;
+ }
+
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -260,6 +266,7 @@ class LoadedPackage {
bool dynamic_ = false;
bool system_ = false;
bool overlay_ = false;
+ bool custom_loader_ = false;
bool defines_overlayable_ = false;
ByteBucketArray<TypeSpecPtr> type_specs_;
@@ -282,7 +289,8 @@ class LoadedArsc {
static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
const LoadedIdmap* loaded_idmap = nullptr,
bool system = false,
- bool load_as_shared_library = false);
+ bool load_as_shared_library = false,
+ bool for_loader = false);
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<const LoadedArsc> CreateEmpty();
@@ -311,7 +319,19 @@ class LoadedArsc {
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
LoadedArsc() = default;
- bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
+ bool LoadTable(
+ const Chunk& chunk,
+ const LoadedIdmap* loaded_idmap,
+ bool load_as_shared_library,
+ bool for_loader
+ );
+
+ static std::unique_ptr<const LoadedArsc> LoadData(std::unique_ptr<LoadedArsc>& loaded_arsc,
+ const char* data,
+ size_t length,
+ const LoadedIdmap* loaded_idmap = nullptr,
+ bool load_as_shared_library = false,
+ bool for_loader = false);
ResStringPool global_string_pool_;
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d58e8d20c8aa..fd57a92c216b 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -25,6 +25,7 @@
#include "data/overlayable/R.h"
#include "data/sparse/R.h"
#include "data/styles/R.h"
+#include "data/system/R.h"
namespace app = com::android::app;
namespace basic = com::android::basic;
@@ -387,6 +388,39 @@ TEST(LoadedArscTest, GetOverlayableMap) {
ASSERT_EQ(map.at("OverlayableResources2"), "overlay://com.android.overlayable");
}
+TEST(LoadedArscTest, LoadCustomLoader) {
+ std::string contents;
+
+ std::unique_ptr<Asset>
+ asset = ApkAssets::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+
+ MockLoadedIdmap loaded_idmap;
+ const StringPiece data(
+ reinterpret_cast<const char*>(asset->getBuffer(true /*wordAligned*/)),
+ asset->getLength());
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc =
+ LoadedArsc::Load(data, nullptr, false, false, true);
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(android::R::string::cancel));
+ ASSERT_THAT(package, NotNull());
+ EXPECT_THAT(package->GetPackageName(), StrEq("android"));
+ EXPECT_THAT(package->GetPackageId(), Eq(0x01));
+
+ const uint8_t type_index = get_type_id(android::R::string::cancel) - 1;
+ const uint16_t entry_index = get_entry_id(android::R::string::cancel);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
+}
+
// structs with size fields (like Res_value, ResTable_entry) should be
// backwards and forwards compatible (aka checking the size field against
// sizeof(Res_value) might not be backwards compatible.
diff --git a/libs/androidfw/tests/data/loader/resources.arsc b/libs/androidfw/tests/data/loader/resources.arsc
new file mode 100644
index 000000000000..2c881f2cdfe5
--- /dev/null
+++ b/libs/androidfw/tests/data/loader/resources.arsc
Binary files differ
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
index becb38830fb3..374107484784 100644
--- a/libs/androidfw/tests/data/system/R.h
+++ b/libs/androidfw/tests/data/system/R.h
@@ -40,6 +40,12 @@ struct R {
number = 0x01030000, // sv
};
};
+
+ struct string {
+ enum : uint32_t {
+ cancel = 0x01040000,
+ };
+ };
};
} // namespace android
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9c36d76cf370..6824be8e1e3b 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -79,6 +80,8 @@ public class Location implements Parcelable {
*
* @hide
*/
+ @TestApi
+ @SystemApi
public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
/**
@@ -1214,8 +1217,9 @@ public class Location implements Parcelable {
* @param value the Location to attach
* @hide
*/
- @UnsupportedAppUsage
- public void setExtraLocation(String key, Location value) {
+ @TestApi
+ @SystemApi
+ public void setExtraLocation(@Nullable String key, @Nullable Location value) {
if (mExtras == null) {
mExtras = new Bundle();
}
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index f421029909bd..7ed431d4660d 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -1028,8 +1028,6 @@ public class MediaMetadataRetriever implements AutoCloseable {
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
* @see MediaFormat#COLOR_STANDARD_BT601_NTSC
* @see MediaFormat#COLOR_STANDARD_BT2020
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_STANDARD = 35;
@@ -1040,8 +1038,6 @@ public class MediaMetadataRetriever implements AutoCloseable {
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
* @see MediaFormat#COLOR_TRANSFER_ST2084
* @see MediaFormat#COLOR_TRANSFER_HLG
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_TRANSFER = 36;
@@ -1050,8 +1046,6 @@ public class MediaMetadataRetriever implements AutoCloseable {
*
* @see MediaFormat#COLOR_RANGE_LIMITED
* @see MediaFormat#COLOR_RANGE_FULL
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_RANGE = 37;
// Add more here...
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fb581b532dd2..a315c1eefb52 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -139,6 +139,12 @@ public class ThumbnailUtils {
/**
* Create a thumbnail for given audio file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The audio file.
* @param size The desired thumbnail size.
@@ -231,6 +237,12 @@ public class ThumbnailUtils {
/**
* Create a thumbnail for given image file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The audio file.
* @param size The desired thumbnail size.
@@ -334,6 +346,12 @@ public class ThumbnailUtils {
/**
* Create a thumbnail for given video file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The video file.
* @param size The desired thumbnail size.
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
new file mode 100644
index 000000000000..5ae4957b494d
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+
+ <style name="Theme.AlertDialogActivity.NoAnimation">
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+
+ <style name="Theme.AlertDialogActivity.NoActionBar"
+ parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a7e7f085ffd7..c6d051d74239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -29,6 +29,7 @@ import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import androidx.collection.ArraySet;
@@ -390,7 +391,12 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
private void updateRegionForNotch(Region region) {
- DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout();
+ WindowInsets windowInsets = mStatusBarWindowView.getRootWindowInsets();
+ if (windowInsets == null) {
+ Log.w(TAG, "StatusBarWindowView is not attached.");
+ return;
+ }
+ DisplayCutout cutout = windowInsets.getDisplayCutout();
if (cutout == null) {
return;
}
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
index 4ed581d6e915..a4bf2b045361 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
@@ -88,11 +88,8 @@ final class RuleEvaluator {
// NOT connector has only 1 formula attached.
return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata);
case AND:
- boolean result = true;
- for (Formula subFormula : openFormula.getFormulas()) {
- result &= isMatch(subFormula, appInstallMetadata);
- }
- return result;
+ return openFormula.getFormulas().stream().allMatch(
+ subFormula -> isMatch(subFormula, appInstallMetadata));
default:
Slog.i(TAG, String.format("Returned no match for unknown connector %s",
openFormula.getConnector()));
diff --git a/services/core/java/com/android/server/integrity/model/Rule.java b/services/core/java/com/android/server/integrity/model/Rule.java
index ff21d6f70fb1..63b9b911ff4f 100644
--- a/services/core/java/com/android/server/integrity/model/Rule.java
+++ b/services/core/java/com/android/server/integrity/model/Rule.java
@@ -78,8 +78,8 @@ public final class Rule {
return false;
}
Rule that = (Rule) o;
- return mFormula.equals(that.mFormula)
- && mEffect == that.mEffect;
+ return Objects.equals(mFormula, that.mFormula)
+ && Objects.equals(mEffect, that.mEffect);
}
@Override
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
index ee3c1b1be6d9..60732a3db59a 100644
--- a/telephony/java/android/telephony/ICellInfoCallback.aidl
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -16,7 +16,6 @@
package android.telephony;
-import android.os.ParcelableException;
import android.telephony.CellInfo;
import java.util.List;
@@ -28,5 +27,5 @@ import java.util.List;
oneway interface ICellInfoCallback
{
void onCellInfo(in List<CellInfo> state);
- void onError(in int errorCode, in ParcelableException detail);
+ void onError(in int errorCode, in String exceptionName, in String message);
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index f4330fa0b725..2d35f8eae816 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1814,6 +1814,36 @@ public final class SmsManager {
// SMS send failure result codes
+ /** @hide */
+ @IntDef(prefix = { "RESULT" }, value = {
+ RESULT_ERROR_NONE,
+ RESULT_ERROR_GENERIC_FAILURE,
+ RESULT_ERROR_RADIO_OFF,
+ RESULT_ERROR_NULL_PDU,
+ RESULT_ERROR_NO_SERVICE,
+ RESULT_ERROR_LIMIT_EXCEEDED,
+ RESULT_ERROR_FDN_CHECK_FAILURE,
+ RESULT_ERROR_SHORT_CODE_NOT_ALLOWED,
+ RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED,
+ RESULT_RADIO_NOT_AVAILABLE,
+ RESULT_NETWORK_REJECT,
+ RESULT_INVALID_ARGUMENTS,
+ RESULT_INVALID_STATE,
+ RESULT_NO_MEMORY,
+ RESULT_INVALID_SMS_FORMAT,
+ RESULT_SYSTEM_ERROR,
+ RESULT_MODEM_ERROR,
+ RESULT_NETWORK_ERROR,
+ RESULT_INVALID_SMSC_ADDRESS,
+ RESULT_OPERATION_NOT_ALLOWED,
+ RESULT_INTERNAL_ERROR,
+ RESULT_NO_RESOURCES,
+ RESULT_CANCELLED,
+ RESULT_REQUEST_NOT_SUPPORTED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Result {}
+
/**
* No error.
* @hide
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ee76a6f3992d..03e57e728610 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5570,18 +5570,20 @@ public class TelephonyManager {
telephony.requestCellInfoUpdate(
getSubId(),
new ICellInfoCallback.Stub() {
+ @Override
public void onCellInfo(List<CellInfo> cellInfo) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
- public void onError(int errorCode, android.os.ParcelableException detail) {
+ @Override
+ public void onError(int errorCode, String exceptionName, String message) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onError(
- errorCode, detail.getCause())));
+ errorCode,
+ createThrowableByClassName(exceptionName, message))));
}
}, getOpPackageName());
-
} catch (RemoteException ex) {
}
}
@@ -5610,21 +5612,36 @@ public class TelephonyManager {
telephony.requestCellInfoUpdateWithWorkSource(
getSubId(),
new ICellInfoCallback.Stub() {
+ @Override
public void onCellInfo(List<CellInfo> cellInfo) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
- public void onError(int errorCode, android.os.ParcelableException detail) {
+ @Override
+ public void onError(int errorCode, String exceptionName, String message) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onError(
- errorCode, detail.getCause())));
+ errorCode,
+ createThrowableByClassName(exceptionName, message))));
}
}, getOpPackageName(), workSource);
} catch (RemoteException ex) {
}
}
+ private static Throwable createThrowableByClassName(String className, String message) {
+ if (className == null) {
+ return null;
+ }
+ try {
+ Class<?> c = Class.forName(className);
+ return (Throwable) c.getConstructor(String.class).newInstance(message);
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ }
+ return new RuntimeException(className + ": " + message);
+ }
+
/**
* Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
* PhoneStateListener.onCellInfoChanged} will be invoked.
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 175769bd34e4..36ece958d501 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -17,6 +17,7 @@
package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.SystemApi;
import android.os.RemoteException;
import android.telephony.SmsManager;
@@ -148,14 +149,16 @@ public class ImsSmsImplBase {
*
* @param token unique token generated by the platform that should be used when triggering
* callbacks for this specific message.
- * @param messageRef the message reference.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param format the format of the message.
* @param smsc the Short Message Service Center address.
* @param isRetry whether it is a retry of an already attempted message or not.
* @param pdu PDU representing the contents of the message.
*/
- public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ public void sendSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @SmsMessage.Format String format, String smsc, boolean isRetry,
byte[] pdu) {
// Base implementation returns error. Should be overridden.
try {
@@ -172,14 +175,13 @@ public class ImsSmsImplBase {
* provider.
*
* @param token token provided in {@link #onSmsReceived(int, String, byte[])}
- * @param messageRef the message reference
- * @param result result of delivering the message. Valid values are:
- * {@link #DELIVER_STATUS_OK},
- * {@link #DELIVER_STATUS_ERROR_GENERIC},
- * {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
- * {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param result result of delivering the message.
*/
- public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ public void acknowledgeSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @DeliverStatusResult int result) {
Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
@@ -191,12 +193,13 @@ public class ImsSmsImplBase {
*
* @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
* or {@link #onSmsStatusReportReceived(int, String, byte[])}
- * @param messageRef the message reference
- * @param result result of delivering the message. Valid values are:
- * {@link #STATUS_REPORT_STATUS_OK},
- * {@link #STATUS_REPORT_STATUS_ERROR}
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param result result of delivering the message.
*/
- public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+ public void acknowledgeSmsReport(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @StatusReportResult int result) {
Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
}
@@ -210,12 +213,12 @@ public class ImsSmsImplBase {
* {@link #DELIVER_STATUS_ERROR_GENERIC} result code.
* @param token unique token generated by IMS providers that the platform will use to trigger
* callbacks for this message.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param format the format of the message.
* @param pdu PDU representing the contents of the message.
* @throws RuntimeException if called before {@link #onReady()} is triggered.
*/
- public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
+ public final void onSmsReceived(int token, @SmsMessage.Format String format, byte[] pdu)
+ throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -241,13 +244,16 @@ public class ImsSmsImplBase {
* sent successfully.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
*
* @throws RuntimeException if called before {@link #onReady()} is triggered or if the
* connection to the framework is not available. If this happens attempting to send the SMS
* should be aborted.
*/
- public final void onSendSmsResultSuccess(int token, int messageRef) throws RuntimeException {
+ public final void onSendSmsResultSuccess(int token,
+ @IntRange(from = 0, to = 65535) int messageRef) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -266,34 +272,11 @@ public class ImsSmsImplBase {
* to the platform.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
* @param status result of sending the SMS.
- * @param reason reason in case status is failure. Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
- * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
- * {@link SmsManager#RESULT_NETWORK_REJECT},
- * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
- * {@link SmsManager#RESULT_INVALID_STATE},
- * {@link SmsManager#RESULT_NO_MEMORY},
- * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
- * {@link SmsManager#RESULT_SYSTEM_ERROR},
- * {@link SmsManager#RESULT_MODEM_ERROR},
- * {@link SmsManager#RESULT_NETWORK_ERROR},
- * {@link SmsManager#RESULT_ENCODING_ERROR},
- * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
- * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
- * {@link SmsManager#RESULT_INTERNAL_ERROR},
- * {@link SmsManager#RESULT_NO_RESOURCES},
- * {@link SmsManager#RESULT_CANCELLED},
- * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+ * @param reason reason in case status is failure.
*
* @throws RuntimeException if called before {@link #onReady()} is triggered or if the
* connection to the framework is not available. If this happens attempting to send the SMS
@@ -303,8 +286,8 @@ public class ImsSmsImplBase {
* send result.
*/
@Deprecated
- public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
- int reason) throws RuntimeException {
+ public final void onSendSmsResult(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @SendStatusResult int status, @SmsManager.Result int reason) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -324,34 +307,10 @@ public class ImsSmsImplBase {
* network.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
* @param status result of sending the SMS.
- * @param reason Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
- * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
- * {@link SmsManager#RESULT_NETWORK_REJECT},
- * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
- * {@link SmsManager#RESULT_INVALID_STATE},
- * {@link SmsManager#RESULT_NO_MEMORY},
- * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
- * {@link SmsManager#RESULT_SYSTEM_ERROR},
- * {@link SmsManager#RESULT_MODEM_ERROR},
- * {@link SmsManager#RESULT_NETWORK_ERROR},
- * {@link SmsManager#RESULT_ENCODING_ERROR},
- * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
- * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
- * {@link SmsManager#RESULT_INTERNAL_ERROR},
- * {@link SmsManager#RESULT_NO_RESOURCES},
- * {@link SmsManager#RESULT_CANCELLED},
- * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
* @param networkErrorCode the error code reported by the carrier network if sending this SMS
* has resulted in an error or {@link #RESULT_NO_NETWORK_ERROR} if no network error was
* generated. See 3GPP TS 24.011 Section 7.3.4 for valid error codes and more information.
@@ -360,9 +319,9 @@ public class ImsSmsImplBase {
* connection to the framework is not available. If this happens attempting to send the SMS
* should be aborted.
*/
- public final void onSendSmsResultError(int token, int messageRef, @SendStatusResult int status,
- int reason, int networkErrorCode)
- throws RuntimeException {
+ public final void onSendSmsResultError(int token,
+ @IntRange(from = 0, to = 65535) int messageRef, @SendStatusResult int status,
+ @SmsManager.Result int reason, int networkErrorCode) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -384,9 +343,10 @@ public class ImsSmsImplBase {
* the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
* with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param format the format of the message.
* @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
*
@@ -394,7 +354,8 @@ public class ImsSmsImplBase {
* message reference.
*/
@Deprecated
- public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+ public final void onSmsStatusReportReceived(int token,
+ @IntRange(from = 0, to = 65535) int messageRef, @SmsMessage.Format String format,
byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
@@ -419,13 +380,12 @@ public class ImsSmsImplBase {
* with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @param token unique token generated by IMS providers that the platform will use to trigger
* callbacks for this message.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param format the format of the message.
* @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
*/
- public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
- throws RuntimeException {
+ public final void onSmsStatusReportReceived(int token, @SmsMessage.Format String format,
+ byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -450,13 +410,11 @@ public class ImsSmsImplBase {
}
/**
- * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
- * Provider.
+ * Returns the SMS format that the ImsService expects.
*
- * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @return The expected format of the SMS messages.
*/
- public String getSmsFormat() {
+ public @SmsMessage.Format String getSmsFormat() {
return SmsMessage.FORMAT_3GPP;
}