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