• 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 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      * Return the behavior change delegate
118      *
119      * @hide
120      */
121     // VisibleForTesting
122     @NonNull
getBehaviorChangeDelegate()123     public static BehaviorChangeDelegate getBehaviorChangeDelegate() {
124         return sCallbacks;
125     }
126 
127     /**
128      * For use by tests only. Causes values from {@code overrides} to be returned instead of the
129      * real value.
130      *
131      * @hide
132      */
133     @SystemApi(client = MODULE_LIBRARIES)
setOverrides(ChangeConfig overrides)134     public static void setOverrides(ChangeConfig overrides) {
135         // Setting overrides twice in a row does not need to be supported because
136         // this method is only for enabling/disabling changes for the duration of
137         // a single test.
138         // In production, the app is restarted when changes get enabled or disabled,
139         // and the ChangeConfig is then set exactly once on that app process.
140         if (sCallbacks instanceof OverrideCallbacks) {
141             throw new IllegalStateException("setOverrides has already been called!");
142         }
143         sCallbacks = new OverrideCallbacks(sCallbacks, overrides);
144     }
145 
146     /**
147      * For use by tests only. Removes overrides set by {@link #setOverrides}.
148      *
149      * @hide
150      */
151     @SystemApi(client = MODULE_LIBRARIES)
clearOverrides()152     public static void clearOverrides() {
153         if (!(sCallbacks instanceof OverrideCallbacks)) {
154             throw new IllegalStateException("No overrides set");
155         }
156         sCallbacks = ((OverrideCallbacks) sCallbacks).delegate;
157     }
158 
159     /**
160      * Base class for compatibility API implementations. The default implementation logs a warning
161      * to logcat.
162      *
163      * This is provided as a class rather than an interface to allow new methods to be added without
164      * breaking @SystemApi binary compatibility.
165      *
166      * @hide
167      */
168     @SystemApi(client = MODULE_LIBRARIES)
169     public interface BehaviorChangeDelegate {
170         /**
171          * Called when a change is reported via {@link Compatibility#reportUnconditionalChange}
172          *
173          * @hide
174          */
175         @SystemApi(client = MODULE_LIBRARIES)
onChangeReported(long changeId)176         default void onChangeReported(long changeId) {
177             // Do not use String.format here (b/160912695)
178             System.logW("No Compatibility callbacks set! Reporting change " + changeId);
179         }
180 
181         /**
182          * Called when a change is queried via {@link Compatibility#isChangeEnabled}
183          *
184          * @hide
185          */
186         @SystemApi(client = MODULE_LIBRARIES)
isChangeEnabled(long changeId)187         default boolean isChangeEnabled(long changeId) {
188             // Do not use String.format here (b/160912695)
189             // TODO(b/289900411): Rate limit this log if it's necessary in the release build.
190             // System.logW("No Compatibility callbacks set! Querying change " + changeId);
191             return true;
192         }
193     }
194 
195     /**
196      * @hide
197      */
198     @SystemApi(client = MODULE_LIBRARIES)
199     @IntraCoreApi
200     public static final class ChangeConfig {
201         private final Set<Long> enabled;
202         private final Set<Long> disabled;
203 
204         /**
205          * @hide
206          */
207         @SystemApi(client = MODULE_LIBRARIES)
208         @IntraCoreApi
ChangeConfig(@onNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled)209         public ChangeConfig(@NonNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled) {
210             this.enabled = Objects.requireNonNull(enabled);
211             this.disabled = Objects.requireNonNull(disabled);
212             if (enabled.contains(null)) {
213                 throw new NullPointerException();
214             }
215             if (disabled.contains(null)) {
216                 throw new NullPointerException();
217             }
218             Set<Long> intersection = new HashSet<>(enabled);
219             intersection.retainAll(disabled);
220             if (!intersection.isEmpty()) {
221                 throw new IllegalArgumentException("Cannot have changes " + intersection
222                         + " enabled and disabled!");
223             }
224         }
225 
226         /**
227          * @hide
228          */
229         @SystemApi(client = MODULE_LIBRARIES)
230         @IntraCoreApi
isEmpty()231         public boolean isEmpty() {
232             return enabled.isEmpty() && disabled.isEmpty();
233         }
234 
toLongArray(Set<Long> values)235         private static long[] toLongArray(Set<Long> values) {
236             long[] result = new long[values.size()];
237             int idx = 0;
238             for (Long value: values) {
239                 result[idx++] = value;
240             }
241             return result;
242         }
243 
244         /**
245          * @hide
246          */
247         @SystemApi(client = MODULE_LIBRARIES)
248         @IntraCoreApi
getEnabledChangesArray()249         public @NonNull long[] getEnabledChangesArray() {
250             return toLongArray(enabled);
251         }
252 
253 
254         /**
255          * @hide
256          */
257         @SystemApi(client = MODULE_LIBRARIES)
258         @IntraCoreApi
getDisabledChangesArray()259         public @NonNull long[] getDisabledChangesArray() {
260             return toLongArray(disabled);
261         }
262 
263 
264         /**
265          * @hide
266          */
267         @SystemApi(client = MODULE_LIBRARIES)
268         @IntraCoreApi
getEnabledSet()269         public @NonNull Set<@NonNull Long> getEnabledSet() {
270             return Collections.unmodifiableSet(enabled);
271         }
272 
273 
274         /**
275          * @hide
276          */
277         @SystemApi(client = MODULE_LIBRARIES)
278         @IntraCoreApi
getDisabledSet()279         public @NonNull Set<@NonNull Long> getDisabledSet() {
280             return Collections.unmodifiableSet(disabled);
281         }
282 
283 
284         /**
285          * @hide
286          */
287         @SystemApi(client = MODULE_LIBRARIES)
288         @IntraCoreApi
isForceEnabled(long changeId)289         public boolean isForceEnabled(long changeId) {
290             return enabled.contains(changeId);
291         }
292 
293 
294         /**
295          * @hide
296          */
297         @SystemApi(client = MODULE_LIBRARIES)
298         @IntraCoreApi
isForceDisabled(long changeId)299         public boolean isForceDisabled(long changeId) {
300             return disabled.contains(changeId);
301         }
302 
303 
304         /**
305          * @hide
306          */
307         @Override
equals(Object o)308         public boolean equals(Object o) {
309             if (this == o) return true;
310             if (!(o instanceof ChangeConfig)) {
311                 return false;
312             }
313             ChangeConfig that = (ChangeConfig) o;
314             return enabled.equals(that.enabled) &&
315                     disabled.equals(that.disabled);
316         }
317 
318         /**
319          * @hide
320          */
321         @Override
hashCode()322         public int hashCode() {
323             return Objects.hash(enabled, disabled);
324         }
325 
326 
327         /**
328          * @hide
329          */
330         @Override
toString()331         public String toString() {
332             return "ChangeConfig{enabled=" + enabled + ", disabled=" + disabled + '}';
333         }
334     }
335 
336     private static class OverrideCallbacks implements BehaviorChangeDelegate {
337         private final BehaviorChangeDelegate delegate;
338         private final ChangeConfig changeConfig;
339 
OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig)340         private OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig) {
341             this.delegate = Objects.requireNonNull(delegate);
342             this.changeConfig = Objects.requireNonNull(changeConfig);
343         }
344         @Override
isChangeEnabled(long changeId)345         public boolean isChangeEnabled(long changeId) {
346            if (changeConfig.isForceEnabled(changeId)) {
347                return true;
348            }
349            if (changeConfig.isForceDisabled(changeId)) {
350                return false;
351            }
352            return delegate.isChangeEnabled(changeId);
353         }
354     }
355 }
356