• 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 com.android.internal.compat;
18 
19 import static android.text.TextUtils.formatSimple;
20 
21 import static java.util.Collections.EMPTY_SET;
22 
23 import android.annotation.IntDef;
24 import android.util.Log;
25 import android.util.Slog;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.compat.flags.Flags;
29 import com.android.internal.util.FrameworkStatsLog;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.Collections;
34 import java.util.HashSet;
35 import java.util.Objects;
36 import java.util.Set;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.function.Function;
39 
40 /**
41  * A helper class to report changes to stats log.
42  *
43  * @hide
44  */
45 @android.ravenwood.annotation.RavenwoodKeepWholeClass
46 public class ChangeReporter {
47     private static final String TAG = "CompatChangeReporter";
48     private static final Function<Integer, Set<ChangeReport>> NEW_CHANGE_REPORT_SET =
49             uid -> Collections.synchronizedSet(new HashSet<>());
50     private int mSource;
51 
52     private static final class ChangeReport {
53         long mChangeId;
54         int mState;
55 
ChangeReport(long changeId, @State int state)56         ChangeReport(long changeId, @State int state) {
57             mChangeId = changeId;
58             mState = state;
59         }
60 
61         @Override
equals(Object o)62         public boolean equals(Object o) {
63             if (this == o) return true;
64             if (o == null || getClass() != o.getClass()) return false;
65             ChangeReport that = (ChangeReport) o;
66             return mChangeId == that.mChangeId
67                     && mState == that.mState;
68         }
69 
70         @Override
hashCode()71         public int hashCode() {
72             return Objects.hash(mChangeId, mState);
73         }
74     }
75 
76     // Maps uid to a set of ChangeReports (that were reported for that uid).
77     private final ConcurrentHashMap<Integer, Set<ChangeReport>> mReportedChanges;
78 
79     // When true will of every time to debug (logcat).
80     private boolean mDebugLogAll;
81 
ChangeReporter(@ource int source)82     public ChangeReporter(@Source int source) {
83         mSource = source;
84         mReportedChanges =  new ConcurrentHashMap<>();
85         mDebugLogAll = false;
86     }
87 
88     /**
89      * Report the change to stats log and to the debug log if the change was not previously
90      * logged already.
91      *
92      * @param uid              affected by the change
93      * @param changeId         the reported change id
94      * @param state            of the reported change - enabled/disabled/only logged
95      * @param isKnownSystemApp do we know that the affected app is a system app? (if true is
96      *                         definitely a system app, if false it may or may not be a system app)
97      * @param isLoggableBySdk  whether debug logging is allowed for this change based on target
98      *                         SDK version. This is combined with other logic to determine whether
99      *                         to actually log. If the sdk version does not matter, should be true.
100      */
reportChange(int uid, long changeId, int state, boolean isKnownSystemApp, boolean isLoggableBySdk)101     public void reportChange(int uid, long changeId, int state, boolean isKnownSystemApp,
102             boolean isLoggableBySdk) {
103         boolean isAlreadyReported =
104                 checkAndSetIsAlreadyReported(uid, new ChangeReport(changeId, state));
105         if (shouldWriteToStatsLog(isKnownSystemApp, isAlreadyReported)) {
106             FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
107                     changeId, state, mSource);
108         }
109         if (shouldWriteToDebug(isAlreadyReported, state, isLoggableBySdk)) {
110             debugLog(uid, changeId, state);
111         }
112     }
113 
114     /**
115      * Report the change to stats log and to the debug log if the change was not previously
116      * logged already.
117      *
118      * @param uid      affected by the change
119      * @param changeId the reported change id
120      * @param state    of the reported change - enabled/disabled/only logged
121      */
reportChange(int uid, long changeId, int state)122     public void reportChange(int uid, long changeId, int state) {
123         reportChange(uid, changeId, state, false, true);
124     }
125 
126     /**
127      * Start logging all the time to logcat.
128      */
startDebugLogAll()129     public void startDebugLogAll() {
130         mDebugLogAll = true;
131     }
132 
133     /**
134      * Stop logging all the time to logcat.
135      */
stopDebugLogAll()136     public void stopDebugLogAll() {
137         mDebugLogAll = false;
138     }
139 
140     /**
141      * Returns whether the next report should be logged to FrameworkStatsLog.
142      *
143      * @param isKnownSystemApp do we know that the affected app is a system app? (if true is
144      *                         definitely a system app, if false it may or may not be a system app)
145      * @param isAlreadyReported is the change already reported
146      * @return true if the report should be logged
147      */
148     @VisibleForTesting
shouldWriteToStatsLog(boolean isKnownSystemApp, boolean isAlreadyReported)149     boolean shouldWriteToStatsLog(boolean isKnownSystemApp, boolean isAlreadyReported) {
150         // We don't log where we know the source is a system app or is already reported
151         return !isKnownSystemApp && !isAlreadyReported;
152     }
153 
154     /**
155      * Returns whether the next report should be logged to logcat.
156      *
157      * @param isAlreadyReported is the change already reported
158      * @param state             of the reported change - enabled/disabled/only logged
159      * @param isLoggableBySdk   whether debug logging is allowed for this change based on target SDK
160      *                          version. This is combined with other logic to determine whether to
161      *                          actually log. If the sdk version does not matter, should be true.
162      * @return true if the report should be logged
163      */
shouldWriteToDebug( boolean isAlreadyReported, int state, boolean isLoggableBySdk)164     private boolean shouldWriteToDebug(
165             boolean isAlreadyReported, int state, boolean isLoggableBySdk) {
166         // If log all bit is on, always return true.
167         if (mDebugLogAll) return true;
168         // If the change has already been reported, do not write.
169         if (isAlreadyReported) return false;
170 
171         // If the flag is turned off or the TAG's logging is forced to debug level with
172         // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
173         // have already passed.
174         boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging();
175         if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true;
176 
177         // Log if the change is enabled and targets the latest sdk version.
178         return isLoggableBySdk && state != STATE_DISABLED;
179     }
180 
181     /**
182      * Returns whether the next report should be logged to logcat.
183      *
184      * @param uid         affected by the change
185      * @param changeId    the reported change id
186      * @param state       of the reported change - enabled/disabled/only logged
187      *
188      * @return true if the report should be logged
189      */
190     @VisibleForTesting
shouldWriteToDebug(int uid, long changeId, int state)191     boolean shouldWriteToDebug(int uid, long changeId, int state) {
192         return shouldWriteToDebug(uid, changeId, state, true);
193     }
194 
195     /**
196      * Returns whether the next report should be logged to logcat.
197      *
198      * @param uid               affected by the change
199      * @param changeId          the reported change id
200      * @param state             of the reported change - enabled/disabled/only logged
201      * @param isLoggableBySdk   whether debug logging is allowed for this change based on target SDK
202      *                          version. This is combined with other logic to determine whether to
203      *                          actually log. If the sdk version does not matter, should be true.
204      * @return true if the report should be logged
205      */
206     @VisibleForTesting
shouldWriteToDebug(int uid, long changeId, int state, boolean isLoggableBySdk)207     boolean shouldWriteToDebug(int uid, long changeId, int state, boolean isLoggableBySdk) {
208         return shouldWriteToDebug(
209                 isAlreadyReported(uid, new ChangeReport(changeId, state)), state, isLoggableBySdk);
210     }
211 
212     /**
213      * Return if change has been reported. Also mark change as reported if not.
214      *
215      * @param uid affected by the change
216      * @param changeReport change reported to be checked and marked as reported.
217      *
218      * @return true if change has been reported, and vice versa.
219      */
checkAndSetIsAlreadyReported(int uid, ChangeReport changeReport)220     private boolean checkAndSetIsAlreadyReported(int uid, ChangeReport changeReport) {
221         boolean isAlreadyReported = isAlreadyReported(uid, changeReport);
222         if (!isAlreadyReported) {
223             markAsReported(uid, changeReport);
224         }
225         return isAlreadyReported;
226     }
227 
isAlreadyReported(int uid, ChangeReport report)228     private boolean isAlreadyReported(int uid, ChangeReport report) {
229         return mReportedChanges.getOrDefault(uid, EMPTY_SET).contains(report);
230     }
231 
232     /**
233      * Returns whether the next report should be logged.
234      *
235      * @param uid      affected by the change
236      * @param changeId the reported change id
237      * @param state    of the reported change - enabled/disabled/only logged
238      * @return true if the report should be logged
239      */
240     @VisibleForTesting
isAlreadyReported(int uid, long changeId, int state)241     boolean isAlreadyReported(int uid, long changeId, int state) {
242         return isAlreadyReported(uid, new ChangeReport(changeId, state));
243     }
244 
markAsReported(int uid, ChangeReport report)245     private void markAsReported(int uid, ChangeReport report) {
246         mReportedChanges.computeIfAbsent(uid, NEW_CHANGE_REPORT_SET).add(report);
247     }
248 
249     /**
250      * Clears the saved information about a given uid. Requests to report uid again will be reported
251      * regardless to the past reports.
252      *
253      * <p> Only intended to be called from PlatformCompat.
254      *
255      * @param uid to reset
256      */
resetReportedChanges(int uid)257     public void resetReportedChanges(int uid) {
258         mReportedChanges.remove(uid);
259     }
260 
debugLog(int uid, long changeId, int state)261     private void debugLog(int uid, long changeId, int state) {
262         String message = formatSimple("Compat change id reported: %d; UID %d; state: %s", changeId,
263                 uid, stateToString(state));
264         if (mSource == SOURCE_SYSTEM_SERVER) {
265             Slog.d(TAG, message);
266         } else {
267             Log.d(TAG, message);
268         }
269     }
270 
271     /**
272      * Transforms {@link #ChangeReporter.State} enum to a string.
273      *
274      * @param state to transform
275      * @return a string representing the state
276      */
stateToString(@tate int state)277     private static String stateToString(@State int state) {
278         switch (state) {
279             case STATE_LOGGED:
280                 return "LOGGED";
281             case STATE_ENABLED:
282                 return "ENABLED";
283             case STATE_DISABLED:
284                 return "DISABLED";
285             default:
286                 return "UNKNOWN";
287         }
288     }
289 
290     /** These values should be kept in sync with those in atoms.proto */
291     public static final int STATE_UNKNOWN_STATE =
292                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__UNKNOWN_STATE;
293     public static final int STATE_ENABLED =
294                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED;
295     public static final int STATE_DISABLED =
296                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED;
297     public static final int STATE_LOGGED =
298                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED;
299     public static final int SOURCE_UNKNOWN_SOURCE =
300                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__UNKNOWN_SOURCE;
301     public static final int SOURCE_APP_PROCESS =
302                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS;
303     public static final int SOURCE_SYSTEM_SERVER =
304                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER;
305 
306     @Retention(RetentionPolicy.SOURCE)
307     @IntDef(prefix = { "STATE_" }, value = {
308             STATE_UNKNOWN_STATE,
309             STATE_ENABLED,
310             STATE_DISABLED,
311             STATE_LOGGED
312     })
313     public @interface State {
314     }
315 
316     @Retention(RetentionPolicy.SOURCE)
317     @IntDef(prefix = { "SOURCE_" }, value = {
318             SOURCE_UNKNOWN_SOURCE,
319             SOURCE_APP_PROCESS,
320             SOURCE_SYSTEM_SERVER
321     })
322     public @interface Source {
323     }
324 }
325