356 lines
11 KiB
Java
356 lines
11 KiB
Java
/*
|
|
* 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.compat;
|
|
|
|
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
|
|
|
import android.annotation.SystemApi;
|
|
import android.compat.annotation.ChangeId;
|
|
|
|
import libcore.api.IntraCoreApi;
|
|
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import libcore.util.NonNull;
|
|
|
|
/**
|
|
* Internal APIs for logging and gating compatibility changes.
|
|
*
|
|
* @see ChangeId
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public final class Compatibility {
|
|
|
|
private Compatibility() {}
|
|
|
|
/**
|
|
* Reports that a compatibility change is affecting the current process now.
|
|
*
|
|
* <p>Calls to this method from a non-app process are ignored. This allows code implementing
|
|
* APIs that are used by apps and by other code (e.g. the system server) to report changes
|
|
* regardless of the process it's running in. When called in a non-app process, this method is
|
|
* a no-op.
|
|
*
|
|
* <p>Note: for changes that are gated using {@link #isChangeEnabled(long)}, you do not need to
|
|
* call this API directly. The change will be reported for you in the case that
|
|
* {@link #isChangeEnabled(long)} returns {@code true}.
|
|
*
|
|
* @param changeId The ID of the compatibility change taking effect.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public static void reportUnconditionalChange(@ChangeId long changeId) {
|
|
sCallbacks.onChangeReported(changeId);
|
|
}
|
|
|
|
/**
|
|
* Query if a given compatibility change is enabled for the current process. This method should
|
|
* only be called by code running inside a process of the affected app.
|
|
*
|
|
* <p>If this method returns {@code true}, the calling code should implement the compatibility
|
|
* change, resulting in differing behaviour compared to earlier releases. If this method returns
|
|
* {@code false}, the calling code should behave as it did in earlier releases.
|
|
*
|
|
* <p>When this method returns {@code true}, it will also report the change as
|
|
* {@link #reportUnconditionalChange(long)} would, so there is no need to call that method
|
|
* directly.
|
|
*
|
|
* @param changeId The ID of the compatibility change in question.
|
|
* @return {@code true} if the change is enabled for the current app.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public static boolean isChangeEnabled(@ChangeId long changeId) {
|
|
return sCallbacks.isChangeEnabled(changeId);
|
|
}
|
|
|
|
private static final BehaviorChangeDelegate DEFAULT_CALLBACKS = new BehaviorChangeDelegate(){};
|
|
|
|
private volatile static BehaviorChangeDelegate sCallbacks = DEFAULT_CALLBACKS;
|
|
|
|
/**
|
|
* Sets the behavior change delegate.
|
|
*
|
|
* All changes reported via the {@link Compatibility} class will be forwarded to this class.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
public static void setBehaviorChangeDelegate(BehaviorChangeDelegate callbacks) {
|
|
sCallbacks = Objects.requireNonNull(callbacks);
|
|
}
|
|
|
|
/**
|
|
* Removes a behavior change delegate previously set via {@link #setBehaviorChangeDelegate}.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
public static void clearBehaviorChangeDelegate() {
|
|
sCallbacks = DEFAULT_CALLBACKS;
|
|
}
|
|
|
|
/**
|
|
* Return the behavior change delegate
|
|
*
|
|
* @hide
|
|
*/
|
|
// VisibleForTesting
|
|
@NonNull
|
|
public static BehaviorChangeDelegate getBehaviorChangeDelegate() {
|
|
return sCallbacks;
|
|
}
|
|
|
|
/**
|
|
* For use by tests only. Causes values from {@code overrides} to be returned instead of the
|
|
* real value.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
public static void setOverrides(ChangeConfig overrides) {
|
|
// Setting overrides twice in a row does not need to be supported because
|
|
// this method is only for enabling/disabling changes for the duration of
|
|
// a single test.
|
|
// In production, the app is restarted when changes get enabled or disabled,
|
|
// and the ChangeConfig is then set exactly once on that app process.
|
|
if (sCallbacks instanceof OverrideCallbacks) {
|
|
throw new IllegalStateException("setOverrides has already been called!");
|
|
}
|
|
sCallbacks = new OverrideCallbacks(sCallbacks, overrides);
|
|
}
|
|
|
|
/**
|
|
* For use by tests only. Removes overrides set by {@link #setOverrides}.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
public static void clearOverrides() {
|
|
if (!(sCallbacks instanceof OverrideCallbacks)) {
|
|
throw new IllegalStateException("No overrides set");
|
|
}
|
|
sCallbacks = ((OverrideCallbacks) sCallbacks).delegate;
|
|
}
|
|
|
|
/**
|
|
* Base class for compatibility API implementations. The default implementation logs a warning
|
|
* to logcat.
|
|
*
|
|
* This is provided as a class rather than an interface to allow new methods to be added without
|
|
* breaking @SystemApi binary compatibility.
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
public interface BehaviorChangeDelegate {
|
|
/**
|
|
* Called when a change is reported via {@link Compatibility#reportUnconditionalChange}
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
default void onChangeReported(long changeId) {
|
|
// Do not use String.format here (b/160912695)
|
|
System.logW("No Compatibility callbacks set! Reporting change " + changeId);
|
|
}
|
|
|
|
/**
|
|
* Called when a change is queried via {@link Compatibility#isChangeEnabled}
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
default boolean isChangeEnabled(long changeId) {
|
|
// Do not use String.format here (b/160912695)
|
|
// TODO(b/289900411): Rate limit this log if it's necessary in the release build.
|
|
// System.logW("No Compatibility callbacks set! Querying change " + changeId);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public static final class ChangeConfig {
|
|
private final Set<Long> enabled;
|
|
private final Set<Long> disabled;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public ChangeConfig(@NonNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled) {
|
|
this.enabled = Objects.requireNonNull(enabled);
|
|
this.disabled = Objects.requireNonNull(disabled);
|
|
if (enabled.contains(null)) {
|
|
throw new NullPointerException();
|
|
}
|
|
if (disabled.contains(null)) {
|
|
throw new NullPointerException();
|
|
}
|
|
Set<Long> intersection = new HashSet<>(enabled);
|
|
intersection.retainAll(disabled);
|
|
if (!intersection.isEmpty()) {
|
|
throw new IllegalArgumentException("Cannot have changes " + intersection
|
|
+ " enabled and disabled!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public boolean isEmpty() {
|
|
return enabled.isEmpty() && disabled.isEmpty();
|
|
}
|
|
|
|
private static long[] toLongArray(Set<Long> values) {
|
|
long[] result = new long[values.size()];
|
|
int idx = 0;
|
|
for (Long value: values) {
|
|
result[idx++] = value;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public @NonNull long[] getEnabledChangesArray() {
|
|
return toLongArray(enabled);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public @NonNull long[] getDisabledChangesArray() {
|
|
return toLongArray(disabled);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public @NonNull Set<@NonNull Long> getEnabledSet() {
|
|
return Collections.unmodifiableSet(enabled);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public @NonNull Set<@NonNull Long> getDisabledSet() {
|
|
return Collections.unmodifiableSet(disabled);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public boolean isForceEnabled(long changeId) {
|
|
return enabled.contains(changeId);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
@IntraCoreApi
|
|
public boolean isForceDisabled(long changeId) {
|
|
return disabled.contains(changeId);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof ChangeConfig)) {
|
|
return false;
|
|
}
|
|
ChangeConfig that = (ChangeConfig) o;
|
|
return enabled.equals(that.enabled) &&
|
|
disabled.equals(that.disabled);
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(enabled, disabled);
|
|
}
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@Override
|
|
public String toString() {
|
|
return "ChangeConfig{enabled=" + enabled + ", disabled=" + disabled + '}';
|
|
}
|
|
}
|
|
|
|
private static class OverrideCallbacks implements BehaviorChangeDelegate {
|
|
private final BehaviorChangeDelegate delegate;
|
|
private final ChangeConfig changeConfig;
|
|
|
|
private OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig) {
|
|
this.delegate = Objects.requireNonNull(delegate);
|
|
this.changeConfig = Objects.requireNonNull(changeConfig);
|
|
}
|
|
@Override
|
|
public boolean isChangeEnabled(long changeId) {
|
|
if (changeConfig.isForceEnabled(changeId)) {
|
|
return true;
|
|
}
|
|
if (changeConfig.isForceDisabled(changeId)) {
|
|
return false;
|
|
}
|
|
return delegate.isChangeEnabled(changeId);
|
|
}
|
|
}
|
|
}
|