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