1 /* 2 * Copyright (C) 2022 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.mms.service.metrics; 18 19 import static com.android.mms.MmsStatsLog.INCOMING_MMS; 20 import static com.android.mms.MmsStatsLog.OUTGOING_MMS; 21 22 import android.app.StatsManager; 23 import android.content.Context; 24 import android.util.Log; 25 import android.util.StatsEvent; 26 27 import androidx.annotation.VisibleForTesting; 28 29 import com.android.internal.util.ConcurrentUtils; 30 import com.android.mms.IncomingMms; 31 import com.android.mms.MmsStatsLog; 32 import com.android.mms.OutgoingMms; 33 34 import java.time.Duration; 35 import java.util.List; 36 37 /** 38 * Implements statsd pullers for Mms. 39 * 40 * <p>This class registers pullers to statsd, which will be called once a day to obtain mms 41 * statistics that cannot be sent to statsd in real time. 42 */ 43 public class MmsMetricsCollector implements StatsManager.StatsPullAtomCallback { 44 private static final String TAG = MmsMetricsCollector.class.getSimpleName(); 45 /** Disables various restrictions to ease debugging during development. */ 46 private static final boolean DBG = false; // STOPSHIP if true 47 private static final long MILLIS_PER_HOUR = Duration.ofHours(1).toMillis(); 48 private static final long MILLIS_PER_SECOND = Duration.ofSeconds(1).toMillis(); 49 /** 50 * Sets atom pull cool down to 23 hours to help enforcing privacy requirement. 51 * 52 * <p>Applies to certain atoms. The interval of 23 hours leaves some margin for pull operations 53 * that occur once a day. 54 */ 55 private static final long MIN_COOLDOWN_MILLIS = 56 DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR; 57 private final PersistMmsAtomsStorage mStorage; 58 private final StatsManager mStatsManager; 59 60 MmsMetricsCollector(Context context)61 public MmsMetricsCollector(Context context) { 62 this(context, new PersistMmsAtomsStorage(context)); 63 } 64 65 @VisibleForTesting MmsMetricsCollector(Context context, PersistMmsAtomsStorage storage)66 public MmsMetricsCollector(Context context, PersistMmsAtomsStorage storage) { 67 mStorage = storage; 68 mStatsManager = context.getSystemService(StatsManager.class); 69 if (mStatsManager != null) { 70 registerAtom(INCOMING_MMS); 71 registerAtom(OUTGOING_MMS); 72 Log.d(TAG, "[MmsMetricsCollector]: registered atoms"); 73 } else { 74 Log.e(TAG, "[MmsMetricsCollector]: could not get StatsManager, " 75 + "atoms not registered"); 76 } 77 } 78 buildStatsEvent(IncomingMms mms)79 private static StatsEvent buildStatsEvent(IncomingMms mms) { 80 return MmsStatsLog.buildStatsEvent( 81 INCOMING_MMS, 82 mms.getRat(), 83 mms.getResult(), 84 mms.getRoaming(), 85 mms.getSimSlotIndex(), 86 mms.getIsMultiSim(), 87 mms.getIsEsim(), 88 mms.getCarrierId(), 89 mms.getAvgIntervalMillis(), 90 mms.getMmsCount(), 91 mms.getRetryId(), 92 mms.getHandledByCarrierApp(), 93 mms.getIsManagedProfile(), 94 mms.getIsNtn(), 95 mms.getIsNbIotNtn()); 96 } 97 buildStatsEvent(OutgoingMms mms)98 private static StatsEvent buildStatsEvent(OutgoingMms mms) { 99 return MmsStatsLog.buildStatsEvent( 100 OUTGOING_MMS, 101 mms.getRat(), 102 mms.getResult(), 103 mms.getRoaming(), 104 mms.getSimSlotIndex(), 105 mms.getIsMultiSim(), 106 mms.getIsEsim(), 107 mms.getCarrierId(), 108 mms.getAvgIntervalMillis(), 109 mms.getMmsCount(), 110 mms.getIsFromDefaultApp(), 111 mms.getRetryId(), 112 mms.getHandledByCarrierApp(), 113 mms.getIsManagedProfile(), 114 mms.getIsNtn(), 115 mms.getIsNbIotNtn()); 116 } 117 118 @Override onPullAtom(int atomTag, List<StatsEvent> data)119 public int onPullAtom(int atomTag, List<StatsEvent> data) { 120 switch (atomTag) { 121 case INCOMING_MMS: 122 return pullIncomingMms(data); 123 case OUTGOING_MMS: 124 return pullOutgoingMms(data); 125 default: 126 Log.e(TAG, String.format("unexpected atom ID %d", atomTag)); 127 return StatsManager.PULL_SKIP; 128 } 129 } 130 pullIncomingMms(List<StatsEvent> data)131 private int pullIncomingMms(List<StatsEvent> data) { 132 List<IncomingMms> incomingMmsList = mStorage.getIncomingMms(MIN_COOLDOWN_MILLIS); 133 if (incomingMmsList != null) { 134 // MMS List is already shuffled when MMS were inserted. 135 incomingMmsList.forEach(mms -> data.add(buildStatsEvent(mms))); 136 return StatsManager.PULL_SUCCESS; 137 } else { 138 Log.w(TAG, "INCOMING_MMS pull too frequent, skipping"); 139 return StatsManager.PULL_SKIP; 140 } 141 } 142 pullOutgoingMms(List<StatsEvent> data)143 private int pullOutgoingMms(List<StatsEvent> data) { 144 List<OutgoingMms> outgoingMmsList = mStorage.getOutgoingMms(MIN_COOLDOWN_MILLIS); 145 if (outgoingMmsList != null) { 146 // MMS List is already shuffled when MMS were inserted. 147 outgoingMmsList.forEach(mms -> data.add(buildStatsEvent(mms))); 148 return StatsManager.PULL_SUCCESS; 149 } else { 150 Log.w(TAG, "OUTGOING_MMS pull too frequent, skipping"); 151 return StatsManager.PULL_SKIP; 152 } 153 } 154 155 /** Registers a pulled atom ID {@code atomId}. */ registerAtom(int atomId)156 private void registerAtom(int atomId) { 157 mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null, 158 ConcurrentUtils.DIRECT_EXECUTOR, this); 159 } 160 161 /** Returns the {@link PersistMmsAtomsStorage} backing the puller. */ getAtomsStorage()162 public PersistMmsAtomsStorage getAtomsStorage() { 163 return mStorage; 164 } 165 } 166