• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
20 
21 import android.annotation.Nullable;
22 import android.os.SystemClock;
23 import android.telephony.Annotation.ApnType;
24 import android.telephony.Annotation.DataFailureCause;
25 import android.telephony.Annotation.NetworkType;
26 import android.telephony.DataFailCause;
27 import android.telephony.NetworkRegistrationInfo;
28 import android.telephony.ServiceState;
29 import android.telephony.SubscriptionInfo;
30 import android.telephony.SubscriptionManager;
31 import android.telephony.TelephonyManager;
32 import android.telephony.data.ApnSetting;
33 import android.telephony.data.ApnSetting.ProtocolType;
34 import android.telephony.data.DataCallResponse;
35 import android.telephony.data.DataService;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.telephony.Phone;
39 import com.android.internal.telephony.PhoneFactory;
40 import com.android.internal.telephony.ServiceStateTracker;
41 import com.android.internal.telephony.data.DataNetwork;
42 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
43 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
44 import com.android.internal.telephony.subscription.SubscriptionManagerService;
45 import com.android.telephony.Rlog;
46 
47 import java.util.Arrays;
48 import java.util.Random;
49 
50 /** Collects data call change events per DataConnection for the pulled atom. */
51 public class DataCallSessionStats {
52     private static final String TAG = DataCallSessionStats.class.getSimpleName();
53 
54     private final Phone mPhone;
55     private long mStartTime;
56     @Nullable private DataCallSession mDataCallSession;
57 
58     private final PersistAtomsStorage mAtomsStorage =
59             PhoneFactory.getMetricsCollector().getAtomsStorage();
60 
61     private static final Random RANDOM = new Random();
62 
63     public static final int SIZE_LIMIT_HANDOVER_FAILURES = 15;
64 
DataCallSessionStats(Phone phone)65     public DataCallSessionStats(Phone phone) {
66         mPhone = phone;
67     }
68 
69     /** Creates a new ongoing atom when data call is set up. */
onSetupDataCall(@pnType int apnTypeBitMask)70     public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) {
71         mDataCallSession = getDefaultProto(apnTypeBitMask);
72         mStartTime = getTimeMillis();
73         PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this);
74     }
75 
76     /**
77      * Updates the ongoing dataCall's atom for data call response event.
78      *
79      * @param response setup Data call response
80      * @param currentRat The data call current Network Type
81      * @param apnTypeBitmask APN type bitmask
82      * @param protocol Data connection protocol
83      * @param failureCause The raw failure cause from modem/IWLAN data service.
84      */
onSetupDataCallResponse( @ullable DataCallResponse response, @NetworkType int currentRat, @ApnType int apnTypeBitmask, @ProtocolType int protocol, int failureCause)85     public synchronized void onSetupDataCallResponse(
86             @Nullable DataCallResponse response,
87             @NetworkType int currentRat,
88             @ApnType int apnTypeBitmask,
89             @ProtocolType int protocol,
90             int failureCause) {
91         // there should've been a call to onSetupDataCall to initiate the atom,
92         // so this method is being called out of order -> no metric will be logged
93         if (mDataCallSession == null) {
94             loge("onSetupDataCallResponse: no DataCallSession atom has been initiated.");
95             return;
96         }
97 
98         if (currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
99             mDataCallSession.ratAtEnd = currentRat;
100             mDataCallSession.bandAtEnd =
101                     (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN)
102                             ? 0
103                             : ServiceStateStats.getBand(mPhone);
104         }
105 
106         // only set if apn hasn't been set during setup
107         if (mDataCallSession.apnTypeBitmask == 0) {
108             mDataCallSession.apnTypeBitmask = apnTypeBitmask;
109         }
110 
111         mDataCallSession.ipType = protocol;
112         mDataCallSession.failureCause = failureCause;
113         if (response != null) {
114             mDataCallSession.suggestedRetryMillis =
115                     (int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE);
116             // If setup has failed, then store the atom
117             if (failureCause != DataFailCause.NONE) {
118                 mDataCallSession.setupFailed = true;
119                 endDataCallSession();
120             }
121         }
122     }
123 
124     /**
125      * Updates the dataCall atom when data call is deactivated.
126      *
127      * @param reason Tear down reason
128      */
setDeactivateDataCallReason(@ataNetwork.TearDownReason int reason)129     public synchronized void setDeactivateDataCallReason(@DataNetwork.TearDownReason int reason) {
130         // there should've been another call to initiate the atom,
131         // so this method is being called out of order -> no metric will be logged
132         if (mDataCallSession == null) {
133             loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated.");
134             return;
135         }
136         // Skip the pre-U enum. See enum DataDeactivateReasonEnum in enums.proto
137         mDataCallSession.deactivateReason = reason + DataService.REQUEST_REASON_HANDOVER + 1;
138     }
139 
140     /**
141      * Stores the atom when DataConnection reaches DISCONNECTED state.
142      *
143      * @param failureCause failure cause as per android.telephony.DataFailCause
144      */
onDataCallDisconnected(@ataFailureCause int failureCause)145     public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) {
146         // there should've been another call to initiate the atom,
147         // so this method is being called out of order -> no atom will be saved
148         // this also happens when DataConnection is created, which is expected
149         if (mDataCallSession == null) {
150             logi("onDataCallDisconnected: no DataCallSession atom has been initiated.");
151             return;
152         }
153         mDataCallSession.failureCause = failureCause;
154         mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime);
155         endDataCallSession();
156     }
157 
158     /**
159      * Updates the atom when a handover fails. Note we only record distinct failure causes, as in
160      * most cases retry failures are due to the same cause.
161      *
162      * @param failureCause failure cause as per android.telephony.DataFailCause
163      */
onHandoverFailure(@ataFailureCause int failureCause, @NetworkType int sourceRat, @NetworkType int targetRat)164     public synchronized void onHandoverFailure(@DataFailureCause int failureCause,
165             @NetworkType int sourceRat, @NetworkType int targetRat) {
166         if (mDataCallSession != null
167                 && mDataCallSession.handoverFailureCauses.length
168                 < SIZE_LIMIT_HANDOVER_FAILURES) {
169 
170             int[] failureCauses = mDataCallSession.handoverFailureCauses;
171             int[] handoverFailureRats = mDataCallSession.handoverFailureRat;
172             int failureDirection = sourceRat | (targetRat << 16);
173 
174             for (int i = 0; i < failureCauses.length; i++) {
175                 if (failureCauses[i] == failureCause
176                         && handoverFailureRats[i] == failureDirection) {
177                     return;
178                 }
179             }
180 
181             mDataCallSession.handoverFailureCauses = Arrays.copyOf(
182                     failureCauses, failureCauses.length + 1);
183             mDataCallSession.handoverFailureCauses[failureCauses.length] = failureCause;
184 
185             mDataCallSession.handoverFailureRat = Arrays.copyOf(handoverFailureRats,
186                     handoverFailureRats.length + 1);
187             mDataCallSession.handoverFailureRat[handoverFailureRats.length] = failureDirection;
188         }
189     }
190 
191     /**
192      * Updates the atom when data registration state or RAT changes.
193      *
194      * <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data
195      * registration state change.
196      */
onDrsOrRatChanged(@etworkType int currentRat)197     public synchronized void onDrsOrRatChanged(@NetworkType int currentRat) {
198         if (mDataCallSession != null && currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
199             if (mDataCallSession.ratAtEnd != currentRat) {
200                 mDataCallSession.ratSwitchCount++;
201                 mDataCallSession.ratAtEnd = currentRat;
202             }
203             // band may have changed even if RAT was the same
204             mDataCallSession.bandAtEnd =
205                     (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN)
206                             ? 0
207                             : ServiceStateStats.getBand(mPhone);
208         }
209     }
210 
211     /** Stores the current unmetered network types information in permanent storage. */
onUnmeteredUpdate(@etworkType int networkType)212     public void onUnmeteredUpdate(@NetworkType int networkType) {
213         mAtomsStorage
214                 .addUnmeteredNetworks(
215                         mPhone.getPhoneId(),
216                         mPhone.getCarrierId(),
217                         TelephonyManager.getBitMaskForNetworkType(networkType));
218     }
219 
220     /**
221      * Take a snapshot of the on-going data call segment to add to the atom storage.
222      *
223      * Note the following fields are reset after the snapshot:
224      * - rat switch count
225      * - handover failure causes
226      * - handover failure rats
227      */
conclude()228     public synchronized void conclude() {
229         if (mDataCallSession != null) {
230             DataCallSession call = copyOf(mDataCallSession);
231             long nowMillis = getTimeMillis();
232             call.durationMinutes = convertMillisToMinutes(nowMillis - mStartTime);
233             mStartTime = nowMillis;
234             mDataCallSession.ratSwitchCount = 0L;
235             mDataCallSession.handoverFailureCauses = new int[0];
236             mDataCallSession.handoverFailureRat = new int[0];
237             mAtomsStorage.addDataCallSession(call);
238         }
239     }
240 
241     /** Put the current data call to an end after being uploaded to AtomStorage. */
endDataCallSession()242     private void endDataCallSession() {
243         mDataCallSession.oosAtEnd = getIsOos();
244         mDataCallSession.ongoing = false;
245         // set if this data call is established for internet on the non-Dds
246         SubscriptionInfo subInfo = SubscriptionManagerService.getInstance()
247                 .getSubscriptionInfo(mPhone.getSubId());
248         if (mPhone.getSubId() != SubscriptionManager.getDefaultDataSubscriptionId()
249                 && ((mDataCallSession.apnTypeBitmask & ApnSetting.TYPE_DEFAULT)
250                 == ApnSetting.TYPE_DEFAULT)
251                 && subInfo != null && !subInfo.isOpportunistic()) {
252             mDataCallSession.isNonDds = true;
253         }
254 
255         // store for the data call list event, after DataCall is disconnected and entered into
256         // inactive mode
257         PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this);
258         mAtomsStorage.addDataCallSession(mDataCallSession);
259         mDataCallSession = null;
260     }
261 
convertMillisToMinutes(long millis)262     private static long convertMillisToMinutes(long millis) {
263         return Math.round(millis / 60000.0);
264     }
265 
copyOf(DataCallSession call)266     private static DataCallSession copyOf(DataCallSession call) {
267         DataCallSession copy = new DataCallSession();
268         copy.dimension = call.dimension;
269         copy.isMultiSim = call.isMultiSim;
270         copy.isEsim = call.isEsim;
271         copy.apnTypeBitmask = call.apnTypeBitmask;
272         copy.carrierId = call.carrierId;
273         copy.isRoaming = call.isRoaming;
274         copy.ratAtEnd = call.ratAtEnd;
275         copy.oosAtEnd = call.oosAtEnd;
276         copy.ratSwitchCount = call.ratSwitchCount;
277         copy.isOpportunistic = call.isOpportunistic;
278         copy.ipType = call.ipType;
279         copy.setupFailed = call.setupFailed;
280         copy.failureCause = call.failureCause;
281         copy.suggestedRetryMillis = call.suggestedRetryMillis;
282         copy.deactivateReason = call.deactivateReason;
283         copy.durationMinutes = call.durationMinutes;
284         copy.ongoing = call.ongoing;
285         copy.bandAtEnd = call.bandAtEnd;
286         copy.handoverFailureCauses = Arrays.copyOf(call.handoverFailureCauses,
287                 call.handoverFailureCauses.length);
288         copy.handoverFailureRat = Arrays.copyOf(call.handoverFailureRat,
289                 call.handoverFailureRat.length);
290         copy.isNonDds = call.isNonDds;
291         return copy;
292     }
293 
294     /** Creates a proto for a normal {@code DataCallSession} with default values. */
getDefaultProto(@pnType int apnTypeBitmask)295     private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) {
296         DataCallSession proto = new DataCallSession();
297         proto.dimension = RANDOM.nextInt();
298         proto.isMultiSim = SimSlotState.isMultiSim();
299         proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId());
300         proto.apnTypeBitmask = apnTypeBitmask;
301         proto.carrierId = mPhone.getCarrierId();
302         proto.isRoaming = getIsRoaming();
303         proto.oosAtEnd = false;
304         proto.ratSwitchCount = 0L;
305         proto.isOpportunistic = getIsOpportunistic();
306         proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
307         proto.setupFailed = false;
308         proto.failureCause = DataFailCause.NONE;
309         proto.suggestedRetryMillis = 0;
310         proto.deactivateReason = DataNetwork.TEAR_DOWN_REASON_NONE;
311         proto.durationMinutes = 0;
312         proto.ongoing = true;
313         proto.handoverFailureCauses = new int[0];
314         proto.handoverFailureRat = new int[0];
315         proto.isNonDds = false;
316         return proto;
317     }
318 
getIsRoaming()319     private boolean getIsRoaming() {
320         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
321         ServiceState serviceState =
322                 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
323         return ServiceStateStats.isNetworkRoaming(serviceState, NetworkRegistrationInfo.DOMAIN_PS);
324     }
325 
getIsOpportunistic()326     private boolean getIsOpportunistic() {
327         SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
328                 .getSubscriptionInfoInternal(mPhone.getSubId());
329         return subInfo != null && subInfo.isOpportunistic();
330     }
331 
getIsOos()332     private boolean getIsOos() {
333         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
334         ServiceState serviceState =
335                 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
336         return serviceState != null
337                 && serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE;
338     }
339 
logi(String format, Object... args)340     private void logi(String format, Object... args) {
341         Rlog.i(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
342     }
343 
loge(String format, Object... args)344     private void loge(String format, Object... args) {
345         Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
346     }
347 
348     @VisibleForTesting
getTimeMillis()349     protected long getTimeMillis() {
350         return SystemClock.elapsedRealtime();
351     }
352 }
353