1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.compat; 18 19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 21 import android.annotation.SystemApi; 22 import android.compat.annotation.ChangeId; 23 24 import libcore.api.IntraCoreApi; 25 26 import java.util.Collections; 27 import java.util.HashSet; 28 import java.util.Objects; 29 import java.util.Set; 30 import libcore.util.NonNull; 31 32 /** 33 * Internal APIs for logging and gating compatibility changes. 34 * 35 * @see ChangeId 36 * 37 * @hide 38 */ 39 @SystemApi(client = MODULE_LIBRARIES) 40 @IntraCoreApi 41 public final class Compatibility { 42 Compatibility()43 private Compatibility() {} 44 45 /** 46 * Reports that a compatibility change is affecting the current process now. 47 * 48 * <p>Calls to this method from a non-app process are ignored. This allows code implementing 49 * APIs that are used by apps and by other code (e.g. the system server) to report changes 50 * regardless of the process it's running in. When called in a non-app process, this method is 51 * a no-op. 52 * 53 * <p>Note: for changes that are gated using {@link #isChangeEnabled(long)}, you do not need to 54 * call this API directly. The change will be reported for you in the case that 55 * {@link #isChangeEnabled(long)} returns {@code true}. 56 * 57 * @param changeId The ID of the compatibility change taking effect. 58 * 59 * @hide 60 */ 61 @SystemApi(client = MODULE_LIBRARIES) 62 @IntraCoreApi reportUnconditionalChange(@hangeId long changeId)63 public static void reportUnconditionalChange(@ChangeId long changeId) { 64 sCallbacks.onChangeReported(changeId); 65 } 66 67 /** 68 * Query if a given compatibility change is enabled for the current process. This method should 69 * only be called by code running inside a process of the affected app. 70 * 71 * <p>If this method returns {@code true}, the calling code should implement the compatibility 72 * change, resulting in differing behaviour compared to earlier releases. If this method returns 73 * {@code false}, the calling code should behave as it did in earlier releases. 74 * 75 * <p>When this method returns {@code true}, it will also report the change as 76 * {@link #reportUnconditionalChange(long)} would, so there is no need to call that method 77 * directly. 78 * 79 * @param changeId The ID of the compatibility change in question. 80 * @return {@code true} if the change is enabled for the current app. 81 * 82 * @hide 83 */ 84 @SystemApi(client = MODULE_LIBRARIES) 85 @IntraCoreApi isChangeEnabled(@hangeId long changeId)86 public static boolean isChangeEnabled(@ChangeId long changeId) { 87 return sCallbacks.isChangeEnabled(changeId); 88 } 89 90 private static final BehaviorChangeDelegate DEFAULT_CALLBACKS = new BehaviorChangeDelegate(){}; 91 92 private volatile static BehaviorChangeDelegate sCallbacks = DEFAULT_CALLBACKS; 93 94 /** 95 * Sets the behavior change delegate. 96 * 97 * All changes reported via the {@link Compatibility} class will be forwarded to this class. 98 * 99 * @hide 100 */ 101 @SystemApi(client = MODULE_LIBRARIES) setBehaviorChangeDelegate(BehaviorChangeDelegate callbacks)102 public static void setBehaviorChangeDelegate(BehaviorChangeDelegate callbacks) { 103 sCallbacks = Objects.requireNonNull(callbacks); 104 } 105 106 /** 107 * Removes a behavior change delegate previously set via {@link #setBehaviorChangeDelegate}. 108 * 109 * @hide 110 */ 111 @SystemApi(client = MODULE_LIBRARIES) clearBehaviorChangeDelegate()112 public static void clearBehaviorChangeDelegate() { 113 sCallbacks = DEFAULT_CALLBACKS; 114 } 115 116 /** 117 * For use by tests only. Causes values from {@code overrides} to be returned instead of the 118 * real value. 119 * 120 * @hide 121 */ 122 @SystemApi(client = MODULE_LIBRARIES) setOverrides(ChangeConfig overrides)123 public static void setOverrides(ChangeConfig overrides) { 124 // Setting overrides twice in a row does not need to be supported because 125 // this method is only for enabling/disabling changes for the duration of 126 // a single test. 127 // In production, the app is restarted when changes get enabled or disabled, 128 // and the ChangeConfig is then set exactly once on that app process. 129 if (sCallbacks instanceof OverrideCallbacks) { 130 throw new IllegalStateException("setOverrides has already been called!"); 131 } 132 sCallbacks = new OverrideCallbacks(sCallbacks, overrides); 133 } 134 135 /** 136 * For use by tests only. Removes overrides set by {@link #setOverrides}. 137 * 138 * @hide 139 */ 140 @SystemApi(client = MODULE_LIBRARIES) clearOverrides()141 public static void clearOverrides() { 142 if (!(sCallbacks instanceof OverrideCallbacks)) { 143 throw new IllegalStateException("No overrides set"); 144 } 145 sCallbacks = ((OverrideCallbacks) sCallbacks).delegate; 146 } 147 148 /** 149 * Base class for compatibility API implementations. The default implementation logs a warning 150 * to logcat. 151 * 152 * This is provided as a class rather than an interface to allow new methods to be added without 153 * breaking @SystemApi binary compatibility. 154 * 155 * @hide 156 */ 157 @SystemApi(client = MODULE_LIBRARIES) 158 public interface BehaviorChangeDelegate { 159 /** 160 * Called when a change is reported via {@link Compatibility#reportUnconditionalChange} 161 * 162 * @hide 163 */ 164 @SystemApi(client = MODULE_LIBRARIES) onChangeReported(long changeId)165 default void onChangeReported(long changeId) { 166 // Do not use String.format here (b/160912695) 167 System.logW("No Compatibility callbacks set! Reporting change " + changeId); 168 } 169 170 /** 171 * Called when a change is queried via {@link Compatibility#isChangeEnabled} 172 * 173 * @hide 174 */ 175 @SystemApi(client = MODULE_LIBRARIES) isChangeEnabled(long changeId)176 default boolean isChangeEnabled(long changeId) { 177 // Do not use String.format here (b/160912695) 178 System.logW("No Compatibility callbacks set! Querying change " + changeId); 179 return true; 180 } 181 } 182 183 /** 184 * @hide 185 */ 186 @SystemApi(client = MODULE_LIBRARIES) 187 @IntraCoreApi 188 public static final class ChangeConfig { 189 private final Set<Long> enabled; 190 private final Set<Long> disabled; 191 192 /** 193 * @hide 194 */ 195 @SystemApi(client = MODULE_LIBRARIES) 196 @IntraCoreApi ChangeConfig(@onNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled)197 public ChangeConfig(@NonNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled) { 198 this.enabled = Objects.requireNonNull(enabled); 199 this.disabled = Objects.requireNonNull(disabled); 200 if (enabled.contains(null)) { 201 throw new NullPointerException(); 202 } 203 if (disabled.contains(null)) { 204 throw new NullPointerException(); 205 } 206 Set<Long> intersection = new HashSet<>(enabled); 207 intersection.retainAll(disabled); 208 if (!intersection.isEmpty()) { 209 throw new IllegalArgumentException("Cannot have changes " + intersection 210 + " enabled and disabled!"); 211 } 212 } 213 214 /** 215 * @hide 216 */ 217 @SystemApi(client = MODULE_LIBRARIES) 218 @IntraCoreApi isEmpty()219 public boolean isEmpty() { 220 return enabled.isEmpty() && disabled.isEmpty(); 221 } 222 toLongArray(Set<Long> values)223 private static long[] toLongArray(Set<Long> values) { 224 long[] result = new long[values.size()]; 225 int idx = 0; 226 for (Long value: values) { 227 result[idx++] = value; 228 } 229 return result; 230 } 231 232 /** 233 * @hide 234 */ 235 @SystemApi(client = MODULE_LIBRARIES) 236 @IntraCoreApi getEnabledChangesArray()237 public @NonNull long[] getEnabledChangesArray() { 238 return toLongArray(enabled); 239 } 240 241 242 /** 243 * @hide 244 */ 245 @SystemApi(client = MODULE_LIBRARIES) 246 @IntraCoreApi getDisabledChangesArray()247 public @NonNull long[] getDisabledChangesArray() { 248 return toLongArray(disabled); 249 } 250 251 252 /** 253 * @hide 254 */ 255 @SystemApi(client = MODULE_LIBRARIES) 256 @IntraCoreApi getEnabledSet()257 public @NonNull Set<@NonNull Long> getEnabledSet() { 258 return Collections.unmodifiableSet(enabled); 259 } 260 261 262 /** 263 * @hide 264 */ 265 @SystemApi(client = MODULE_LIBRARIES) 266 @IntraCoreApi getDisabledSet()267 public @NonNull Set<@NonNull Long> getDisabledSet() { 268 return Collections.unmodifiableSet(disabled); 269 } 270 271 272 /** 273 * @hide 274 */ 275 @SystemApi(client = MODULE_LIBRARIES) 276 @IntraCoreApi isForceEnabled(long changeId)277 public boolean isForceEnabled(long changeId) { 278 return enabled.contains(changeId); 279 } 280 281 282 /** 283 * @hide 284 */ 285 @SystemApi(client = MODULE_LIBRARIES) 286 @IntraCoreApi isForceDisabled(long changeId)287 public boolean isForceDisabled(long changeId) { 288 return disabled.contains(changeId); 289 } 290 291 292 /** 293 * @hide 294 */ 295 @Override equals(Object o)296 public boolean equals(Object o) { 297 if (this == o) return true; 298 if (!(o instanceof ChangeConfig)) { 299 return false; 300 } 301 ChangeConfig that = (ChangeConfig) o; 302 return enabled.equals(that.enabled) && 303 disabled.equals(that.disabled); 304 } 305 306 /** 307 * @hide 308 */ 309 @Override hashCode()310 public int hashCode() { 311 return Objects.hash(enabled, disabled); 312 } 313 314 315 /** 316 * @hide 317 */ 318 @Override toString()319 public String toString() { 320 return "ChangeConfig{enabled=" + enabled + ", disabled=" + disabled + '}'; 321 } 322 } 323 324 private static class OverrideCallbacks implements BehaviorChangeDelegate { 325 private final BehaviorChangeDelegate delegate; 326 private final ChangeConfig changeConfig; 327 OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig)328 private OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig) { 329 this.delegate = Objects.requireNonNull(delegate); 330 this.changeConfig = Objects.requireNonNull(changeConfig); 331 } 332 @Override isChangeEnabled(long changeId)333 public boolean isChangeEnabled(long changeId) { 334 if (changeConfig.isForceEnabled(changeId)) { 335 return true; 336 } 337 if (changeConfig.isForceDisabled(changeId)) { 338 return false; 339 } 340 return delegate.isChangeEnabled(changeId); 341 } 342 } 343 } 344