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__RESULT__MMS_RESULT_ERROR_UNSPECIFIED;
20 import static com.android.mms.MmsStatsLog.INCOMING_MMS__RESULT__MMS_RESULT_SUCCESS;
21 import static com.android.mms.MmsStatsLog.OUTGOING_MMS__RESULT__MMS_RESULT_ERROR_UNSPECIFIED;
22 import static com.android.mms.MmsStatsLog.OUTGOING_MMS__RESULT__MMS_RESULT_SUCCESS;
23 
24 import android.app.Activity;
25 import android.content.Context;
26 import android.os.Binder;
27 import android.os.SystemClock;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.telephony.ServiceState;
31 import android.telephony.SmsManager;
32 import android.telephony.SubscriptionInfo;
33 import android.telephony.SubscriptionManager;
34 import android.telephony.TelephonyManager;
35 import android.telephony.UiccCardInfo;
36 import android.util.Log;
37 
38 import com.android.internal.telephony.SmsApplication;
39 import com.android.internal.telephony.flags.Flags;
40 import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
41 import com.android.mms.IncomingMms;
42 import com.android.mms.OutgoingMms;
43 
44 import java.util.List;
45 
46 /** Collects mms events for the pulled atom. */
47 public class MmsStats {
48     private static final String TAG = MmsStats.class.getSimpleName();
49 
50     private final Context mContext;
51     private final PersistMmsAtomsStorage mPersistMmsAtomsStorage;
52     private final String mCallingPkg;
53     private final boolean mIsIncomingMms;
54     private final long mTimestamp;
55     private int mSubId;
56     private TelephonyManager mTelephonyManager;
57 
MmsStats(Context context, PersistMmsAtomsStorage persistMmsAtomsStorage, int subId, TelephonyManager telephonyManager, String callingPkg, boolean isIncomingMms)58     public MmsStats(Context context, PersistMmsAtomsStorage persistMmsAtomsStorage, int subId,
59             TelephonyManager telephonyManager, String callingPkg, boolean isIncomingMms) {
60         mContext = context;
61         mPersistMmsAtomsStorage = persistMmsAtomsStorage;
62         mSubId = subId;
63         mTelephonyManager = telephonyManager;
64         mCallingPkg = callingPkg;
65         mIsIncomingMms = isIncomingMms;
66         mTimestamp = SystemClock.elapsedRealtime();
67     }
68 
69     /** Updates subId and corresponding telephonyManager. */
updateSubId(int subId, TelephonyManager telephonyManager)70     public void updateSubId(int subId, TelephonyManager telephonyManager) {
71         mSubId = subId;
72         mTelephonyManager = telephonyManager;
73     }
74 
75     /** Adds incoming or outgoing mms atom to storage. */
addAtomToStorage(int result)76     public void addAtomToStorage(int result) {
77         addAtomToStorage(result, 0, false, 0);
78     }
79 
80     /** Adds incoming or outgoing mms atom to storage. */
addAtomToStorage(int result, int retryId, boolean handledByCarrierApp, long mMessageId)81     public void addAtomToStorage(int result, int retryId, boolean handledByCarrierApp,
82             long mMessageId) {
83         long identity = Binder.clearCallingIdentity();
84         try {
85             if (mIsIncomingMms) {
86                 onIncomingMms(result, retryId, handledByCarrierApp);
87             } else {
88                 onOutgoingMms(result, retryId, handledByCarrierApp);
89             }
90             if (isUsingNonTerrestrialNetwork()) {
91                 CarrierRoamingSatelliteSessionStats carrierRoamingSatelliteSessionStats =
92                         CarrierRoamingSatelliteSessionStats.getInstance(mSubId);
93                 carrierRoamingSatelliteSessionStats.onMms(mIsIncomingMms, mMessageId);
94             }
95         } finally {
96             Binder.restoreCallingIdentity(identity);
97         }
98     }
99 
100     /** Creates a new atom when MMS is received. */
onIncomingMms(int result, int retryId, boolean handledByCarrierApp)101     private void onIncomingMms(int result, int retryId, boolean handledByCarrierApp) {
102         IncomingMms incomingMms = IncomingMms.newBuilder()
103                 .setRat(getDataNetworkType())
104                 .setResult(getIncomingMmsResult(result))
105                 .setRoaming(getDataRoamingType())
106                 .setSimSlotIndex(getSlotIndex())
107                 .setIsMultiSim(getIsMultiSim())
108                 .setIsEsim(getIsEuicc())
109                 .setCarrierId(getSimCarrierId())
110                 .setAvgIntervalMillis(getInterval())
111                 .setMmsCount(1)
112                 .setRetryId(retryId)
113                 .setHandledByCarrierApp(handledByCarrierApp)
114                 .setIsManagedProfile(isManagedProfile())
115                 .setIsNtn(isUsingNonTerrestrialNetwork())
116                 .build();
117         mPersistMmsAtomsStorage.addIncomingMms(incomingMms);
118     }
119 
120     /** Creates a new atom when MMS is sent. */
onOutgoingMms(int result, int retryId, boolean handledByCarrierApp)121     private void onOutgoingMms(int result, int retryId, boolean handledByCarrierApp) {
122         OutgoingMms outgoingMms = OutgoingMms.newBuilder()
123                 .setRat(getDataNetworkType())
124                 .setResult(getOutgoingMmsResult(result))
125                 .setRoaming(getDataRoamingType())
126                 .setSimSlotIndex(getSlotIndex())
127                 .setIsMultiSim(getIsMultiSim())
128                 .setIsEsim(getIsEuicc())
129                 .setCarrierId(getSimCarrierId())
130                 .setAvgIntervalMillis(getInterval())
131                 .setMmsCount(1)
132                 .setIsFromDefaultApp(isDefaultMmsApp())
133                 .setRetryId(retryId)
134                 .setHandledByCarrierApp(handledByCarrierApp)
135                 .setIsManagedProfile(isManagedProfile())
136                 .setIsNtn(isUsingNonTerrestrialNetwork())
137                 .build();
138         mPersistMmsAtomsStorage.addOutgoingMms(outgoingMms);
139     }
140 
141     /** @return {@code true} if this SIM is dedicated to work profile */
isManagedProfile()142     private boolean isManagedProfile() {
143         SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
144         if (subManager == null || !subManager.isActiveSubscriptionId(mSubId)) return false;
145         UserHandle userHandle = subManager.getSubscriptionUserHandle(mSubId);
146         UserManager userManager = mContext.getSystemService(UserManager.class);
147         if (userHandle == null || userManager == null) return false;
148         return userManager.isManagedProfile(userHandle.getIdentifier());
149     }
150 
151     /** Returns data network type of current subscription. */
getDataNetworkType()152     private int getDataNetworkType() {
153         return mTelephonyManager.getDataNetworkType();
154     }
155 
156     /** Returns incoming mms result. */
getIncomingMmsResult(int result)157     private int getIncomingMmsResult(int result) {
158         switch (result) {
159             case SmsManager.MMS_ERROR_UNSPECIFIED:
160                 // SmsManager.MMS_ERROR_UNSPECIFIED(1) -> MMS_RESULT_ERROR_UNSPECIFIED(0)
161                 return INCOMING_MMS__RESULT__MMS_RESULT_ERROR_UNSPECIFIED;
162             case Activity.RESULT_OK:
163                 // Activity.RESULT_OK -> MMS_RESULT_SUCCESS(1)
164                 return INCOMING_MMS__RESULT__MMS_RESULT_SUCCESS;
165             default:
166                 // Int value of other SmsManager.MMS_ERROR matches MMS_RESULT_ERROR
167                 return result;
168         }
169     }
170 
171     /** Returns outgoing mms result. */
getOutgoingMmsResult(int result)172     private int getOutgoingMmsResult(int result) {
173         switch (result) {
174             case SmsManager.MMS_ERROR_UNSPECIFIED:
175                 // SmsManager.MMS_ERROR_UNSPECIFIED(1) -> MMS_RESULT_ERROR_UNSPECIFIED(0)
176                 return OUTGOING_MMS__RESULT__MMS_RESULT_ERROR_UNSPECIFIED;
177             case Activity.RESULT_OK:
178                 // Activity.RESULT_OK -> MMS_RESULT_SUCCESS(1)
179                 return OUTGOING_MMS__RESULT__MMS_RESULT_SUCCESS;
180             default:
181                 // Int value of other SmsManager.MMS_ERROR matches MMS_RESULT_ERROR
182                 return result;
183         }
184     }
185 
186     /** Returns data network roaming type of current subscription. */
getDataRoamingType()187     private int getDataRoamingType() {
188         ServiceState serviceState = mTelephonyManager.getServiceState();
189         return (serviceState != null) ? serviceState.getDataRoamingType() :
190                 ServiceState.ROAMING_TYPE_NOT_ROAMING;
191     }
192 
193     /** Returns slot index associated with the subscription. */
getSlotIndex()194     private int getSlotIndex() {
195         return SubscriptionManager.getSlotIndex(mSubId);
196     }
197 
198     /** Returns whether the device has multiple active SIM profiles. */
getIsMultiSim()199     private boolean getIsMultiSim() {
200         SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
201         if(subManager == null) {
202             return false;
203         }
204         if (Flags.workProfileApiSplit()) {
205             subManager = subManager.createForAllUserProfiles();
206         }
207         List<SubscriptionInfo> activeSubscriptionInfo = subManager.getActiveSubscriptionInfoList();
208         return (activeSubscriptionInfo.size() > 1);
209     }
210 
211     /** Returns if current subscription is embedded subscription. */
getIsEuicc()212     private boolean getIsEuicc() {
213         List<UiccCardInfo> uiccCardInfoList = mTelephonyManager.getUiccCardsInfo();
214         for (UiccCardInfo card : uiccCardInfoList) {
215             if (card.getPhysicalSlotIndex() == getSlotIndex()) {
216                 return card.isEuicc();
217             }
218         }
219         return false;
220     }
221 
222     /** Returns carrier id of the current subscription used by MMS. */
getSimCarrierId()223     private int getSimCarrierId() {
224         return mTelephonyManager.getSimCarrierId();
225     }
226 
227     /** Returns if the MMS was originated from the default MMS application. */
isDefaultMmsApp()228     private boolean isDefaultMmsApp() {
229         UserHandle userHandle = null;
230         SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
231         if ((subManager != null) && (subManager.isActiveSubscriptionId(mSubId))) {
232             userHandle = subManager.getSubscriptionUserHandle(mSubId);
233         }
234         return SmsApplication.isDefaultMmsApplicationAsUser(mContext, mCallingPkg, userHandle);
235     }
236 
237     /** Determines whether device is non-terrestrial network or not. */
isUsingNonTerrestrialNetwork()238     private boolean isUsingNonTerrestrialNetwork() {
239         if (!Flags.carrierEnabledSatelliteFlag()) {
240             return false;
241         }
242 
243         ServiceState ss = mTelephonyManager.getServiceState();
244         if (ss != null) {
245             return ss.isUsingNonTerrestrialNetwork();
246         } else {
247             Log.e(TAG, "isUsingNonTerrestrialNetwork(): ServiceState is null");
248         }
249         return false;
250     }
251 
252     /**
253      * Returns the interval in milliseconds between sending/receiving MMS message and current time.
254      * Calculates the time taken to send message to the network
255      * or download message from the network.
256      */
getInterval()257     private long getInterval() {
258         return (SystemClock.elapsedRealtime() - mTimestamp);
259     }
260 }
261