• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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