1 /* 2 * Copyright (C) 2024 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.telecom.metrics; 18 19 import static com.android.server.telecom.TelecomStatsLog.CALL_AUDIO_ROUTE_STATS; 20 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS; 21 import static com.android.server.telecom.TelecomStatsLog.TELECOM_API_STATS; 22 import static com.android.server.telecom.TelecomStatsLog.TELECOM_ERROR_STATS; 23 import static com.android.server.telecom.TelecomStatsLog.TELECOM_EVENT_STATS; 24 25 import android.annotation.NonNull; 26 import android.app.StatsManager; 27 import android.content.Context; 28 import android.os.Binder; 29 import android.os.HandlerThread; 30 import android.telecom.Log; 31 import android.util.StatsEvent; 32 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.modules.utils.HandlerExecutor; 36 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.concurrent.ConcurrentHashMap; 41 import java.util.concurrent.atomic.AtomicBoolean; 42 43 public class TelecomMetricsController implements StatsManager.StatsPullAtomCallback { 44 45 private static final String TAG = TelecomMetricsController.class.getSimpleName(); 46 47 private final Context mContext; 48 private final HandlerThread mHandlerThread; 49 private final ConcurrentHashMap<Integer, TelecomPulledAtom> mStats = new ConcurrentHashMap<>(); 50 private final AtomicBoolean mIsTestMode = new AtomicBoolean(false); 51 TelecomMetricsController(@onNull Context context, @NonNull HandlerThread handlerThread)52 private TelecomMetricsController(@NonNull Context context, 53 @NonNull HandlerThread handlerThread) { 54 mContext = context; 55 mHandlerThread = handlerThread; 56 } 57 58 @NonNull make(@onNull Context context)59 public static TelecomMetricsController make(@NonNull Context context) { 60 Log.i(TAG, "TMC.m1"); 61 HandlerThread handlerThread = new HandlerThread(TAG); 62 handlerThread.start(); 63 return make(context, handlerThread); 64 } 65 66 @VisibleForTesting 67 @NonNull make(@onNull Context context, @NonNull HandlerThread handlerThread)68 public static TelecomMetricsController make(@NonNull Context context, 69 @NonNull HandlerThread handlerThread) { 70 Log.i(TAG, "TMC.m2"); 71 Objects.requireNonNull(context); 72 Objects.requireNonNull(handlerThread); 73 return new TelecomMetricsController(context, handlerThread); 74 } 75 76 @NonNull getApiStats()77 public ApiStats getApiStats() { 78 ApiStats stats = (ApiStats) mStats.get(TELECOM_API_STATS); 79 if (stats == null) { 80 long token = Binder.clearCallingIdentity(); 81 try { 82 stats = new ApiStats(mContext, mHandlerThread.getLooper(), isTestMode()); 83 registerAtom(stats.getTag(), stats); 84 } finally { 85 Binder.restoreCallingIdentity(token); 86 } 87 } 88 return stats; 89 } 90 91 @NonNull getAudioRouteStats()92 public AudioRouteStats getAudioRouteStats() { 93 AudioRouteStats stats = (AudioRouteStats) mStats.get(CALL_AUDIO_ROUTE_STATS); 94 if (stats == null) { 95 stats = new AudioRouteStats(mContext, mHandlerThread.getLooper(), isTestMode()); 96 registerAtom(stats.getTag(), stats); 97 } 98 return stats; 99 } 100 101 @NonNull getCallStats()102 public CallStats getCallStats() { 103 CallStats stats = (CallStats) mStats.get(CALL_STATS); 104 if (stats == null) { 105 stats = new CallStats(mContext, mHandlerThread.getLooper(), isTestMode()); 106 registerAtom(stats.getTag(), stats); 107 } 108 return stats; 109 } 110 111 @NonNull getErrorStats()112 public ErrorStats getErrorStats() { 113 ErrorStats stats = (ErrorStats) mStats.get(TELECOM_ERROR_STATS); 114 if (stats == null) { 115 stats = new ErrorStats(mContext, mHandlerThread.getLooper(), isTestMode()); 116 registerAtom(stats.getTag(), stats); 117 } 118 return stats; 119 } 120 121 @NonNull getEventStats()122 public EventStats getEventStats() { 123 EventStats stats = (EventStats) mStats.get(TELECOM_EVENT_STATS); 124 if (stats == null) { 125 stats = new EventStats(mContext, mHandlerThread.getLooper(), isTestMode()); 126 registerAtom(stats.getTag(), stats); 127 } 128 return stats; 129 } 130 131 @Override onPullAtom(final int atomTag, final List<StatsEvent> data)132 public int onPullAtom(final int atomTag, final List<StatsEvent> data) { 133 if (mStats.containsKey(atomTag)) { 134 return Objects.requireNonNull(mStats.get(atomTag)).pull(data); 135 } 136 return StatsManager.PULL_SKIP; 137 } 138 139 @VisibleForTesting getStats()140 public Map<Integer, TelecomPulledAtom> getStats() { 141 return mStats; 142 } 143 144 @VisibleForTesting registerAtom(int tag, TelecomPulledAtom atom)145 public void registerAtom(int tag, TelecomPulledAtom atom) { 146 final StatsManager statsManager = mContext.getSystemService(StatsManager.class); 147 if (statsManager != null) { 148 statsManager.setPullAtomCallback(tag, null, new HandlerExecutor(atom), this); 149 mStats.put(tag, atom); 150 } else { 151 Log.w(TAG, "Unable to register the pulled atom as StatsManager is null"); 152 } 153 } 154 destroy()155 public void destroy() { 156 clearStats(); 157 mHandlerThread.quitSafely(); 158 } 159 setTestMode(boolean enabled)160 public void setTestMode(boolean enabled) { 161 mIsTestMode.set(enabled); 162 clearStats(); 163 } 164 isTestMode()165 public boolean isTestMode() { 166 return mIsTestMode.get(); 167 } 168 clearStats()169 private void clearStats() { 170 final StatsManager statsManager = mContext.getSystemService(StatsManager.class); 171 if (statsManager != null) { 172 mStats.forEach((tag, stat) -> { 173 statsManager.clearPullAtomCallback(tag); 174 stat.flush(); 175 }); 176 } else { 177 Log.w(TAG, "Unable to clear pulled atoms as StatsManager is null"); 178 } 179 180 mStats.clear(); 181 } 182 } 183