Merge "Switch to ConcurrentHashMap for CompatConfig" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c37aa97046
@@ -36,9 +36,9 @@ import com.android.server.compat.overrides.ChangeOverrides;
|
|||||||
import com.android.server.compat.overrides.OverrideValue;
|
import com.android.server.compat.overrides.OverrideValue;
|
||||||
import com.android.server.compat.overrides.RawOverrideValue;
|
import com.android.server.compat.overrides.RawOverrideValue;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the state of a single compatibility change.
|
* Represents the state of a single compatibility change.
|
||||||
@@ -82,8 +82,8 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
|
|
||||||
ChangeListener mListener = null;
|
ChangeListener mListener = null;
|
||||||
|
|
||||||
private Map<String, Boolean> mEvaluatedOverrides;
|
private ConcurrentHashMap<String, Boolean> mEvaluatedOverrides;
|
||||||
private Map<String, PackageOverride> mRawOverrides;
|
private ConcurrentHashMap<String, PackageOverride> mRawOverrides;
|
||||||
|
|
||||||
public CompatChange(long changeId) {
|
public CompatChange(long changeId) {
|
||||||
this(changeId, null, -1, -1, false, false, null, false);
|
this(changeId, null, -1, -1, false, false, null, false);
|
||||||
@@ -114,11 +114,11 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
description, overridable);
|
description, overridable);
|
||||||
|
|
||||||
// Initialize override maps.
|
// Initialize override maps.
|
||||||
mEvaluatedOverrides = new HashMap<>();
|
mEvaluatedOverrides = new ConcurrentHashMap<>();
|
||||||
mRawOverrides = new HashMap<>();
|
mRawOverrides = new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerListener(ChangeListener listener) {
|
synchronized void registerListener(ChangeListener listener) {
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Listener for change " + toString() + " already registered.");
|
"Listener for change " + toString() + " already registered.");
|
||||||
@@ -131,8 +131,6 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
* Force the enabled state of this change for a given package name. The change will only take
|
* Force the enabled state of this change for a given package name. The change will only take
|
||||||
* effect after that packages process is killed and restarted.
|
* effect after that packages process is killed and restarted.
|
||||||
*
|
*
|
||||||
* <p>Note, this method is not thread safe so callers must ensure thread safety.
|
|
||||||
*
|
|
||||||
* @param pname Package name to enable the change for.
|
* @param pname Package name to enable the change for.
|
||||||
* @param enabled Whether or not to enable the change.
|
* @param enabled Whether or not to enable the change.
|
||||||
*/
|
*/
|
||||||
@@ -155,14 +153,12 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
* Tentatively set the state of this change for a given package name.
|
* Tentatively set the state of this change for a given package name.
|
||||||
* The override will only take effect after that package is installed, if applicable.
|
* The override will only take effect after that package is installed, if applicable.
|
||||||
*
|
*
|
||||||
* <p>Note, this method is not thread safe so callers must ensure thread safety.
|
|
||||||
*
|
|
||||||
* @param packageName Package name to tentatively enable the change for.
|
* @param packageName Package name to tentatively enable the change for.
|
||||||
* @param override The package override to be set
|
* @param override The package override to be set
|
||||||
* @param allowedState Whether the override is allowed.
|
* @param allowedState Whether the override is allowed.
|
||||||
* @param versionCode The version code of the package.
|
* @param versionCode The version code of the package.
|
||||||
*/
|
*/
|
||||||
void addPackageOverride(String packageName, PackageOverride override,
|
synchronized void addPackageOverride(String packageName, PackageOverride override,
|
||||||
OverrideAllowedState allowedState, @Nullable Long versionCode) {
|
OverrideAllowedState allowedState, @Nullable Long versionCode) {
|
||||||
if (getLoggingOnly()) {
|
if (getLoggingOnly()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
@@ -185,12 +181,12 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
* @return {@code true} if the recheck yielded a result that requires invalidating caches
|
* @return {@code true} if the recheck yielded a result that requires invalidating caches
|
||||||
* (a deferred override was consolidated or a regular override was removed).
|
* (a deferred override was consolidated or a regular override was removed).
|
||||||
*/
|
*/
|
||||||
boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
|
synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
|
||||||
@Nullable Long versionCode) {
|
@Nullable Long versionCode) {
|
||||||
boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
|
boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
|
||||||
|
|
||||||
// If the app is not installed or no longer has raw overrides, evaluate to false
|
// If the app is not installed or no longer has raw overrides, evaluate to false
|
||||||
if (versionCode == null || !hasRawOverride(packageName) || !allowed) {
|
if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) {
|
||||||
removePackageOverrideInternal(packageName);
|
removePackageOverrideInternal(packageName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -211,10 +207,6 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPackageOverride(String pname) {
|
|
||||||
return mRawOverrides.containsKey(pname);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove any package override for the given package name, restoring the default behaviour.
|
* Remove any package override for the given package name, restoring the default behaviour.
|
||||||
*
|
*
|
||||||
@@ -224,9 +216,11 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
* @param allowedState Whether the override is allowed.
|
* @param allowedState Whether the override is allowed.
|
||||||
* @param versionCode The version code of the package.
|
* @param versionCode The version code of the package.
|
||||||
*/
|
*/
|
||||||
boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
|
synchronized boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
|
||||||
@Nullable Long versionCode) {
|
@Nullable Long versionCode) {
|
||||||
if (mRawOverrides.remove(pname) != null) {
|
if (mRawOverrides.containsKey(pname)) {
|
||||||
|
allowedState.enforce(getId(), pname);
|
||||||
|
mRawOverrides.remove(pname);
|
||||||
recheckOverride(pname, allowedState, versionCode);
|
recheckOverride(pname, allowedState, versionCode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -244,8 +238,11 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
if (app == null) {
|
if (app == null) {
|
||||||
return defaultValue();
|
return defaultValue();
|
||||||
}
|
}
|
||||||
if (mEvaluatedOverrides.containsKey(app.packageName)) {
|
if (app.packageName != null) {
|
||||||
return mEvaluatedOverrides.get(app.packageName);
|
final Boolean enabled = mEvaluatedOverrides.get(app.packageName);
|
||||||
|
if (enabled != null) {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (getDisabled()) {
|
if (getDisabled()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -269,9 +266,9 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
* @return {@code true} if the change should be enabled for the package.
|
* @return {@code true} if the change should be enabled for the package.
|
||||||
*/
|
*/
|
||||||
boolean willBeEnabled(String packageName) {
|
boolean willBeEnabled(String packageName) {
|
||||||
if (hasRawOverride(packageName)) {
|
final PackageOverride override = mRawOverrides.get(packageName);
|
||||||
int eval = mRawOverrides.get(packageName).evaluateForAllVersions();
|
if (override != null) {
|
||||||
switch (eval) {
|
switch (override.evaluateForAllVersions()) {
|
||||||
case VALUE_ENABLED:
|
case VALUE_ENABLED:
|
||||||
return true;
|
return true;
|
||||||
case VALUE_DISABLED:
|
case VALUE_DISABLED:
|
||||||
@@ -292,30 +289,12 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
return !getDisabled();
|
return !getDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
synchronized void clearOverrides() {
|
||||||
* Checks whether a change has an override for a package.
|
|
||||||
* @param packageName name of the package
|
|
||||||
* @return true if there is such override
|
|
||||||
*/
|
|
||||||
private boolean hasOverride(String packageName) {
|
|
||||||
return mEvaluatedOverrides.containsKey(packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a change has a deferred override for a package.
|
|
||||||
* @param packageName name of the package
|
|
||||||
* @return true if there is such a deferred override
|
|
||||||
*/
|
|
||||||
private boolean hasRawOverride(String packageName) {
|
|
||||||
return mRawOverrides.containsKey(packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearOverrides() {
|
|
||||||
mRawOverrides.clear();
|
mRawOverrides.clear();
|
||||||
mEvaluatedOverrides.clear();
|
mEvaluatedOverrides.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadOverrides(ChangeOverrides changeOverrides) {
|
synchronized void loadOverrides(ChangeOverrides changeOverrides) {
|
||||||
// Load deferred overrides for backwards compatibility
|
// Load deferred overrides for backwards compatibility
|
||||||
if (changeOverrides.getDeferred() != null) {
|
if (changeOverrides.getDeferred() != null) {
|
||||||
for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
|
for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
|
||||||
@@ -348,7 +327,7 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeOverrides saveOverrides() {
|
synchronized ChangeOverrides saveOverrides() {
|
||||||
if (mRawOverrides.isEmpty()) {
|
if (mRawOverrides.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -406,7 +385,7 @@ public final class CompatChange extends CompatibilityChangeInfo {
|
|||||||
return sb.append(")").toString();
|
return sb.append(")").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListener(String packageName) {
|
private synchronized void notifyListener(String packageName) {
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
mListener.onCompatChange(packageName);
|
mListener.onCompatChange(packageName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import android.content.pm.PackageManager;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.LongArray;
|
import android.util.LongArray;
|
||||||
import android.util.LongSparseArray;
|
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
@@ -55,11 +54,12 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.xml.datatype.DatatypeConfigurationException;
|
import javax.xml.datatype.DatatypeConfigurationException;
|
||||||
|
|
||||||
@@ -76,9 +76,7 @@ final class CompatConfig {
|
|||||||
private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
|
private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
|
||||||
private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
|
private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
|
||||||
|
|
||||||
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
|
private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>();
|
||||||
@GuardedBy("mReadWriteLock")
|
|
||||||
private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
|
|
||||||
|
|
||||||
private final OverrideValidatorImpl mOverrideValidator;
|
private final OverrideValidatorImpl mOverrideValidator;
|
||||||
private final AndroidBuildClassifier mAndroidBuildClassifier;
|
private final AndroidBuildClassifier mAndroidBuildClassifier;
|
||||||
@@ -113,21 +111,13 @@ final class CompatConfig {
|
|||||||
/**
|
/**
|
||||||
* Adds a change.
|
* Adds a change.
|
||||||
*
|
*
|
||||||
* <p>This is intended to be used by code that reads change config from the filesystem. This
|
* <p>This is intended to be used by unit tests only.
|
||||||
* should be done at system startup time.
|
|
||||||
*
|
|
||||||
* <p>Any change with the same ID will be overwritten.
|
|
||||||
*
|
*
|
||||||
* @param change the change to add
|
* @param change the change to add
|
||||||
*/
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
void addChange(CompatChange change) {
|
void addChange(CompatChange change) {
|
||||||
mReadWriteLock.writeLock().lock();
|
mChanges.put(change.getId(), change);
|
||||||
try {
|
|
||||||
mChanges.put(change.getId(), change);
|
|
||||||
invalidateCache();
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,20 +133,14 @@ final class CompatConfig {
|
|||||||
*/
|
*/
|
||||||
long[] getDisabledChanges(ApplicationInfo app) {
|
long[] getDisabledChanges(ApplicationInfo app) {
|
||||||
LongArray disabled = new LongArray();
|
LongArray disabled = new LongArray();
|
||||||
mReadWriteLock.readLock().lock();
|
for (CompatChange c : mChanges.values()) {
|
||||||
try {
|
if (!c.isEnabled(app, mAndroidBuildClassifier)) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
disabled.add(c.getId());
|
||||||
CompatChange c = mChanges.valueAt(i);
|
|
||||||
if (!c.isEnabled(app, mAndroidBuildClassifier)) {
|
|
||||||
disabled.add(c.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
// Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray
|
final long[] sortedChanges = disabled.toArray();
|
||||||
// (mChanges) ensures it's already sorted.
|
Arrays.sort(sortedChanges);
|
||||||
return disabled.toArray();
|
return sortedChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,15 +150,10 @@ final class CompatConfig {
|
|||||||
* @return the change ID, or {@code -1} if no change with that name exists
|
* @return the change ID, or {@code -1} if no change with that name exists
|
||||||
*/
|
*/
|
||||||
long lookupChangeId(String name) {
|
long lookupChangeId(String name) {
|
||||||
mReadWriteLock.readLock().lock();
|
for (CompatChange c : mChanges.values()) {
|
||||||
try {
|
if (TextUtils.equals(c.getName(), name)) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
return c.getId();
|
||||||
if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) {
|
|
||||||
return mChanges.keyAt(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -188,17 +167,12 @@ final class CompatConfig {
|
|||||||
* change ID is not known, as unknown changes are enabled by default.
|
* change ID is not known, as unknown changes are enabled by default.
|
||||||
*/
|
*/
|
||||||
boolean isChangeEnabled(long changeId, ApplicationInfo app) {
|
boolean isChangeEnabled(long changeId, ApplicationInfo app) {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
if (c == null) {
|
||||||
CompatChange c = mChanges.get(changeId);
|
// we know nothing about this change: default behaviour is enabled.
|
||||||
if (c == null) {
|
return true;
|
||||||
// we know nothing about this change: default behaviour is enabled.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return c.isEnabled(app, mAndroidBuildClassifier);
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
|
return c.isEnabled(app, mAndroidBuildClassifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,17 +184,12 @@ final class CompatConfig {
|
|||||||
* {@code true} if the change ID is not known, as unknown changes are enabled by default.
|
* {@code true} if the change ID is not known, as unknown changes are enabled by default.
|
||||||
*/
|
*/
|
||||||
boolean willChangeBeEnabled(long changeId, String packageName) {
|
boolean willChangeBeEnabled(long changeId, String packageName) {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
if (c == null) {
|
||||||
CompatChange c = mChanges.get(changeId);
|
// we know nothing about this change: default behaviour is enabled.
|
||||||
if (c == null) {
|
return true;
|
||||||
// we know nothing about this change: default behaviour is enabled.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return c.willBeEnabled(packageName);
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
|
return c.willBeEnabled(packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -239,7 +208,7 @@ final class CompatConfig {
|
|||||||
* @return {@code true} if the change existed before adding the override
|
* @return {@code true} if the change existed before adding the override
|
||||||
* @throws IllegalStateException if overriding is not allowed
|
* @throws IllegalStateException if overriding is not allowed
|
||||||
*/
|
*/
|
||||||
boolean addOverride(long changeId, String packageName, boolean enabled) {
|
synchronized boolean addOverride(long changeId, String packageName, boolean enabled) {
|
||||||
boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
|
boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
|
||||||
new PackageOverride.Builder().setEnabled(enabled).build());
|
new PackageOverride.Builder().setEnabled(enabled).build());
|
||||||
saveOverrides();
|
saveOverrides();
|
||||||
@@ -250,12 +219,11 @@ final class CompatConfig {
|
|||||||
/**
|
/**
|
||||||
* Overrides the enabled state for a given change and app.
|
* Overrides the enabled state for a given change and app.
|
||||||
*
|
*
|
||||||
* <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
|
|
||||||
*
|
*
|
||||||
* @param overrides list of overrides to default changes config.
|
* @param overrides list of overrides to default changes config.
|
||||||
* @param packageName app for which the overrides will be applied.
|
* @param packageName app for which the overrides will be applied.
|
||||||
*/
|
*/
|
||||||
void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
|
synchronized void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
|
||||||
for (Long changeId : overrides.overrides.keySet()) {
|
for (Long changeId : overrides.overrides.keySet()) {
|
||||||
addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
|
addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
|
||||||
}
|
}
|
||||||
@@ -265,36 +233,24 @@ final class CompatConfig {
|
|||||||
|
|
||||||
private boolean addOverrideUnsafe(long changeId, String packageName,
|
private boolean addOverrideUnsafe(long changeId, String packageName,
|
||||||
PackageOverride overrides) {
|
PackageOverride overrides) {
|
||||||
boolean alreadyKnown = true;
|
final AtomicBoolean alreadyKnown = new AtomicBoolean(true);
|
||||||
OverrideAllowedState allowedState =
|
OverrideAllowedState allowedState =
|
||||||
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
|
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
|
||||||
allowedState.enforce(changeId, packageName);
|
allowedState.enforce(changeId, packageName);
|
||||||
Long versionCode = getVersionCodeOrNull(packageName);
|
Long versionCode = getVersionCodeOrNull(packageName);
|
||||||
mReadWriteLock.writeLock().lock();
|
|
||||||
try {
|
final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> {
|
||||||
CompatChange c = mChanges.get(changeId);
|
alreadyKnown.set(false);
|
||||||
if (c == null) {
|
return new CompatChange(changeId);
|
||||||
alreadyKnown = false;
|
});
|
||||||
c = new CompatChange(changeId);
|
c.addPackageOverride(packageName, overrides, allowedState, versionCode);
|
||||||
addChange(c);
|
invalidateCache();
|
||||||
}
|
return alreadyKnown.get();
|
||||||
c.addPackageOverride(packageName, overrides, allowedState, versionCode);
|
|
||||||
invalidateCache();
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
return alreadyKnown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks whether the change is known to the compat config. */
|
/** Checks whether the change is known to the compat config. */
|
||||||
boolean isKnownChangeId(long changeId) {
|
boolean isKnownChangeId(long changeId) {
|
||||||
mReadWriteLock.readLock().lock();
|
return mChanges.containsKey(changeId);
|
||||||
try {
|
|
||||||
CompatChange c = mChanges.get(changeId);
|
|
||||||
return c != null;
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -302,55 +258,35 @@ final class CompatConfig {
|
|||||||
* target SDK gated).
|
* target SDK gated).
|
||||||
*/
|
*/
|
||||||
int maxTargetSdkForChangeIdOptIn(long changeId) {
|
int maxTargetSdkForChangeIdOptIn(long changeId) {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
if (c != null && c.getEnableSinceTargetSdk() != -1) {
|
||||||
CompatChange c = mChanges.get(changeId);
|
return c.getEnableSinceTargetSdk() - 1;
|
||||||
if (c != null && c.getEnableSinceTargetSdk() != -1) {
|
|
||||||
return c.getEnableSinceTargetSdk() - 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the change is marked as logging only.
|
* Returns whether the change is marked as logging only.
|
||||||
*/
|
*/
|
||||||
boolean isLoggingOnly(long changeId) {
|
boolean isLoggingOnly(long changeId) {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
return c != null && c.getLoggingOnly();
|
||||||
CompatChange c = mChanges.get(changeId);
|
|
||||||
return c != null && c.getLoggingOnly();
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the change is marked as disabled.
|
* Returns whether the change is marked as disabled.
|
||||||
*/
|
*/
|
||||||
boolean isDisabled(long changeId) {
|
boolean isDisabled(long changeId) {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
return c != null && c.getDisabled();
|
||||||
CompatChange c = mChanges.get(changeId);
|
|
||||||
return c != null && c.getDisabled();
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the change is overridable.
|
* Returns whether the change is overridable.
|
||||||
*/
|
*/
|
||||||
boolean isOverridable(long changeId) {
|
boolean isOverridable(long changeId) {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
return c != null && c.getOverridable();
|
||||||
CompatChange c = mChanges.get(changeId);
|
|
||||||
return c != null && c.getOverridable();
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,10 +299,12 @@ final class CompatConfig {
|
|||||||
* @param packageName the app package name that was overridden
|
* @param packageName the app package name that was overridden
|
||||||
* @return {@code true} if an override existed;
|
* @return {@code true} if an override existed;
|
||||||
*/
|
*/
|
||||||
boolean removeOverride(long changeId, String packageName) {
|
synchronized boolean removeOverride(long changeId, String packageName) {
|
||||||
boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
|
boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
|
||||||
saveOverrides();
|
if (overrideExists) {
|
||||||
invalidateCache();
|
saveOverrides();
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
return overrideExists;
|
return overrideExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,14 +314,9 @@ final class CompatConfig {
|
|||||||
*/
|
*/
|
||||||
private boolean removeOverrideUnsafe(long changeId, String packageName) {
|
private boolean removeOverrideUnsafe(long changeId, String packageName) {
|
||||||
Long versionCode = getVersionCodeOrNull(packageName);
|
Long versionCode = getVersionCodeOrNull(packageName);
|
||||||
mReadWriteLock.writeLock().lock();
|
CompatChange c = mChanges.get(changeId);
|
||||||
try {
|
if (c != null) {
|
||||||
CompatChange c = mChanges.get(changeId);
|
return removeOverrideUnsafe(c, packageName, versionCode);
|
||||||
if (c != null) {
|
|
||||||
return removeOverrideUnsafe(c, packageName, versionCode);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -397,13 +330,7 @@ final class CompatConfig {
|
|||||||
long changeId = change.getId();
|
long changeId = change.getId();
|
||||||
OverrideAllowedState allowedState =
|
OverrideAllowedState allowedState =
|
||||||
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
|
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
|
||||||
if (change.hasPackageOverride(packageName)) {
|
return change.removePackageOverride(packageName, allowedState, versionCode);
|
||||||
allowedState.enforce(changeId, packageName);
|
|
||||||
change.removePackageOverride(packageName, allowedState, versionCode);
|
|
||||||
invalidateCache();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -414,19 +341,16 @@ final class CompatConfig {
|
|||||||
*
|
*
|
||||||
* @param packageName the package for which the overrides should be purged
|
* @param packageName the package for which the overrides should be purged
|
||||||
*/
|
*/
|
||||||
void removePackageOverrides(String packageName) {
|
synchronized void removePackageOverrides(String packageName) {
|
||||||
Long versionCode = getVersionCodeOrNull(packageName);
|
Long versionCode = getVersionCodeOrNull(packageName);
|
||||||
mReadWriteLock.writeLock().lock();
|
boolean shouldInvalidateCache = false;
|
||||||
try {
|
for (CompatChange change : mChanges.values()) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
shouldInvalidateCache |= removeOverrideUnsafe(change, packageName, versionCode);
|
||||||
CompatChange change = mChanges.valueAt(i);
|
}
|
||||||
removeOverrideUnsafe(change, packageName, versionCode);
|
if (shouldInvalidateCache) {
|
||||||
}
|
saveOverrides();
|
||||||
} finally {
|
invalidateCache();
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
}
|
||||||
saveOverrides();
|
|
||||||
invalidateCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -439,34 +363,31 @@ final class CompatConfig {
|
|||||||
* @param overridesToRemove list of change IDs for which to restore the default behaviour.
|
* @param overridesToRemove list of change IDs for which to restore the default behaviour.
|
||||||
* @param packageName the package for which the overrides should be purged
|
* @param packageName the package for which the overrides should be purged
|
||||||
*/
|
*/
|
||||||
void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
|
synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
|
||||||
String packageName) {
|
String packageName) {
|
||||||
|
boolean shouldInvalidateCache = false;
|
||||||
for (Long changeId : overridesToRemove.changeIds) {
|
for (Long changeId : overridesToRemove.changeIds) {
|
||||||
removeOverrideUnsafe(changeId, packageName);
|
shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName);
|
||||||
|
}
|
||||||
|
if (shouldInvalidateCache) {
|
||||||
|
saveOverrides();
|
||||||
|
invalidateCache();
|
||||||
}
|
}
|
||||||
saveOverrides();
|
|
||||||
invalidateCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
|
private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
|
||||||
int targetSdkVersion) {
|
int targetSdkVersion) {
|
||||||
LongArray allowed = new LongArray();
|
LongArray allowed = new LongArray();
|
||||||
mReadWriteLock.readLock().lock();
|
for (CompatChange change : mChanges.values()) {
|
||||||
try {
|
if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
continue;
|
||||||
CompatChange change = mChanges.valueAt(i);
|
}
|
||||||
if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
|
OverrideAllowedState allowedState =
|
||||||
continue;
|
mOverrideValidator.getOverrideAllowedState(change.getId(),
|
||||||
}
|
packageName);
|
||||||
OverrideAllowedState allowedState =
|
if (allowedState.state == OverrideAllowedState.ALLOWED) {
|
||||||
mOverrideValidator.getOverrideAllowedState(change.getId(),
|
allowed.add(change.getId());
|
||||||
packageName);
|
|
||||||
if (allowedState.state == OverrideAllowedState.ALLOWED) {
|
|
||||||
allowed.add(change.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
return allowed.toArray();
|
return allowed.toArray();
|
||||||
}
|
}
|
||||||
@@ -479,12 +400,15 @@ final class CompatConfig {
|
|||||||
*/
|
*/
|
||||||
int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
|
int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
|
||||||
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
|
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
|
||||||
|
boolean shouldInvalidateCache = false;
|
||||||
for (long changeId : changes) {
|
for (long changeId : changes) {
|
||||||
addOverrideUnsafe(changeId, packageName,
|
shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName,
|
||||||
new PackageOverride.Builder().setEnabled(true).build());
|
new PackageOverride.Builder().setEnabled(true).build());
|
||||||
}
|
}
|
||||||
saveOverrides();
|
if (shouldInvalidateCache) {
|
||||||
invalidateCache();
|
saveOverrides();
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
return changes.length;
|
return changes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,30 +420,27 @@ final class CompatConfig {
|
|||||||
*/
|
*/
|
||||||
int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
|
int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
|
||||||
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
|
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
|
||||||
|
boolean shouldInvalidateCache = false;
|
||||||
for (long changeId : changes) {
|
for (long changeId : changes) {
|
||||||
addOverrideUnsafe(changeId, packageName,
|
shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName,
|
||||||
new PackageOverride.Builder().setEnabled(false).build());
|
new PackageOverride.Builder().setEnabled(false).build());
|
||||||
}
|
}
|
||||||
saveOverrides();
|
if (shouldInvalidateCache) {
|
||||||
invalidateCache();
|
saveOverrides();
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
return changes.length;
|
return changes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
|
boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
|
||||||
boolean alreadyKnown = true;
|
final AtomicBoolean alreadyKnown = new AtomicBoolean(true);
|
||||||
mReadWriteLock.writeLock().lock();
|
final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> {
|
||||||
try {
|
alreadyKnown.set(false);
|
||||||
CompatChange c = mChanges.get(changeId);
|
invalidateCache();
|
||||||
if (c == null) {
|
return new CompatChange(changeId);
|
||||||
alreadyKnown = false;
|
});
|
||||||
c = new CompatChange(changeId);
|
c.registerListener(listener);
|
||||||
addChange(c);
|
return alreadyKnown.get();
|
||||||
}
|
|
||||||
c.registerListener(listener);
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
return alreadyKnown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean defaultChangeIdValue(long changeId) {
|
boolean defaultChangeIdValue(long changeId) {
|
||||||
@@ -537,12 +458,7 @@ final class CompatConfig {
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void clearChanges() {
|
void clearChanges() {
|
||||||
mReadWriteLock.writeLock().lock();
|
mChanges.clear();
|
||||||
try {
|
|
||||||
mChanges.clear();
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -551,18 +467,12 @@ final class CompatConfig {
|
|||||||
* @param pw {@link PrintWriter} instance to which the information will be dumped
|
* @param pw {@link PrintWriter} instance to which the information will be dumped
|
||||||
*/
|
*/
|
||||||
void dumpConfig(PrintWriter pw) {
|
void dumpConfig(PrintWriter pw) {
|
||||||
mReadWriteLock.readLock().lock();
|
if (mChanges.size() == 0) {
|
||||||
try {
|
pw.println("No compat overrides.");
|
||||||
if (mChanges.size() == 0) {
|
return;
|
||||||
pw.println("No compat overrides.");
|
}
|
||||||
return;
|
for (CompatChange c : mChanges.values()) {
|
||||||
}
|
pw.println(c.toString());
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
|
||||||
CompatChange c = mChanges.valueAt(i);
|
|
||||||
pw.println(c.toString());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,18 +484,12 @@ final class CompatConfig {
|
|||||||
CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) {
|
CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) {
|
||||||
Set<Long> enabled = new HashSet<>();
|
Set<Long> enabled = new HashSet<>();
|
||||||
Set<Long> disabled = new HashSet<>();
|
Set<Long> disabled = new HashSet<>();
|
||||||
mReadWriteLock.readLock().lock();
|
for (CompatChange c : mChanges.values()) {
|
||||||
try {
|
if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
enabled.add(c.getId());
|
||||||
CompatChange c = mChanges.valueAt(i);
|
} else {
|
||||||
if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) {
|
disabled.add(c.getId());
|
||||||
enabled.add(c.getId());
|
|
||||||
} else {
|
|
||||||
disabled.add(c.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled));
|
return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled));
|
||||||
}
|
}
|
||||||
@@ -596,17 +500,12 @@ final class CompatConfig {
|
|||||||
* @return an array of {@link CompatibilityChangeInfo} with the current changes
|
* @return an array of {@link CompatibilityChangeInfo} with the current changes
|
||||||
*/
|
*/
|
||||||
CompatibilityChangeInfo[] dumpChanges() {
|
CompatibilityChangeInfo[] dumpChanges() {
|
||||||
mReadWriteLock.readLock().lock();
|
CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()];
|
||||||
try {
|
int i = 0;
|
||||||
CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()];
|
for (CompatChange change : mChanges.values()) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
changeInfos[i++] = new CompatibilityChangeInfo(change);
|
||||||
CompatChange change = mChanges.valueAt(i);
|
|
||||||
changeInfos[i] = new CompatibilityChangeInfo(change);
|
|
||||||
}
|
|
||||||
return changeInfos;
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
|
return changeInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initConfigFromLib(File libraryDir) {
|
void initConfigFromLib(File libraryDir) {
|
||||||
@@ -626,10 +525,12 @@ final class CompatConfig {
|
|||||||
Config config = com.android.server.compat.config.XmlParser.read(in);
|
Config config = com.android.server.compat.config.XmlParser.read(in);
|
||||||
for (Change change : config.getCompatChange()) {
|
for (Change change : config.getCompatChange()) {
|
||||||
Slog.d(TAG, "Adding: " + change.toString());
|
Slog.d(TAG, "Adding: " + change.toString());
|
||||||
addChange(new CompatChange(change));
|
mChanges.put(change.getId(), new CompatChange(change));
|
||||||
}
|
}
|
||||||
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
|
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
|
||||||
Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e);
|
Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e);
|
||||||
|
} finally {
|
||||||
|
invalidateCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,15 +542,12 @@ final class CompatConfig {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void initOverrides(File dynamicOverridesFile, File staticOverridesFile) {
|
void initOverrides(File dynamicOverridesFile, File staticOverridesFile) {
|
||||||
// Clear overrides from all changes before loading.
|
// Clear overrides from all changes before loading.
|
||||||
mReadWriteLock.writeLock().lock();
|
|
||||||
try {
|
for (CompatChange c : mChanges.values()) {
|
||||||
for (int i = 0; i < mChanges.size(); ++i) {
|
c.clearOverrides();
|
||||||
mChanges.valueAt(i).clearOverrides();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.writeLock().unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loadOverrides(staticOverridesFile);
|
loadOverrides(staticOverridesFile);
|
||||||
|
|
||||||
mOverridesFile = dynamicOverridesFile;
|
mOverridesFile = dynamicOverridesFile;
|
||||||
@@ -698,18 +596,12 @@ final class CompatConfig {
|
|||||||
}
|
}
|
||||||
synchronized (mOverridesFile) {
|
synchronized (mOverridesFile) {
|
||||||
Overrides overrides = new Overrides();
|
Overrides overrides = new Overrides();
|
||||||
mReadWriteLock.readLock().lock();
|
List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
|
||||||
try {
|
for (CompatChange c : mChanges.values()) {
|
||||||
List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
|
ChangeOverrides changeOverrides = c.saveOverrides();
|
||||||
for (int idx = 0; idx < mChanges.size(); ++idx) {
|
if (changeOverrides != null) {
|
||||||
CompatChange c = mChanges.valueAt(idx);
|
changeOverridesList.add(changeOverrides);
|
||||||
ChangeOverrides changeOverrides = c.saveOverrides();
|
|
||||||
if (changeOverrides != null) {
|
|
||||||
changeOverridesList.add(changeOverrides);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
// Create the file if it doesn't already exist
|
// Create the file if it doesn't already exist
|
||||||
try {
|
try {
|
||||||
@@ -741,20 +633,11 @@ final class CompatConfig {
|
|||||||
void recheckOverrides(String packageName) {
|
void recheckOverrides(String packageName) {
|
||||||
Long versionCode = getVersionCodeOrNull(packageName);
|
Long versionCode = getVersionCodeOrNull(packageName);
|
||||||
boolean shouldInvalidateCache = false;
|
boolean shouldInvalidateCache = false;
|
||||||
mReadWriteLock.readLock().lock();
|
for (CompatChange c : mChanges.values()) {
|
||||||
try {
|
OverrideAllowedState allowedState =
|
||||||
for (int idx = 0; idx < mChanges.size(); ++idx) {
|
mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(),
|
||||||
CompatChange c = mChanges.valueAt(idx);
|
packageName);
|
||||||
if (!c.hasPackageOverride(packageName)) {
|
shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
OverrideAllowedState allowedState =
|
|
||||||
mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(),
|
|
||||||
packageName);
|
|
||||||
shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mReadWriteLock.readLock().unlock();
|
|
||||||
}
|
}
|
||||||
if (shouldInvalidateCache) {
|
if (shouldInvalidateCache) {
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
|
|||||||
Reference in New Issue
Block a user