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 com.android.server.compat;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.ChangeId;
21 import android.compat.annotation.EnabledAfter;
22 import android.content.pm.ApplicationInfo;
23 
24 import com.android.internal.compat.CompatibilityChangeInfo;
25 import com.android.server.compat.config.Change;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 
30 /**
31  * Represents the state of a single compatibility change.
32  *
33  * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk}
34  * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any
35  * target SDK criteria set. These settings can be overridden for a specific package using
36  * {@link #addPackageOverride(String, boolean)}.
37  *
38  * <p>Note, this class is not thread safe so callers must ensure thread safety.
39  */
40 public final class CompatChange extends CompatibilityChangeInfo {
41 
42     /**
43      * A change ID to be used only in the CTS test for this SystemApi
44      */
45     @ChangeId
46     @EnabledAfter(targetSdkVersion = 1234) // Needs to be > test APK targetSdkVersion.
47     private static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
48 
49     /**
50      * Callback listener for when compat changes are updated for a package.
51      * See {@link #registerListener(ChangeListener)} for more details.
52      */
53     public interface ChangeListener {
54         /**
55          * Called upon an override change for packageName and the change this listener is
56          * registered for. Called before the app is killed.
57          */
onCompatChange(String packageName)58         void onCompatChange(String packageName);
59     }
60 
61     ChangeListener mListener = null;
62 
63     private Map<String, Boolean> mPackageOverrides;
64 
CompatChange(long changeId)65     public CompatChange(long changeId) {
66         this(changeId, null, -1, false, false, null);
67     }
68 
69     /**
70      * @param changeId Unique ID for the change. See {@link android.compat.Compatibility}.
71      * @param name Short descriptive name.
72      * @param enableAfterTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledAfter};
73      *                             -1 if the change is always enabled.
74      * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set.
75      */
CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk, boolean disabled, boolean loggingOnly, String description)76     public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk,
77             boolean disabled, boolean loggingOnly, String description) {
78         super(changeId, name, enableAfterTargetSdk, disabled, loggingOnly, description);
79     }
80 
81     /**
82      * @param change an object generated by services/core/xsd/platform-compat-config.xsd
83      */
CompatChange(Change change)84     public CompatChange(Change change) {
85         super(change.getId(), change.getName(), change.getEnableAfterTargetSdk(),
86                 change.getDisabled(), change.getLoggingOnly(), change.getDescription());
87     }
88 
registerListener(ChangeListener listener)89     void registerListener(ChangeListener listener) {
90         if (mListener != null) {
91             throw new IllegalStateException(
92                     "Listener for change " + toString() + " already registered.");
93         }
94         mListener = listener;
95     }
96 
97 
98     /**
99      * Force the enabled state of this change for a given package name. The change will only take
100      * effect after that packages process is killed and restarted.
101      *
102      * <p>Note, this method is not thread safe so callers must ensure thread safety.
103      *
104      * @param pname Package name to enable the change for.
105      * @param enabled Whether or not to enable the change.
106      */
addPackageOverride(String pname, boolean enabled)107     void addPackageOverride(String pname, boolean enabled) {
108         if (getLoggingOnly()) {
109             throw new IllegalArgumentException(
110                     "Can't add overrides for a logging only change " + toString());
111         }
112         if (mPackageOverrides == null) {
113             mPackageOverrides = new HashMap<>();
114         }
115         mPackageOverrides.put(pname, enabled);
116         notifyListener(pname);
117     }
118 
119     /**
120      * Remove any package override for the given package name, restoring the default behaviour.
121      *
122      * <p>Note, this method is not thread safe so callers must ensure thread safety.
123      *
124      * @param pname Package name to reset to defaults for.
125      */
removePackageOverride(String pname)126     void removePackageOverride(String pname) {
127         if (mPackageOverrides != null) {
128             if (mPackageOverrides.remove(pname) != null) {
129                 notifyListener(pname);
130             }
131         }
132     }
133 
134     /**
135      * Find if this change is enabled for the given package, taking into account any overrides that
136      * exist.
137      *
138      * @param app Info about the app in question
139      * @return {@code true} if the change should be enabled for the package.
140      */
isEnabled(ApplicationInfo app)141     boolean isEnabled(ApplicationInfo app) {
142         if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) {
143             return mPackageOverrides.get(app.packageName);
144         }
145         if (getDisabled()) {
146             return false;
147         }
148         if (getEnableAfterTargetSdk() != -1) {
149             return app.targetSdkVersion > getEnableAfterTargetSdk();
150         }
151         return true;
152     }
153 
154     /**
155      * Checks whether a change has an override for a package.
156      * @param packageName name of the package
157      * @return true if there is such override
158      */
hasOverride(String packageName)159     boolean hasOverride(String packageName) {
160         return mPackageOverrides != null && mPackageOverrides.containsKey(packageName);
161     }
162 
163     @Override
toString()164     public String toString() {
165         StringBuilder sb = new StringBuilder("ChangeId(")
166                 .append(getId());
167         if (getName() != null) {
168             sb.append("; name=").append(getName());
169         }
170         if (getEnableAfterTargetSdk() != -1) {
171             sb.append("; enableAfterTargetSdk=").append(getEnableAfterTargetSdk());
172         }
173         if (getDisabled()) {
174             sb.append("; disabled");
175         }
176         if (getLoggingOnly()) {
177             sb.append("; loggingOnly");
178         }
179         if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
180             sb.append("; packageOverrides=").append(mPackageOverrides);
181         }
182         return sb.append(")").toString();
183     }
184 
notifyListener(String packageName)185     private void notifyListener(String packageName) {
186         if (mListener != null) {
187             mListener.onCompatChange(packageName);
188         }
189     }
190 }
191