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.ServiceState.RilRadioTechnology;
33 import android.telephony.TelephonyManager;
34 import android.telephony.data.ApnSetting.ProtocolType;
35 import android.telephony.data.DataCallResponse;
36 import android.telephony.data.DataService;
37 import android.telephony.data.DataService.DeactivateDataReason;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.telephony.Phone;
41 import com.android.internal.telephony.PhoneFactory;
42 import com.android.internal.telephony.ServiceStateTracker;
43 import com.android.internal.telephony.SubscriptionController;
44 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
45 import com.android.telephony.Rlog;
46 
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 
DataCallSessionStats(Phone phone)62     public DataCallSessionStats(Phone phone) {
63         mPhone = phone;
64     }
65 
66     /** Creates a new ongoing atom when data call is set up. */
onSetupDataCall(@pnType int apnTypeBitMask)67     public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) {
68         mDataCallSession = getDefaultProto(apnTypeBitMask);
69         mStartTime = getTimeMillis();
70         PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this);
71     }
72 
73     /**
74      * Updates the ongoing dataCall's atom for data call response event.
75      *
76      * @param response setup Data call response
77      * @param radioTechnology The data call RAT
78      * @param apnTypeBitmask APN type bitmask
79      * @param protocol Data connection protocol
80      * @param failureCause failure cause as per android.telephony.DataFailCause
81      */
onSetupDataCallResponse( @ullable DataCallResponse response, @RilRadioTechnology int radioTechnology, @ApnType int apnTypeBitmask, @ProtocolType int protocol, @DataFailureCause int failureCause)82     public synchronized void onSetupDataCallResponse(
83             @Nullable DataCallResponse response,
84             @RilRadioTechnology int radioTechnology,
85             @ApnType int apnTypeBitmask,
86             @ProtocolType int protocol,
87             @DataFailureCause int failureCause) {
88         // there should've been a call to onSetupDataCall to initiate the atom,
89         // so this method is being called out of order -> no metric will be logged
90         if (mDataCallSession == null) {
91             loge("onSetupDataCallResponse: no DataCallSession atom has been initiated.");
92             return;
93         }
94 
95         @NetworkType int currentRat = ServiceState.rilRadioTechnologyToNetworkType(radioTechnology);
96         if (currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
97             mDataCallSession.ratAtEnd = currentRat;
98             mDataCallSession.bandAtEnd =
99                     (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN)
100                             ? 0
101                             : ServiceStateStats.getBand(mPhone);
102         }
103 
104         // only set if apn hasn't been set during setup
105         if (mDataCallSession.apnTypeBitmask == 0) {
106             mDataCallSession.apnTypeBitmask = apnTypeBitmask;
107         }
108 
109         mDataCallSession.ipType = protocol;
110         mDataCallSession.failureCause = failureCause;
111         if (response != null) {
112             mDataCallSession.suggestedRetryMillis =
113                     (int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE);
114             // If setup has failed, then store the atom
115             if (failureCause != DataFailCause.NONE) {
116                 mDataCallSession.failureCause = failureCause;
117                 mDataCallSession.oosAtEnd = getIsOos();
118                 mDataCallSession.setupFailed = true;
119                 mDataCallSession.ongoing = false;
120                 PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this);
121                 mAtomsStorage.addDataCallSession(mDataCallSession);
122                 mDataCallSession = null;
123             }
124         }
125     }
126 
127     /**
128      * Updates the dataCall atom when data call is deactivated.
129      *
130      * @param reason Deactivate reason
131      */
setDeactivateDataCallReason(@eactivateDataReason int reason)132     public synchronized void setDeactivateDataCallReason(@DeactivateDataReason int reason) {
133         // there should've been another call to initiate the atom,
134         // so this method is being called out of order -> no metric will be logged
135         if (mDataCallSession == null) {
136             loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated.");
137             return;
138         }
139         switch (reason) {
140             case DataService.REQUEST_REASON_NORMAL:
141                 mDataCallSession.deactivateReason =
142                         DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL;
143                 break;
144             case DataService.REQUEST_REASON_SHUTDOWN:
145                 mDataCallSession.deactivateReason =
146                         DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF;
147                 break;
148             case DataService.REQUEST_REASON_HANDOVER:
149                 mDataCallSession.deactivateReason =
150                         DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER;
151                 break;
152             default:
153                 mDataCallSession.deactivateReason =
154                         DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
155                 break;
156         }
157     }
158 
159     /**
160      * Stores the atom when DataConnection reaches DISCONNECTED state.
161      *
162      * @param failureCause failure cause as per android.telephony.DataFailCause
163      */
onDataCallDisconnected(@ataFailureCause int failureCause)164     public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) {
165         // there should've been another call to initiate the atom,
166         // so this method is being called out of order -> no atom will be saved
167         // this also happens when DataConnection is created, which is expected
168         if (mDataCallSession == null) {
169             logi("onDataCallDisconnected: no DataCallSession atom has been initiated.");
170             return;
171         }
172         mDataCallSession.failureCause = failureCause;
173         mDataCallSession.oosAtEnd = getIsOos();
174         mDataCallSession.ongoing = false;
175         mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime);
176         // store for the data call list event, after DataCall is disconnected and entered into
177         // inactive mode
178         PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this);
179         mAtomsStorage.addDataCallSession(mDataCallSession);
180         mDataCallSession = null;
181     }
182 
183     /**
184      * Updates the atom when data registration state or RAT changes.
185      *
186      * <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data
187      * registration state change.
188      */
onDrsOrRatChanged(@ilRadioTechnology int radioTechnology)189     public synchronized void onDrsOrRatChanged(@RilRadioTechnology int radioTechnology) {
190         @NetworkType int currentRat = ServiceState.rilRadioTechnologyToNetworkType(radioTechnology);
191         if (mDataCallSession != null && currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
192             if (mDataCallSession.ratAtEnd != currentRat) {
193                 mDataCallSession.ratSwitchCount++;
194                 mDataCallSession.ratAtEnd = currentRat;
195             }
196             // band may have changed even if RAT was the same
197             mDataCallSession.bandAtEnd =
198                     (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN)
199                             ? 0
200                             : ServiceStateStats.getBand(mPhone);
201         }
202     }
203 
204     /** Add the on-going data call segment to the atom storage. */
conclude()205     public synchronized void conclude() {
206         if (mDataCallSession != null) {
207             DataCallSession call = copyOf(mDataCallSession);
208             long nowMillis = getTimeMillis();
209             call.durationMinutes = convertMillisToMinutes(nowMillis - mStartTime);
210             mStartTime = nowMillis;
211             mDataCallSession.ratSwitchCount = 0L;
212             mAtomsStorage.addDataCallSession(call);
213         }
214     }
215 
convertMillisToMinutes(long millis)216     private static long convertMillisToMinutes(long millis) {
217         return Math.round(millis / 60000.0);
218     }
219 
copyOf(DataCallSession call)220     private static DataCallSession copyOf(DataCallSession call) {
221         DataCallSession copy = new DataCallSession();
222         copy.dimension = call.dimension;
223         copy.isMultiSim = call.isMultiSim;
224         copy.isEsim = call.isEsim;
225         copy.apnTypeBitmask = call.apnTypeBitmask;
226         copy.carrierId = call.carrierId;
227         copy.isRoaming = call.isRoaming;
228         copy.ratAtEnd = call.ratAtEnd;
229         copy.oosAtEnd = call.oosAtEnd;
230         copy.ratSwitchCount = call.ratSwitchCount;
231         copy.isOpportunistic = call.isOpportunistic;
232         copy.ipType = call.ipType;
233         copy.setupFailed = call.setupFailed;
234         copy.failureCause = call.failureCause;
235         copy.suggestedRetryMillis = call.suggestedRetryMillis;
236         copy.deactivateReason = call.deactivateReason;
237         copy.durationMinutes = call.durationMinutes;
238         copy.ongoing = call.ongoing;
239         copy.bandAtEnd = call.bandAtEnd;
240         return copy;
241     }
242 
243     /** Creates a proto for a normal {@code DataCallSession} with default values. */
getDefaultProto(@pnType int apnTypeBitmask)244     private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) {
245         DataCallSession proto = new DataCallSession();
246         proto.dimension = RANDOM.nextInt();
247         proto.isMultiSim = SimSlotState.isMultiSim();
248         proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId());
249         proto.apnTypeBitmask = apnTypeBitmask;
250         proto.carrierId = mPhone.getCarrierId();
251         proto.isRoaming = getIsRoaming();
252         proto.oosAtEnd = false;
253         proto.ratSwitchCount = 0L;
254         proto.isOpportunistic = getIsOpportunistic();
255         proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
256         proto.setupFailed = false;
257         proto.failureCause = DataFailCause.NONE;
258         proto.suggestedRetryMillis = 0;
259         proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
260         proto.durationMinutes = 0;
261         proto.ongoing = true;
262         return proto;
263     }
264 
getIsRoaming()265     private boolean getIsRoaming() {
266         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
267         ServiceState serviceState =
268                 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
269         return serviceState != null ? serviceState.getRoaming() : false;
270     }
271 
getIsOpportunistic()272     private boolean getIsOpportunistic() {
273         SubscriptionController subController = SubscriptionController.getInstance();
274         return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false;
275     }
276 
getIsOos()277     private boolean getIsOos() {
278         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
279         ServiceState serviceState =
280                 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
281         return serviceState != null
282                 ? serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE
283                 : false;
284     }
285 
logi(String format, Object... args)286     private void logi(String format, Object... args) {
287         Rlog.i(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
288     }
289 
loge(String format, Object... args)290     private void loge(String format, Object... args) {
291         Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
292     }
293 
294     @VisibleForTesting
getTimeMillis()295     protected long getTimeMillis() {
296         return SystemClock.elapsedRealtime();
297     }
298 }
299