1 /*
2  * Copyright (C) 2023 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.satellite.metrics;
18 
19 import android.annotation.NonNull;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.os.BatteryManager;
25 import android.telephony.satellite.SatelliteManager;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.telephony.metrics.SatelliteStats;
30 import com.android.internal.telephony.satellite.SatelliteServiceUtils;
31 
32 /**
33  * Stats to log to satellite metrics
34  */
35 public class ControllerMetricsStats {
36     private static final int ADD_COUNT = 1;
37     private static final String TAG = ControllerMetricsStats.class.getSimpleName();
38     private static final boolean DBG = false;
39 
40     private static ControllerMetricsStats sInstance;
41 
42     private final Context mContext;
43     private SatelliteStats mSatelliteStats;
44 
45     private long mSatelliteOnTimeMillis;
46     private int mBatteryLevelWhenServiceOn;
47     private boolean mIsSatelliteModemOn;
48     private Boolean mIsBatteryCharged = null;
49     private int mBatteryChargedStartTimeSec;
50     private int mTotalBatteryChargeTimeSec;
51 
52     /**
53      * @return The singleton instance of ControllerMetricsStats.
54      */
getInstance()55     public static ControllerMetricsStats getInstance() {
56         if (sInstance == null) {
57             loge("ControllerMetricsStats was not yet initialized.");
58         }
59         return sInstance;
60     }
61 
62     /**
63      * Create the ControllerMetricsStats singleton instance.
64      *
65      * @param context   The Context for the ControllerMetricsStats.
66      * @return          The singleton instance of ControllerMetricsStats.
67      */
make(@onNull Context context)68     public static ControllerMetricsStats make(@NonNull Context context) {
69         if (sInstance == null) {
70             sInstance = new ControllerMetricsStats(context);
71         }
72         return sInstance;
73     }
74 
75     /**
76      * Create the ControllerMetricsStats singleton instance, testing purpose only.
77      *
78      * @param context   The Context for the ControllerMetricsStats.
79      * @param satelliteStats SatelliteStats instance to test
80      * @return          The singleton instance of ControllerMetricsStats.
81      */
82     @VisibleForTesting
make(@onNull Context context, @NonNull SatelliteStats satelliteStats)83     public static ControllerMetricsStats make(@NonNull Context context,
84             @NonNull SatelliteStats satelliteStats) {
85         if (sInstance == null) {
86             sInstance = new ControllerMetricsStats(context, satelliteStats);
87         }
88         return sInstance;
89     }
90 
91     /**
92      * Create the ControllerMetricsStats to manage metrics report for
93      * {@link SatelliteStats.SatelliteControllerParams}
94      * @param context The Context for the ControllerMetricsStats.
95      */
ControllerMetricsStats(@onNull Context context)96     ControllerMetricsStats(@NonNull Context context) {
97         mContext = context;
98         mSatelliteStats = SatelliteStats.getInstance();
99     }
100 
101     /**
102      * Create the ControllerMetricsStats to manage metrics report for
103      * {@link SatelliteStats.SatelliteControllerParams}
104      *
105      * @param context           The Context for the ControllerMetricsStats.
106      * @param satelliteStats    SatelliteStats object used for testing purpose
107      */
108     @VisibleForTesting
ControllerMetricsStats(@onNull Context context, @NonNull SatelliteStats satelliteStats)109     protected ControllerMetricsStats(@NonNull Context context,
110             @NonNull SatelliteStats satelliteStats) {
111         mContext = context;
112         mSatelliteStats = satelliteStats;
113     }
114 
115 
116     /** Report a counter when an attempt for satellite service on is successfully done */
reportServiceEnablementSuccessCount()117     public void reportServiceEnablementSuccessCount() {
118         logd("reportServiceEnablementSuccessCount()");
119         mSatelliteStats.onSatelliteControllerMetrics(
120                 new SatelliteStats.SatelliteControllerParams.Builder()
121                         .setCountOfSatelliteServiceEnablementsSuccess(ADD_COUNT)
122                         .build());
123     }
124 
125     /** Report a counter when an attempt for satellite service on is failed */
reportServiceEnablementFailCount()126     public void reportServiceEnablementFailCount() {
127         logd("reportServiceEnablementSuccessCount()");
128         mSatelliteStats.onSatelliteControllerMetrics(
129                 new SatelliteStats.SatelliteControllerParams.Builder()
130                         .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT)
131                         .build());
132     }
133 
134     /** Report a counter when an attempt for outgoing datagram is successfully done */
reportOutgoingDatagramSuccessCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)135     public void reportOutgoingDatagramSuccessCount(
136             @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) {
137         SatelliteStats.SatelliteControllerParams.Builder builder =
138                 new SatelliteStats.SatelliteControllerParams.Builder();
139 
140         if (isDemoMode) {
141             builder.setCountOfDemoModeOutgoingDatagramSuccess(ADD_COUNT);
142         } else {
143             builder.setCountOfOutgoingDatagramSuccess(ADD_COUNT);
144         }
145 
146         if (SatelliteServiceUtils.isSosMessage(datagramType)) {
147             builder.setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT);
148         } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
149             builder.setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT);
150         } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
151             builder.setCountOfDatagramTypeKeepAliveSuccess(ADD_COUNT).build();
152         }
153 
154         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
155         logd("reportServiceEnablementSuccessCount(): " + controllerParam);
156         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
157     }
158 
159     /** Report a counter when an attempt for outgoing datagram is failed */
reportOutgoingDatagramFailCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)160     public void reportOutgoingDatagramFailCount(
161             @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) {
162         SatelliteStats.SatelliteControllerParams.Builder builder =
163                 new SatelliteStats.SatelliteControllerParams.Builder();
164 
165         if (isDemoMode) {
166             builder.setCountOfDemoModeOutgoingDatagramFail(ADD_COUNT);
167         } else {
168             builder.setCountOfOutgoingDatagramFail(ADD_COUNT);
169         }
170 
171         if (SatelliteServiceUtils.isSosMessage(datagramType)) {
172             builder.setCountOfDatagramTypeSosSmsFail(ADD_COUNT);
173         } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
174             builder.setCountOfDatagramTypeLocationSharingFail(ADD_COUNT);
175         } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
176             builder.setCountOfDatagramTypeKeepAliveFail(ADD_COUNT);
177         }
178 
179         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
180         logd("reportOutgoingDatagramFailCount(): " + controllerParam);
181         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
182     }
183 
184     /** Report a counter when an attempt for incoming datagram is failed */
reportIncomingDatagramCount( @onNull @atelliteManager.SatelliteResult int result, boolean isDemoMode)185     public void reportIncomingDatagramCount(
186             @NonNull @SatelliteManager.SatelliteResult int result, boolean isDemoMode) {
187         SatelliteStats.SatelliteControllerParams.Builder builder =
188                 new SatelliteStats.SatelliteControllerParams.Builder();
189         if (isDemoMode) {
190             if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
191                 builder.setCountOfDemoModeIncomingDatagramSuccess(ADD_COUNT);
192             } else {
193                 builder.setCountOfDemoModeIncomingDatagramFail(ADD_COUNT);
194             }
195         } else {
196             if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
197                 builder.setCountOfIncomingDatagramSuccess(ADD_COUNT);
198             } else {
199                 builder.setCountOfIncomingDatagramFail(ADD_COUNT);
200             }
201         }
202         SatelliteStats.SatelliteControllerParams  controllerParam = builder.build();
203         logd("reportIncomingDatagramCount(): " + controllerParam);
204         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
205     }
206 
207     /** Report a counter when an attempt for de-provision is success or not */
reportProvisionCount(@onNull @atelliteManager.SatelliteResult int result)208     public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteResult int result) {
209         SatelliteStats.SatelliteControllerParams controllerParam;
210         if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
211             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
212                     .setCountOfProvisionSuccess(ADD_COUNT)
213                     .build();
214         } else {
215             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
216                     .setCountOfProvisionFail(ADD_COUNT)
217                     .build();
218         }
219         logd("reportProvisionCount(): " + controllerParam);
220         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
221     }
222 
223     /** Report a counter when an attempt for de-provision is success or not */
reportDeprovisionCount(@onNull @atelliteManager.SatelliteResult int result)224     public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteResult int result) {
225         SatelliteStats.SatelliteControllerParams controllerParam;
226         if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
227             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
228                     .setCountOfDeprovisionSuccess(ADD_COUNT)
229                     .build();
230         } else {
231             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
232                     .setCountOfDeprovisionFail(ADD_COUNT)
233                     .build();
234         }
235         logd("reportDeprovisionCount(): " + controllerParam);
236         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
237     }
238 
239     /**
240      * Report a counter when checking result whether satellite communication is allowed or not for
241      * current location.
242      */
reportAllowedSatelliteAccessCount(boolean isAllowed)243     public void reportAllowedSatelliteAccessCount(boolean isAllowed) {
244         SatelliteStats.SatelliteControllerParams.Builder builder;
245         if (isAllowed) {
246             builder = new SatelliteStats.SatelliteControllerParams.Builder()
247                     .setCountOfAllowedSatelliteAccess(ADD_COUNT);
248         } else {
249             builder = new SatelliteStats.SatelliteControllerParams.Builder()
250                     .setCountOfDisallowedSatelliteAccess(ADD_COUNT);
251         }
252         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
253         logd("reportAllowedSatelliteAccessCount:" + controllerParam);
254         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
255     }
256 
257     /**
258      * Report a counter when checking whether satellite communication for current location is
259      * allowed has failed.
260      */
reportFailedSatelliteAccessCheckCount()261     public void reportFailedSatelliteAccessCheckCount() {
262         SatelliteStats.SatelliteControllerParams controllerParam =
263                 new SatelliteStats.SatelliteControllerParams.Builder()
264                         .setCountOfSatelliteAccessCheckFail(ADD_COUNT).build();
265         logd("reportFailedSatelliteAccessCheckCount:" + controllerParam);
266         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
267     }
268 
269     /** Return the total service up time for satellite service */
270     @VisibleForTesting
captureTotalServiceUpTimeSec()271     public int captureTotalServiceUpTimeSec() {
272         long totalTimeMillis = getCurrentTime() - mSatelliteOnTimeMillis;
273         mSatelliteOnTimeMillis = 0;
274         return (int) (totalTimeMillis / 1000);
275     }
276 
277     /** Return the total battery charge time while satellite service is on */
278     @VisibleForTesting
captureTotalBatteryChargeTimeSec()279     public int captureTotalBatteryChargeTimeSec() {
280         int totalTime = mTotalBatteryChargeTimeSec;
281         mTotalBatteryChargeTimeSec = 0;
282         return totalTime;
283     }
284 
285     /** Capture the satellite service on time and register battery monitor */
onSatelliteEnabled()286     public void onSatelliteEnabled() {
287         if (!isSatelliteModemOn()) {
288             mIsSatelliteModemOn = true;
289 
290             startCaptureBatteryLevel();
291 
292             // log the timestamp of the satellite modem power on
293             mSatelliteOnTimeMillis = getCurrentTime();
294 
295             // register broadcast receiver for monitoring battery status change
296             IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
297 
298             logd("register BatteryStatusReceiver");
299             mContext.registerReceiver(mBatteryStatusReceiver, filter);
300         }
301     }
302 
303     /** Capture the satellite service off time and de-register battery monitor */
onSatelliteDisabled()304     public void onSatelliteDisabled() {
305         if (isSatelliteModemOn()) {
306             mIsSatelliteModemOn = false;
307 
308             logd("unregister BatteryStatusReceiver");
309             mContext.unregisterReceiver(mBatteryStatusReceiver);
310 
311             int totalServiceUpTime = captureTotalServiceUpTimeSec();
312             int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext);
313             int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec();
314 
315             // report metrics about service up time and battery
316             SatelliteStats.SatelliteControllerParams controllerParam =
317                     new SatelliteStats.SatelliteControllerParams.Builder()
318                             .setTotalServiceUptimeSec(totalServiceUpTime)
319                             .setTotalBatteryConsumptionPercent(batteryConsumptionPercent)
320                             .setTotalBatteryChargedTimeSec(totalBatteryChargeTime)
321                             .build();
322             logd("onSatelliteDisabled(): " + controllerParam);
323             mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
324         }
325     }
326 
327     /** Log the total battery charging time when satellite service is on */
updateSatelliteBatteryChargeTime(boolean isCharged)328     private void updateSatelliteBatteryChargeTime(boolean isCharged) {
329         logd("updateSatelliteBatteryChargeTime(" + isCharged + ")");
330         // update only when the charge state has changed
331         if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) {
332             mIsBatteryCharged = isCharged;
333 
334             // When charged, log the start time of battery charging
335             if (isCharged) {
336                 mBatteryChargedStartTimeSec = (int) (getCurrentTime() / 1000);
337                 // When discharged, log the accumulated total battery charging time.
338             } else {
339                 mTotalBatteryChargeTimeSec +=
340                         (int) (getCurrentTime() / 1000)
341                                 - mBatteryChargedStartTimeSec;
342                 mBatteryChargedStartTimeSec = 0;
343             }
344         }
345     }
346 
347     /** Capture the battery level when satellite service is on */
348     @VisibleForTesting
startCaptureBatteryLevel()349     public void startCaptureBatteryLevel() {
350         try {
351             BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class);
352             mBatteryLevelWhenServiceOn =
353                     batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
354             logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn);
355         } catch (NullPointerException e) {
356             loge("BatteryManager is null");
357         }
358     }
359 
360     /** Capture the total consumption level when service is off */
361     @VisibleForTesting
captureTotalBatteryConsumptionPercent(Context context)362     public int captureTotalBatteryConsumptionPercent(Context context) {
363         try {
364             BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
365             int currentLevel =
366                     batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
367             return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0);
368         } catch (NullPointerException e) {
369             loge("BatteryManager is null");
370             return 0;
371         }
372     }
373 
374     /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */
375     private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() {
376         private long mLastUpdatedTime = 0;
377         private static final long UPDATE_INTERVAL = 60 * 1000;
378 
379         @Override
380         public void onReceive(Context context, Intent intent) {
381             long currentTime = getCurrentTime();
382             if (currentTime - mLastUpdatedTime > UPDATE_INTERVAL) {
383                 mLastUpdatedTime = currentTime;
384                 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
385                 boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING);
386                 logd("Battery is charged(" + isCharged + ")");
387                 updateSatelliteBatteryChargeTime(isCharged);
388             }
389         }
390     };
391 
392     @VisibleForTesting
isSatelliteModemOn()393     public boolean isSatelliteModemOn() {
394         return mIsSatelliteModemOn;
395     }
396 
397     @VisibleForTesting
getCurrentTime()398     public long getCurrentTime() {
399         return System.currentTimeMillis();
400     }
401 
logd(@onNull String log)402     private static void logd(@NonNull String log) {
403         if (DBG) {
404             Log.d(TAG, log);
405         }
406     }
407 
loge(@onNull String log)408     private static void loge(@NonNull String log) {
409         Log.e(TAG, log);
410     }
411 }
412