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