blob: 592ec09aa730c7149941f534330e18182d8a6cbe [file] [log] [blame]
/*
* 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;
this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
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();
}
}
}