• 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      * 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