• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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