/* * 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. * *
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. * *
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. * *
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. * *
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