1 /*
2  * Copyright (C) 2020 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.internal.telephony.metrics;
18 
19 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
20 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
21 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERING;
22 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS;
23 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
24 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
25 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
26 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
27 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
28 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
29 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR;
30 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
31 import static android.util.Patterns.EMAIL_ADDRESS;
32 
33 import android.annotation.Nullable;
34 import android.os.SystemClock;
35 import android.telephony.AccessNetworkConstants;
36 import android.telephony.AccessNetworkConstants.TransportType;
37 import android.telephony.Annotation.NetworkType;
38 import android.telephony.TelephonyManager;
39 import android.telephony.ims.ImsReasonInfo;
40 import android.telephony.ims.ProvisioningManager;
41 import android.telephony.ims.RegistrationManager.ImsRegistrationState;
42 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
43 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability;
44 import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.telephony.Phone;
48 import com.android.internal.telephony.PhoneFactory;
49 import com.android.internal.telephony.imsphone.ImsPhone;
50 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
51 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
52 import com.android.telephony.Rlog;
53 
54 import java.util.regex.Pattern;
55 
56 /** Tracks IMS registration metrics for each phone. */
57 public class ImsStats {
58     private static final String TAG = ImsStats.class.getSimpleName();
59 
60     /**
61      * Minimal duration of the registration state.
62      *
63      * <p>Registration state (including changes in capable/available features) with duration shorter
64      * than this will be ignored as they are considered transient states.
65      */
66     private static final long MIN_REGISTRATION_DURATION_MILLIS = 1L * SECOND_IN_MILLIS;
67 
68     /**
69      * Maximum length of the extra message in the termination reason.
70      *
71      * <p>If the extra message is longer than this length, it will be truncated.
72      */
73     private static final int MAX_EXTRA_MESSAGE_LENGTH = 128;
74 
75     /** Pattern used to match UUIDs in IMS extra messages for filtering. */
76     private static final Pattern PATTERN_UUID =
77             Pattern.compile(
78                     "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
79 
80     /** Replacement for UUIDs. */
81     private static final String REPLACEMENT_UUID = "<UUID_REDACTED>";
82 
83     /**
84      * Pattern used to match URI (e.g. sip, tel) in IMS extra messages for filtering.
85      *
86      * <p>NOTE: this simple pattern aims to catch the most common URI schemes. It is not meant to be
87      * RFC-complaint.
88      */
89     private static final Pattern PATTERN_URI =
90             Pattern.compile("([a-zA-Z]{2,}:)" + EMAIL_ADDRESS.pattern());
91 
92     /** Replacement for URI. */
93     private static final String REPLACEMENT_URI = "$1<REDACTED>";
94 
95     /**
96      * Pattern used to match IPv4 addresses in IMS extra messages for filtering.
97      *
98      * <p>This is a copy of {@code android.util.Patterns.IP_ADDRESS}, which is deprecated and might
99      * be removed in the future.
100      */
101     private static final Pattern PATTERN_IPV4 =
102             Pattern.compile(
103                     "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
104                             + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
105                             + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
106                             + "|[1-9][0-9]|[0-9]))");
107 
108     /** Replacement for IPv4 addresses. */
109     private static final String REPLACEMENT_IPV4 = "<IPV4_REDACTED>";
110 
111     /**
112      * Pattern used to match IPv6 addresses in IMS extra messages for filtering.
113      *
114      * <p>NOTE: this pattern aims to catch the most common IPv6 addresses. It is not meant to be
115      * RFC-complaint or free of false positives.
116      */
117     private static final Pattern PATTERN_IPV6 =
118             Pattern.compile(
119                     // Full address
120                     "([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}"
121                             // Abbreviated address, e.g. 2001:4860:4860::8888
122                             + "|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1,6}"
123                             // Abbreviated network address, e.g. 2607:F8B0::
124                             + "|([0-9a-fA-F]{1,4}:){1,7}:"
125                             // Abbreviated address, e.g. ::1
126                             + "|:(:[0-9a-fA-F]{1,4}){1,7}");
127 
128     /** Replacement for IPv6 addresses. */
129     private static final String REPLACEMENT_IPV6 = "<IPV6_REDACTED>";
130 
131     /**
132      * Pattern used to match potential IMEI values in IMS extra messages for filtering.
133      *
134      * <p>This includes segmented IMEI or IMEI/SV, as well as unsegmented IMEI/SV.
135      */
136     private static final Pattern PATTERN_IMEI =
137             Pattern.compile(
138                     "(^|[^0-9])(?:"
139                             // IMEI, AABBBBBB-CCCCCC-D format; IMEI/SV, AABBBBBB-CCCCCC-EE format
140                             + "[0-9]{8}-[0-9]{6}-[0-9][0-9]?"
141                             // IMEI, AA-BBBBBB-CCCCCC-D format; IMEI/SV, AA-BBBBBB-CCCCCC-EE format
142                             + "|[0-9]{2}-[0-9]{6}-[0-9]{6}-[0-9][0-9]?"
143                             // IMEI/SV, unsegmented
144                             + "|[0-9]{16}"
145                             + ")($|[^0-9])");
146 
147     /** Replacement for IMEI. */
148     private static final String REPLACEMENT_IMEI = "$1<IMEI_REDACTED>$2";
149 
150     /**
151      * Pattern used to match potential unsegmented IMEI/IMSI values in IMS extra messages for
152      * filtering.
153      */
154     private static final Pattern PATTERN_UNSEGMENTED_IMEI_IMSI =
155             Pattern.compile("(^|[^0-9])[0-9]{15}($|[^0-9])");
156 
157     /** Replacement for unsegmented IMEI/IMSI. */
158     private static final String REPLACEMENT_UNSEGMENTED_IMEI_IMSI = "$1<IMEI_IMSI_REDACTED>$2";
159 
160     /**
161      * Pattern used to match hostnames in IMS extra messages for filtering.
162      *
163      * <p>This pattern differs from {@link android.util.Patterns.DOMAIN_NAME} in a few ways: it
164      * requires the name to have at least 3 segments (shorter names are nearly always public or
165      * typos, i.e. missing space after period), does not check the validity of TLDs, and does not
166      * support punycodes in TLDs.
167      */
168     private static final Pattern PATTERN_HOSTNAME =
169             Pattern.compile("([0-9a-zA-Z][0-9a-zA-Z_\\-]{0,61}[0-9a-zA-Z]\\.){2,}[a-zA-Z]{2,}");
170 
171     /** Replacement for hostnames. */
172     private static final String REPLACEMENT_HOSTNAME = "<HOSTNAME_REDACTED>";
173 
174     /**
175      * Pattern used to match potential IDs in IMS extra messages for filtering.
176      *
177      * <p>This pattern target numbers that are potential IDs in unknown formats. It should be
178      * replaced after all other replacements are done to ensure complete and correct filtering.
179      *
180      * <p>Specifically, this pattern looks for any number (including hex) that is separated by dots
181      * or dashes has at least 6 significant digits, and any unsegmented numbers that has at least 5
182      * significant digits.
183      */
184     private static final Pattern PATTERN_UNKNOWN_ID =
185             Pattern.compile(
186                     "(^|[^0-9a-fA-F])(([-\\.]?0)*[1-9a-fA-F]([-\\.]?[0-9a-fA-F]){5,}"
187                             + "|0*[1-9a-fA-F]([0-9a-fA-F]){4,})");
188 
189     /** Replacement for potential IDs. */
190     private static final String REPLACEMENT_UNKNOWN_ID = "$1<ID_REDACTED>";
191 
192     private final ImsPhone mPhone;
193     private final PersistAtomsStorage mStorage;
194 
195     @ImsRegistrationState private int mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
196 
197     private long mLastTimestamp;
198     @Nullable private ImsRegistrationStats mLastRegistrationStats;
199 
200     // Available features are those reported by ImsService to be available for use.
201     private MmTelCapabilities mLastAvailableFeatures = new MmTelCapabilities();
202 
203     // Capable features (enabled by device/carrier). Theses are available before IMS is registered
204     // and not necessarily updated when RAT changes.
205     private final MmTelCapabilities mLastWwanCapableFeatures = new MmTelCapabilities();
206     private final MmTelCapabilities mLastWlanCapableFeatures = new MmTelCapabilities();
207 
ImsStats(ImsPhone phone)208     public ImsStats(ImsPhone phone) {
209         mPhone = phone;
210         mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();
211     }
212 
213     /**
214      * Finalizes the durations of the current IMS registration stats segment.
215      *
216      * <p>This method is also invoked whenever the registration state, feature capability, or
217      * feature availability changes.
218      */
conclude()219     public synchronized void conclude() {
220         long now = getTimeMillis();
221 
222         // Currently not tracking time spent on registering.
223         if (mLastRegistrationState == REGISTRATION_STATE_REGISTERED) {
224             ImsRegistrationStats stats = copyOf(mLastRegistrationStats);
225             long duration = now - mLastTimestamp;
226 
227             if (duration < MIN_REGISTRATION_DURATION_MILLIS) {
228                 logw("conclude: discarding transient stats, duration=%d", duration);
229             } else {
230                 stats.registeredMillis = duration;
231 
232                 stats.voiceAvailableMillis =
233                         mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
234                 stats.videoAvailableMillis =
235                         mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
236                 stats.utAvailableMillis =
237                         mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
238                 stats.smsAvailableMillis =
239                         mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
240 
241                 MmTelCapabilities lastCapableFeatures =
242                         stats.rat == TelephonyManager.NETWORK_TYPE_IWLAN
243                                 ? mLastWlanCapableFeatures
244                                 : mLastWwanCapableFeatures;
245                 stats.voiceCapableMillis =
246                         lastCapableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
247                 stats.videoCapableMillis =
248                         lastCapableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
249                 stats.utCapableMillis =
250                         lastCapableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
251                 stats.smsCapableMillis =
252                         lastCapableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
253 
254                 mStorage.addImsRegistrationStats(stats);
255             }
256         }
257 
258         mLastTimestamp = now;
259     }
260 
261     /** Updates the stats when registered features changed. */
onImsCapabilitiesChanged( @msRegistrationTech int radioTech, MmTelCapabilities capabilities)262     public synchronized void onImsCapabilitiesChanged(
263             @ImsRegistrationTech int radioTech, MmTelCapabilities capabilities) {
264         conclude();
265 
266         boolean ratChanged = false;
267         @NetworkType int newRat = convertRegistrationTechToNetworkType(radioTech);
268         if (mLastRegistrationStats != null && mLastRegistrationStats.rat != newRat) {
269             mLastRegistrationStats.rat = newRat;
270             ratChanged = true;
271         }
272 
273         boolean voiceAvailableNow = capabilities.isCapable(CAPABILITY_TYPE_VOICE);
274         boolean voiceAvailabilityChanged =
275                 (mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) != voiceAvailableNow);
276         mLastAvailableFeatures = capabilities;
277 
278         // Notify voice RAT change if 1. RAT changed while voice over IMS is available, or 2. voice
279         // over IMS availability changed
280         if ((ratChanged && voiceAvailableNow) || voiceAvailabilityChanged) {
281             mPhone.getDefaultPhone().getServiceStateTracker().getServiceStateStats()
282                     .onImsVoiceRegistrationChanged();
283         }
284     }
285 
286     /** Updates the stats when capable features changed. */
onSetFeatureResponse( @mTelCapability int feature, @ImsRegistrationTech int network, int value)287     public synchronized void onSetFeatureResponse(
288             @MmTelCapability int feature, @ImsRegistrationTech int network, int value) {
289         MmTelCapabilities lastCapableFeatures = getLastCapableFeaturesForTech(network);
290         if (lastCapableFeatures != null) {
291             conclude();
292             if (value == ProvisioningManager.PROVISIONING_VALUE_ENABLED) {
293                 lastCapableFeatures.addCapabilities(feature);
294             } else {
295                 lastCapableFeatures.removeCapabilities(feature);
296             }
297         }
298     }
299 
300     /** Updates the stats when IMS registration is progressing. */
onImsRegistering(@ransportType int imsRadioTech)301     public synchronized void onImsRegistering(@TransportType int imsRadioTech) {
302         conclude();
303 
304         mLastRegistrationStats = getDefaultImsRegistrationStats();
305         mLastRegistrationStats.rat = convertTransportTypeToNetworkType(imsRadioTech);
306         mLastRegistrationState = REGISTRATION_STATE_REGISTERING;
307     }
308 
309     /** Updates the stats when IMS registration succeeds. */
onImsRegistered(@ransportType int imsRadioTech)310     public synchronized void onImsRegistered(@TransportType int imsRadioTech) {
311         conclude();
312 
313         // NOTE: mLastRegistrationStats can be null (no registering phase).
314         if (mLastRegistrationStats == null) {
315             mLastRegistrationStats = getDefaultImsRegistrationStats();
316         }
317         mLastRegistrationStats.rat = convertTransportTypeToNetworkType(imsRadioTech);
318         mLastRegistrationState = REGISTRATION_STATE_REGISTERED;
319     }
320 
321     /** Updates the stats and generates a termination atom when IMS registration fails/ends. */
onImsUnregistered(ImsReasonInfo reasonInfo)322     public synchronized void onImsUnregistered(ImsReasonInfo reasonInfo) {
323         conclude();
324 
325         // Generate end reason atom.
326         // NOTE: mLastRegistrationStats can be null (no registering phase).
327         ImsRegistrationTermination termination = new ImsRegistrationTermination();
328         if (mLastRegistrationStats != null) {
329             termination.carrierId = mLastRegistrationStats.carrierId;
330             termination.ratAtEnd = getRatAtEnd(mLastRegistrationStats.rat);
331         } else {
332             termination.carrierId = mPhone.getDefaultPhone().getCarrierId();
333             // We cannot tell whether the registration was intended for WWAN or WLAN
334             termination.ratAtEnd = TelephonyManager.NETWORK_TYPE_UNKNOWN;
335         }
336         termination.isMultiSim = SimSlotState.isMultiSim();
337         termination.setupFailed = (mLastRegistrationState != REGISTRATION_STATE_REGISTERED);
338         termination.reasonCode = reasonInfo.getCode();
339         termination.extraCode = reasonInfo.getExtraCode();
340         termination.extraMessage = filterExtraMessage(reasonInfo.getExtraMessage());
341         termination.count = 1;
342         mStorage.addImsRegistrationTermination(termination);
343 
344         // Reset state to unregistered.
345         mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
346         mLastRegistrationStats = null;
347         mLastAvailableFeatures = new MmTelCapabilities();
348     }
349 
350     /**
351      * Returns the current RAT used for IMS voice registration, or {@link
352      * TelephonyManager#NETWORK_TYPE_UNKNOWN} if there isn't any.
353      */
354     @NetworkType
355     @VisibleForTesting
getImsVoiceRadioTech()356     public synchronized int getImsVoiceRadioTech() {
357         if (mLastRegistrationStats == null
358                 || !mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE)) {
359             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
360         }
361         return mLastRegistrationStats.rat;
362     }
363 
364     @NetworkType
getRatAtEnd(@etworkType int lastStateRat)365     private int getRatAtEnd(@NetworkType int lastStateRat) {
366         return lastStateRat == TelephonyManager.NETWORK_TYPE_IWLAN ? lastStateRat : getWwanPsRat();
367     }
368 
369     @NetworkType
convertTransportTypeToNetworkType(@ransportType int transportType)370     private int convertTransportTypeToNetworkType(@TransportType int transportType) {
371         switch (transportType) {
372             case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
373                 return getWwanPsRat();
374             case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
375                 return TelephonyManager.NETWORK_TYPE_IWLAN;
376             default:
377                 return TelephonyManager.NETWORK_TYPE_UNKNOWN;
378         }
379     }
380 
381     @NetworkType
getWwanPsRat()382     private int getWwanPsRat() {
383         return ServiceStateStats.getDataRat(mPhone.getServiceStateTracker().getServiceState());
384     }
385 
getDefaultImsRegistrationStats()386     private ImsRegistrationStats getDefaultImsRegistrationStats() {
387         Phone phone = mPhone.getDefaultPhone();
388         ImsRegistrationStats stats = new ImsRegistrationStats();
389         stats.carrierId = phone.getCarrierId();
390         stats.simSlotIndex = phone.getPhoneId();
391         return stats;
392     }
393 
394     @Nullable
getLastCapableFeaturesForTech(@msRegistrationTech int radioTech)395     private MmTelCapabilities getLastCapableFeaturesForTech(@ImsRegistrationTech int radioTech) {
396         switch (radioTech) {
397             case REGISTRATION_TECH_NONE:
398                 return null;
399             case REGISTRATION_TECH_IWLAN:
400                 return mLastWlanCapableFeatures;
401             default:
402                 return mLastWwanCapableFeatures;
403         }
404     }
405 
406     @NetworkType
convertRegistrationTechToNetworkType(@msRegistrationTech int radioTech)407     private int convertRegistrationTechToNetworkType(@ImsRegistrationTech int radioTech) {
408         switch (radioTech) {
409             case REGISTRATION_TECH_NONE:
410                 return TelephonyManager.NETWORK_TYPE_UNKNOWN;
411             case REGISTRATION_TECH_LTE:
412                 return TelephonyManager.NETWORK_TYPE_LTE;
413             case REGISTRATION_TECH_IWLAN:
414                 return TelephonyManager.NETWORK_TYPE_IWLAN;
415             case REGISTRATION_TECH_NR:
416                 return TelephonyManager.NETWORK_TYPE_NR;
417             default:
418                 loge("convertRegistrationTechToNetworkType: unknown radio tech %d", radioTech);
419                 return getWwanPsRat();
420         }
421     }
422 
copyOf(ImsRegistrationStats source)423     private static ImsRegistrationStats copyOf(ImsRegistrationStats source) {
424         ImsRegistrationStats dest = new ImsRegistrationStats();
425 
426         dest.carrierId = source.carrierId;
427         dest.simSlotIndex = source.simSlotIndex;
428         dest.rat = source.rat;
429         dest.registeredMillis = source.registeredMillis;
430         dest.voiceCapableMillis = source.voiceCapableMillis;
431         dest.voiceAvailableMillis = source.voiceAvailableMillis;
432         dest.smsCapableMillis = source.smsCapableMillis;
433         dest.smsAvailableMillis = source.smsAvailableMillis;
434         dest.videoCapableMillis = source.videoCapableMillis;
435         dest.videoAvailableMillis = source.videoAvailableMillis;
436         dest.utCapableMillis = source.utCapableMillis;
437         dest.utAvailableMillis = source.utAvailableMillis;
438 
439         return dest;
440     }
441 
442     @VisibleForTesting
getTimeMillis()443     protected long getTimeMillis() {
444         return SystemClock.elapsedRealtime();
445     }
446 
447     /** Filters IMS extra messages to ensure length limit and remove IDs. */
filterExtraMessage(@ullable String str)448     public static String filterExtraMessage(@Nullable String str) {
449         if (str == null) {
450             return "";
451         }
452 
453         str = PATTERN_UUID.matcher(str).replaceAll(REPLACEMENT_UUID);
454         str = PATTERN_URI.matcher(str).replaceAll(REPLACEMENT_URI);
455         str = PATTERN_HOSTNAME.matcher(str).replaceAll(REPLACEMENT_HOSTNAME);
456         str = PATTERN_IPV4.matcher(str).replaceAll(REPLACEMENT_IPV4);
457         str = PATTERN_IPV6.matcher(str).replaceAll(REPLACEMENT_IPV6);
458         str = PATTERN_IMEI.matcher(str).replaceAll(REPLACEMENT_IMEI);
459         str = PATTERN_UNSEGMENTED_IMEI_IMSI.matcher(str)
460                 .replaceAll(REPLACEMENT_UNSEGMENTED_IMEI_IMSI);
461         str = PATTERN_UNKNOWN_ID.matcher(str).replaceAll(REPLACEMENT_UNKNOWN_ID);
462 
463         return str.length() > MAX_EXTRA_MESSAGE_LENGTH
464                 ? str.substring(0, MAX_EXTRA_MESSAGE_LENGTH)
465                 : str;
466     }
467 
logw(String format, Object... args)468     private void logw(String format, Object... args) {
469         Rlog.w(TAG, "[" + mPhone.getPhoneId() + "] " + String.format(format, args));
470     }
471 
loge(String format, Object... args)472     private void loge(String format, Object... args) {
473         Rlog.e(TAG, "[" + mPhone.getPhoneId() + "] " + String.format(format, args));
474     }
475 }
476