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