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