• 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 android.annotation.IntDef;
22 import android.util.Log;
23 import android.util.Slog;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.util.FrameworkStatsLog;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Set;
36 
37 /**
38  * A helper class to report changes to stats log.
39  *
40  * @hide
41  */
42 public final class ChangeReporter {
43     private static final String TAG = "CompatibilityChangeReporter";
44     private int mSource;
45 
46     private static final class ChangeReport {
47         long mChangeId;
48         int mState;
49 
ChangeReport(long changeId, @State int state)50         ChangeReport(long changeId, @State int state) {
51             mChangeId = changeId;
52             mState = state;
53         }
54 
55         @Override
equals(Object o)56         public boolean equals(Object o) {
57             if (this == o) return true;
58             if (o == null || getClass() != o.getClass()) return false;
59             ChangeReport that = (ChangeReport) o;
60             return mChangeId == that.mChangeId
61                     && mState == that.mState;
62         }
63 
64         @Override
hashCode()65         public int hashCode() {
66             return Objects.hash(mChangeId, mState);
67         }
68     }
69 
70     // Maps uid to a set of ChangeReports (that were reported for that uid).
71     @GuardedBy("mReportedChanges")
72     private final Map<Integer, Set<ChangeReport>> mReportedChanges;
73 
74     // When true will of every time to debug (logcat).
75     private boolean mDebugLogAll;
76 
ChangeReporter(@ource int source)77     public ChangeReporter(@Source int source) {
78         mSource = source;
79         mReportedChanges =  new HashMap<>();
80         mDebugLogAll = false;
81     }
82 
83     /**
84      * Report the change to stats log and to the debug log if the change was not previously
85      * logged already.
86      *
87      * @param uid      affected by the change
88      * @param changeId the reported change id
89      * @param state    of the reported change - enabled/disabled/only logged
90      */
reportChange(int uid, long changeId, int state)91     public void reportChange(int uid, long changeId, int state) {
92         if (shouldWriteToStatsLog(uid, changeId, state)) {
93             FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
94                     changeId, state, mSource);
95         }
96         if (shouldWriteToDebug(uid, changeId, state)) {
97             debugLog(uid, changeId, state);
98         }
99         markAsReported(uid, new ChangeReport(changeId, state));
100     }
101 
102     /**
103      * Start logging all the time to logcat.
104      */
startDebugLogAll()105     public void startDebugLogAll() {
106         mDebugLogAll = true;
107     }
108 
109     /**
110      * Stop logging all the time to logcat.
111      */
stopDebugLogAll()112     public void stopDebugLogAll() {
113         mDebugLogAll = false;
114     }
115 
116 
117     /**
118      * Returns whether the next report should be logged to FrameworkStatsLog.
119      *
120      * @param uid      affected by the change
121      * @param changeId the reported change id
122      * @param state    of the reported change - enabled/disabled/only logged
123      * @return true if the report should be logged
124      */
125     @VisibleForTesting
shouldWriteToStatsLog(int uid, long changeId, int state)126     public boolean shouldWriteToStatsLog(int uid, long changeId, int state) {
127         return !isAlreadyReported(uid, new ChangeReport(changeId, state));
128     }
129 
130     /**
131      * Returns whether the next report should be logged to logcat.
132      *
133      * @param uid      affected by the change
134      * @param changeId the reported change id
135      * @param state    of the reported change - enabled/disabled/only logged
136      * @return true if the report should be logged
137      */
138     @VisibleForTesting
shouldWriteToDebug(int uid, long changeId, int state)139     public boolean shouldWriteToDebug(int uid, long changeId, int state) {
140         return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
141     }
142 
isAlreadyReported(int uid, ChangeReport report)143     private boolean isAlreadyReported(int uid, ChangeReport report) {
144         synchronized (mReportedChanges) {
145             Set<ChangeReport> reportedChangesForUid = mReportedChanges.get(uid);
146             if (reportedChangesForUid == null) {
147                 return false;
148             } else {
149                 return reportedChangesForUid.contains(report);
150             }
151         }
152     }
153 
markAsReported(int uid, ChangeReport report)154     private void markAsReported(int uid, ChangeReport report) {
155         synchronized (mReportedChanges) {
156             Set<ChangeReport> reportedChangesForUid = mReportedChanges.get(uid);
157             if (reportedChangesForUid == null) {
158                 mReportedChanges.put(uid, new HashSet<ChangeReport>());
159                 reportedChangesForUid = mReportedChanges.get(uid);
160             }
161             reportedChangesForUid.add(report);
162         }
163     }
164 
165     /**
166      * Clears the saved information about a given uid. Requests to report uid again will be reported
167      * regardless to the past reports.
168      *
169      * <p> Only intended to be called from PlatformCompat.
170      *
171      * @param uid to reset
172      */
resetReportedChanges(int uid)173     public void resetReportedChanges(int uid) {
174         synchronized (mReportedChanges) {
175             mReportedChanges.remove(uid);
176         }
177     }
178 
debugLog(int uid, long changeId, int state)179     private void debugLog(int uid, long changeId, int state) {
180         String message = formatSimple("Compat change id reported: %d; UID %d; state: %s", changeId,
181                 uid, stateToString(state));
182         if (mSource == SOURCE_SYSTEM_SERVER) {
183             Slog.d(TAG, message);
184         } else {
185             Log.d(TAG, message);
186         }
187 
188     }
189 
190     /**
191      * Transforms {@link #ChangeReporter.State} enum to a string.
192      *
193      * @param state to transform
194      * @return a string representing the state
195      */
stateToString(@tate int state)196     private static String stateToString(@State int state) {
197         switch (state) {
198             case STATE_LOGGED:
199                 return "LOGGED";
200             case STATE_ENABLED:
201                 return "ENABLED";
202             case STATE_DISABLED:
203                 return "DISABLED";
204             default:
205                 return "UNKNOWN";
206         }
207     }
208 
209     /** These values should be kept in sync with those in atoms.proto */
210     public static final int STATE_UNKNOWN_STATE =
211                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__UNKNOWN_STATE;
212     public static final int STATE_ENABLED =
213                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED;
214     public static final int STATE_DISABLED =
215                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED;
216     public static final int STATE_LOGGED =
217                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED;
218     public static final int SOURCE_UNKNOWN_SOURCE =
219                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__UNKNOWN_SOURCE;
220     public static final int SOURCE_APP_PROCESS =
221                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS;
222     public static final int SOURCE_SYSTEM_SERVER =
223                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER;
224 
225     @Retention(RetentionPolicy.SOURCE)
226     @IntDef(prefix = { "STATE_" }, value = {
227             STATE_UNKNOWN_STATE,
228             STATE_ENABLED,
229             STATE_DISABLED,
230             STATE_LOGGED
231     })
232     public @interface State {
233     }
234 
235     @Retention(RetentionPolicy.SOURCE)
236     @IntDef(prefix = { "SOURCE_" }, value = {
237             SOURCE_UNKNOWN_SOURCE,
238             SOURCE_APP_PROCESS,
239             SOURCE_SYSTEM_SERVER
240     })
241     public @interface Source {
242     }
243 }
244