/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.service.metrics; import static com.android.mms.MmsStatsLog.INCOMING_MMS; import static com.android.mms.MmsStatsLog.OUTGOING_MMS; import android.app.StatsManager; import android.content.Context; import android.util.Log; import android.util.StatsEvent; import androidx.annotation.VisibleForTesting; import com.android.internal.util.ConcurrentUtils; import com.android.mms.IncomingMms; import com.android.mms.MmsStatsLog; import com.android.mms.OutgoingMms; import java.time.Duration; import java.util.List; /** * Implements statsd pullers for Mms. * *

This class registers pullers to statsd, which will be called once a day to obtain mms * statistics that cannot be sent to statsd in real time. */ public class MmsMetricsCollector implements StatsManager.StatsPullAtomCallback { private static final String TAG = MmsMetricsCollector.class.getSimpleName(); /** Disables various restrictions to ease debugging during development. */ private static final boolean DBG = false; // STOPSHIP if true private static final long MILLIS_PER_HOUR = Duration.ofHours(1).toMillis(); private static final long MILLIS_PER_SECOND = Duration.ofSeconds(1).toMillis(); /** * Sets atom pull cool down to 23 hours to help enforcing privacy requirement. * *

Applies to certain atoms. The interval of 23 hours leaves some margin for pull operations * that occur once a day. */ private static final long MIN_COOLDOWN_MILLIS = DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR; private final PersistMmsAtomsStorage mStorage; private final StatsManager mStatsManager; public MmsMetricsCollector(Context context) { this(context, new PersistMmsAtomsStorage(context)); } @VisibleForTesting public MmsMetricsCollector(Context context, PersistMmsAtomsStorage storage) { mStorage = storage; mStatsManager = context.getSystemService(StatsManager.class); if (mStatsManager != null) { registerAtom(INCOMING_MMS); registerAtom(OUTGOING_MMS); Log.d(TAG, "[MmsMetricsCollector]: registered atoms"); } else { Log.e(TAG, "[MmsMetricsCollector]: could not get StatsManager, " + "atoms not registered"); } } private static StatsEvent buildStatsEvent(IncomingMms mms) { return MmsStatsLog.buildStatsEvent( INCOMING_MMS, mms.getRat(), mms.getResult(), mms.getRoaming(), mms.getSimSlotIndex(), mms.getIsMultiSim(), mms.getIsEsim(), mms.getCarrierId(), mms.getAvgIntervalMillis(), mms.getMmsCount(), mms.getRetryId(), mms.getHandledByCarrierApp(), mms.getIsManagedProfile(), mms.getIsNtn(), mms.getIsNbIotNtn()); } private static StatsEvent buildStatsEvent(OutgoingMms mms) { return MmsStatsLog.buildStatsEvent( OUTGOING_MMS, mms.getRat(), mms.getResult(), mms.getRoaming(), mms.getSimSlotIndex(), mms.getIsMultiSim(), mms.getIsEsim(), mms.getCarrierId(), mms.getAvgIntervalMillis(), mms.getMmsCount(), mms.getIsFromDefaultApp(), mms.getRetryId(), mms.getHandledByCarrierApp(), mms.getIsManagedProfile(), mms.getIsNtn(), mms.getIsNbIotNtn()); } @Override public int onPullAtom(int atomTag, List data) { switch (atomTag) { case INCOMING_MMS: return pullIncomingMms(data); case OUTGOING_MMS: return pullOutgoingMms(data); default: Log.e(TAG, String.format("unexpected atom ID %d", atomTag)); return StatsManager.PULL_SKIP; } } private int pullIncomingMms(List data) { List incomingMmsList = mStorage.getIncomingMms(MIN_COOLDOWN_MILLIS); if (incomingMmsList != null) { // MMS List is already shuffled when MMS were inserted. incomingMmsList.forEach(mms -> data.add(buildStatsEvent(mms))); return StatsManager.PULL_SUCCESS; } else { Log.w(TAG, "INCOMING_MMS pull too frequent, skipping"); return StatsManager.PULL_SKIP; } } private int pullOutgoingMms(List data) { List outgoingMmsList = mStorage.getOutgoingMms(MIN_COOLDOWN_MILLIS); if (outgoingMmsList != null) { // MMS List is already shuffled when MMS were inserted. outgoingMmsList.forEach(mms -> data.add(buildStatsEvent(mms))); return StatsManager.PULL_SUCCESS; } else { Log.w(TAG, "OUTGOING_MMS pull too frequent, skipping"); return StatsManager.PULL_SKIP; } } /** Registers a pulled atom ID {@code atomId}. */ private void registerAtom(int atomId) { mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null, ConcurrentUtils.DIRECT_EXECUTOR, this); } /** Returns the {@link PersistMmsAtomsStorage} backing the puller. */ public PersistMmsAtomsStorage getAtomsStorage() { return mStorage; } }