• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.cellbroadcastreceiver;
18 
19 import static android.content.Context.MODE_PRIVATE;
20 import static android.telephony.SmsCbMessage.MESSAGE_FORMAT_3GPP;
21 
22 import static com.android.cellbroadcastservice.CellBroadcastMetrics.FILTER_CDMA;
23 import static com.android.cellbroadcastservice.CellBroadcastMetrics.FILTER_GSM;
24 
25 import android.content.Context;
26 import android.content.SharedPreferences;
27 import android.telephony.SmsCbMessage;
28 import android.util.Log;
29 import android.util.Pair;
30 
31 import com.android.cellbroadcastservice.CellBroadcastModuleStatsLog;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import com.google.protobuf.InvalidProtocolBufferException;
35 
36 import java.io.IOException;
37 import java.util.HashSet;
38 import java.util.Objects;
39 import java.util.StringJoiner;
40 
41 /**
42  * CellBroadcastReceiverMetrics
43  * Logging featureUpdated when alert message is received or channel range is updated
44  * Logging onConfigUpdated when channel range is updated
45  */
46 public class CellBroadcastReceiverMetrics {
47 
48     private static final String TAG = "CellbroadcastReceiverMetrics";
49     private static final boolean VDBG = false;
50 
51     // Key to access the shared preference of cellbroadcast channel range information for metric.
52     public static final String CBR_CONFIG_UPDATED = "CellBroadcastConfigUpdated";
53     // Key to access the shared preference of cellbroadcast receiver feature for metric.
54     private static final String CBR_METRIC_PREF = "CellBroadcastReceiverMetricSharedPref";
55     private static final String CHANNEL_DELIMITER = ",";
56     private static final String RANGE_DELIMITER = "-";
57 
58     private static CellBroadcastReceiverMetrics sCbrMetrics;
59 
60     HashSet<Pair<Integer, Integer>> mConfigUpdatedCachedChannelSet;
61 
62     private FeatureMetrics mFeatureMetrics;
63     private FeatureMetrics mFeatureMetricsSharedPreferences;
64 
65     /**
66      * Get instance of CellBroadcastReceiverMetrics.
67      */
68     @VisibleForTesting
getInstance()69     public static CellBroadcastReceiverMetrics getInstance() {
70         if (sCbrMetrics == null) {
71             sCbrMetrics = new CellBroadcastReceiverMetrics();
72         }
73         return sCbrMetrics;
74     }
75 
76     /**
77      * set cached feature metrics for current status
78      */
79     @VisibleForTesting
setFeatureMetrics( FeatureMetrics featureMetrics)80     public void setFeatureMetrics(
81             FeatureMetrics featureMetrics) {
82         mFeatureMetrics = featureMetrics;
83     }
84 
85     /**
86      * get cached feature metrics for shared preferences
87      */
88     @VisibleForTesting
getFeatureMetricsSharedPreferences()89     public FeatureMetrics getFeatureMetricsSharedPreferences() {
90         return mFeatureMetricsSharedPreferences;
91     }
92 
93     /**
94      * Set featureMetricsSharedPreferences
95      *
96      * @param featureMetricsSharedPreferences : Cbr features information
97      */
98     @VisibleForTesting
setFeatureMetricsSharedPreferences( FeatureMetrics featureMetricsSharedPreferences)99     public void setFeatureMetricsSharedPreferences(
100             FeatureMetrics featureMetricsSharedPreferences) {
101         mFeatureMetricsSharedPreferences = featureMetricsSharedPreferences;
102     }
103 
104     /**
105      * Get current configuration channel set status
106      */
107     @VisibleForTesting
getCachedChannelSet()108     public HashSet<Pair<Integer, Integer>> getCachedChannelSet() {
109         return mConfigUpdatedCachedChannelSet;
110     }
111 
112     /**
113      * CellbroadcastReceiverMetrics
114      * Logging featureUpdated as needed when alert message is received or channel range is updated
115      */
116     public class FeatureMetrics implements Cloneable {
117         public static final String ALERT_IN_CALL = "enable_alert_handling_during_call";
118         public static final String OVERRIDE_DND = "override_dnd";
119         public static final String ROAMING_SUPPORT = "cmas_roaming_network_strings";
120         public static final String STORE_SMS = "enable_write_alerts_to_sms_inbox";
121         public static final String TEST_MODE = "testing_mode";
122         public static final String TTS_MODE = "enable_alert_speech_default";
123         public static final String TEST_MODE_ON_USER_BUILD = "allow_testing_mode_on_user_build";
124 
125         private boolean mAlertDuringCall;
126         private HashSet<Pair<Integer, Integer>> mDnDChannelSet;
127         private boolean mRoamingSupport;
128         private boolean mStoreSms;
129         private boolean mTestMode;
130         private boolean mEnableAlertSpeech;
131         private boolean mTestModeOnUserBuild;
132 
133         private Context mContext;
134 
FeatureMetrics(Context context)135         FeatureMetrics(Context context) {
136             mContext = context;
137             SharedPreferences sp = mContext.getSharedPreferences(CBR_METRIC_PREF, MODE_PRIVATE);
138             mAlertDuringCall = sp.getBoolean(ALERT_IN_CALL, false);
139             String strOverrideDnD = sp.getString(OVERRIDE_DND, String.valueOf(0));
140             mDnDChannelSet = getChannelSetFromString(strOverrideDnD);
141             mRoamingSupport = sp.getBoolean(ROAMING_SUPPORT, false);
142             mStoreSms = sp.getBoolean(STORE_SMS, false);
143             mTestMode = sp.getBoolean(TEST_MODE, false);
144             mEnableAlertSpeech = sp.getBoolean(TTS_MODE, true);
145             mTestModeOnUserBuild = sp.getBoolean(TEST_MODE_ON_USER_BUILD, true);
146         }
147 
148         @Override
hashCode()149         public int hashCode() {
150             return Objects.hash(mDnDChannelSet, mAlertDuringCall, mRoamingSupport, mStoreSms,
151                     mTestMode, mEnableAlertSpeech, mTestModeOnUserBuild);
152         }
153 
154         @Override
equals(Object object)155         public boolean equals(Object object) {
156             if (object instanceof FeatureMetrics) {
157                 FeatureMetrics features = (FeatureMetrics) object;
158                 return (this.mAlertDuringCall == features.mAlertDuringCall
159                         && this.mDnDChannelSet.equals(features.mDnDChannelSet)
160                         && this.mRoamingSupport == features.mRoamingSupport
161                         && this.mStoreSms == features.mStoreSms
162                         && this.mTestMode == features.mTestMode
163                         && this.mEnableAlertSpeech == features.mEnableAlertSpeech
164                         && this.mTestModeOnUserBuild == features.mTestModeOnUserBuild);
165             }
166             return false;
167         }
168 
169         @Override
clone()170         public Object clone() throws CloneNotSupportedException {
171             FeatureMetrics copy = (FeatureMetrics) super.clone();
172             copy.mDnDChannelSet = new HashSet<>();
173             copy.mDnDChannelSet.addAll(this.mDnDChannelSet);
174             return copy;
175         }
176 
177         /**
178          * Get current status whether alert during call is enabled
179          */
180         @VisibleForTesting
isAlertDuringCall()181         public boolean isAlertDuringCall() {
182             return mAlertDuringCall;
183         }
184 
185         /**
186          * Get current do not disturb channels set
187          */
188         @VisibleForTesting
getDnDChannelSet()189         public HashSet<Pair<Integer, Integer>> getDnDChannelSet() {
190             return mDnDChannelSet;
191         }
192 
193         /**
194          * Get whether currently roaming supported
195          */
196         @VisibleForTesting
isRoamingSupport()197         public boolean isRoamingSupport() {
198             return mRoamingSupport;
199         }
200 
201         /**
202          * Get whether alert messages are saved inbox
203          */
204         @VisibleForTesting
isStoreSms()205         public boolean isStoreSms() {
206             return mStoreSms;
207         }
208 
209         /**
210          * Get whether test mode is enabled
211          */
212         @VisibleForTesting
isTestMode()213         public boolean isTestMode() {
214             return mTestMode;
215         }
216 
217         /**
218          * Get whether alert message support text to speech
219          */
220         @VisibleForTesting
isEnableAlertSpeech()221         public boolean isEnableAlertSpeech() {
222             return mEnableAlertSpeech;
223         }
224 
225         /**
226          * Get whether test mode is not supporting in user build status
227          */
228         @VisibleForTesting
isTestModeOnUserBuild()229         public boolean isTestModeOnUserBuild() {
230             return mTestModeOnUserBuild;
231         }
232 
233         /**
234          * Set alert during call
235          *
236          * @param current : current status of alert during call
237          */
238         @VisibleForTesting
onChangedAlertDuringCall(boolean current)239         public void onChangedAlertDuringCall(boolean current) {
240             mAlertDuringCall = current;
241         }
242 
243         /**
244          * Set alert during call
245          *
246          * @param channelManager : channel manager to get channel range supporting override dnd
247          * @param overAllDnD     : whether override dnd is fully supported or not
248          */
249         @VisibleForTesting
onChangedOverrideDnD( CellBroadcastChannelManager channelManager, boolean overAllDnD)250         public void onChangedOverrideDnD(
251                 CellBroadcastChannelManager channelManager, boolean overAllDnD) {
252             mDnDChannelSet.clear();
253             if (overAllDnD) {
254                 mDnDChannelSet.add(new Pair(Integer.MAX_VALUE, Integer.MAX_VALUE));
255             } else {
256                 channelManager.getAllCellBroadcastChannelRanges().forEach(r -> {
257                     if (r.mOverrideDnd) {
258                         mDnDChannelSet.add(new Pair(r.mStartId, r.mEndId));
259                     }
260                 });
261                 if (mDnDChannelSet.size() == 0) {
262                     mDnDChannelSet.add(new Pair(0, 0));
263                 }
264             }
265         }
266 
267         /**
268          * Set roaming support
269          *
270          * @param current : current status of roaming support
271          */
272         @VisibleForTesting
onChangedRoamingSupport(boolean current)273         public void onChangedRoamingSupport(boolean current) {
274             mRoamingSupport = current;
275         }
276 
277         /**
278          * Set current status of storing alert message inbox
279          *
280          * @param current : current status value of storing inbox
281          */
282         @VisibleForTesting
onChangedStoreSms(boolean current)283         public void onChangedStoreSms(boolean current) {
284             mStoreSms = current;
285         }
286 
287         /**
288          * Set current status of test-mode
289          *
290          * @param current : current status value of test-mode
291          */
292         @VisibleForTesting
onChangedTestMode(boolean current)293         public void onChangedTestMode(boolean current) {
294             mTestMode = current;
295         }
296 
297         /**
298          * Set whether text to speech is supported for alert message
299          *
300          * @param current : current status tts
301          */
302         @VisibleForTesting
onChangedEnableAlertSpeech(boolean current)303         public void onChangedEnableAlertSpeech(boolean current) {
304             mEnableAlertSpeech = current;
305         }
306 
307         /**
308          * Set whether test mode on user build is supported
309          *
310          * @param current : current status of test mode on user build
311          */
onChangedTestModeOnUserBuild(boolean current)312         public void onChangedTestModeOnUserBuild(boolean current) {
313             mTestModeOnUserBuild = current;
314         }
315 
316         /**
317          * Calling check-in method for CB_SERVICE_FEATURE
318          */
319         @VisibleForTesting
logFeatureChanged()320         public void logFeatureChanged() {
321             try {
322                 CellBroadcastModuleStatsLog.write(
323                         CellBroadcastModuleStatsLog.CB_RECEIVER_FEATURE_CHANGED,
324                         mAlertDuringCall,
325                         convertToProtoBuffer(mDnDChannelSet),
326                         mRoamingSupport,
327                         mStoreSms,
328                         mTestMode,
329                         mEnableAlertSpeech,
330                         mTestModeOnUserBuild);
331             } catch (IOException e) {
332                 Log.e(TAG, "IOException while encoding array byte from channel set" + e);
333             }
334             if (VDBG) Log.d(TAG, this.toString());
335         }
336 
337         /**
338          * Update preferences for receiver feature metrics
339          */
updateSharedPreferences()340         public void updateSharedPreferences() {
341             SharedPreferences sp =
342                     mContext.getSharedPreferences(CBR_METRIC_PREF, MODE_PRIVATE);
343             SharedPreferences.Editor editor = sp.edit();
344             editor.putBoolean(ALERT_IN_CALL, mAlertDuringCall);
345             editor.putString(OVERRIDE_DND, getStringFromChannelSet(mDnDChannelSet));
346             editor.putBoolean(ROAMING_SUPPORT, mRoamingSupport);
347             editor.putBoolean(STORE_SMS, mStoreSms);
348             editor.putBoolean(TEST_MODE, mTestMode);
349             editor.putBoolean(TTS_MODE, mEnableAlertSpeech);
350             editor.putBoolean(TEST_MODE_ON_USER_BUILD, mTestModeOnUserBuild);
351             editor.apply();
352         }
353 
354         @Override
toString()355         public String toString() {
356             return "CellBroadcast_Receiver_Feature : "
357                     + "mAlertDuringCall = " + mAlertDuringCall + " | "
358                     + "mOverrideDnD = " + getStringFromChannelSet(mDnDChannelSet) + " | "
359                     + "mRoamingSupport = " + mRoamingSupport + " | "
360                     + "mStoreSms = " + mStoreSms + " | "
361                     + "mTestMode = " + mTestMode + " | "
362                     + "mEnableAlertSpeech = " + mEnableAlertSpeech + " | "
363                     + "mTestModeOnUserBuild = " + mTestModeOnUserBuild;
364         }
365     }
366 
367     /**
368      * Get current feature metrics
369      *
370      * @param context : Context
371      */
372     @VisibleForTesting
getFeatureMetrics(Context context)373     public FeatureMetrics getFeatureMetrics(Context context) {
374         if (mFeatureMetrics == null) {
375             mFeatureMetrics = new FeatureMetrics(context);
376             mFeatureMetricsSharedPreferences = new FeatureMetrics(context);
377         }
378         return mFeatureMetrics;
379     }
380 
381 
382     /**
383      * Convert ChannelSet to ProtoBuffer
384      *
385      * @param rangeList : channel range set
386      */
387     @VisibleForTesting
convertToProtoBuffer(HashSet<Pair<Integer, Integer>> rangeList)388     public byte[] convertToProtoBuffer(HashSet<Pair<Integer, Integer>> rangeList)
389             throws IOException {
390         Cellbroadcastmetric.CellBroadcastChannelRangesProto.Builder rangeListBuilder =
391                 Cellbroadcastmetric.CellBroadcastChannelRangesProto.newBuilder();
392         rangeList.stream().sorted((o1, o2) -> o1.first == o2.first ? o1.second - o2.second
393                 : o1.first - o2.first).forEach(pair -> {
394             Cellbroadcastmetric.CellBroadcastChannelRangeProto.Builder rangeBuilder =
395                     Cellbroadcastmetric.CellBroadcastChannelRangeProto.newBuilder();
396             rangeBuilder.setStart(pair.first);
397             rangeBuilder.setEnd(pair.second);
398             rangeListBuilder.addChannelRanges(rangeBuilder);
399             if (VDBG) {
400                 Log.d(TAG, "[first] : " + pair.first + " [second] : " + pair.second);
401             }
402         });
403         return rangeListBuilder.build().toByteArray();
404     }
405 
406     /**
407      * Convert ProtoBuffer to ChannelSet
408      *
409      * @param arrayByte : channel range set encoded arrayByte
410      */
411     @VisibleForTesting
getDataFromProtoArrayByte(byte[] arrayByte)412     public HashSet<Pair<Integer, Integer>> getDataFromProtoArrayByte(byte[] arrayByte)
413             throws InvalidProtocolBufferException {
414         HashSet<Pair<Integer, Integer>> convertResult = new HashSet<>();
415 
416         Cellbroadcastmetric.CellBroadcastChannelRangesProto channelRangesProto =
417                 Cellbroadcastmetric.CellBroadcastChannelRangesProto
418                         .parser().parseFrom(arrayByte);
419 
420         for (Cellbroadcastmetric.CellBroadcastChannelRangeProto range :
421                 channelRangesProto.getChannelRangesList()) {
422             convertResult.add(new Pair(range.getStart(), range.getEnd()));
423         }
424 
425         return convertResult;
426     }
427 
428     /**
429      * When feature changed and net alert message received then check-in logging
430      *
431      * @param context : Context
432      */
433     @VisibleForTesting
logFeatureChangedAsNeeded(Context context)434     public void logFeatureChangedAsNeeded(Context context) {
435         if (!getFeatureMetrics(context).equals(mFeatureMetricsSharedPreferences)) {
436             mFeatureMetrics.logFeatureChanged();
437             mFeatureMetrics.updateSharedPreferences();
438             try {
439                 mFeatureMetricsSharedPreferences = (FeatureMetrics) mFeatureMetrics.clone();
440             } catch (CloneNotSupportedException e) {
441                 Log.e(TAG, "CloneNotSupportedException error" + e);
442             }
443         }
444     }
445 
446     /**
447      * Convert ChannelSet to String
448      *
449      * @param curChRangeSet : channel range set
450      */
451     @VisibleForTesting
getStringFromChannelSet( HashSet<Pair<Integer, Integer>> curChRangeSet)452     public String getStringFromChannelSet(
453             HashSet<Pair<Integer, Integer>> curChRangeSet) {
454         StringJoiner strChannelList = new StringJoiner(CHANNEL_DELIMITER);
455         curChRangeSet.forEach(pair -> strChannelList.add(
456                 pair.first.equals(pair.second)
457                         ? String.valueOf(pair.first) : pair.first + RANGE_DELIMITER + pair.second));
458         return strChannelList.toString();
459     }
460 
461     /**
462      * Convert String to ChannelSet
463      *
464      * @param strChannelRange : channel range string
465      */
getChannelSetFromString(String strChannelRange)466     public HashSet<Pair<Integer, Integer>> getChannelSetFromString(String strChannelRange) {
467         String[] arrStringChannelRange = strChannelRange.split(CHANNEL_DELIMITER);
468         HashSet<Pair<Integer, Integer>> channelSet = new HashSet<>();
469         try {
470             for (String chRange : arrStringChannelRange) {
471                 if (chRange.contains(RANGE_DELIMITER)) {
472                     String[] range = chRange.split(RANGE_DELIMITER);
473                     channelSet.add(
474                             new Pair(Integer.parseInt(range[0].trim()),
475                                     Integer.parseInt(range[1].trim())));
476                 } else {
477                     channelSet.add(new Pair(Integer.parseInt(chRange.trim()),
478                             Integer.parseInt(chRange.trim())));
479                 }
480             }
481         } catch (NumberFormatException e) {
482             Log.e(TAG, "NumberFormatException error" + e);
483         }
484         return channelSet;
485     }
486 
487     /**
488      * Create a new onConfigUpdated
489      *
490      * @param context       : Context
491      * @param roamingMccMnc : country and operator information
492      * @param curChRangeSet : channel range list information
493      */
onConfigUpdated(Context context, String roamingMccMnc, HashSet<Pair<Integer, Integer>> curChRangeSet)494     public void onConfigUpdated(Context context, String roamingMccMnc,
495             HashSet<Pair<Integer, Integer>> curChRangeSet) {
496 
497         if (curChRangeSet == null) return;
498 
499         SharedPreferences sp = context.getSharedPreferences(CBR_METRIC_PREF, MODE_PRIVATE);
500 
501         if (mConfigUpdatedCachedChannelSet == null) {
502             mConfigUpdatedCachedChannelSet = getChannelSetFromString(
503                     sp.getString(CBR_CONFIG_UPDATED, String.valueOf(0)));
504         }
505 
506         if (!curChRangeSet.equals(mConfigUpdatedCachedChannelSet)) {
507             logFeatureChangedAsNeeded(context);
508             try {
509                 byte[] byteArrayChannelRange = convertToProtoBuffer(curChRangeSet);
510                 if (byteArrayChannelRange != null) {
511                     CellBroadcastModuleStatsLog.write(
512                             CellBroadcastModuleStatsLog.CB_CONFIG_UPDATED,
513                             roamingMccMnc, byteArrayChannelRange);
514 
515                     String stringConfigurationUpdated = getStringFromChannelSet(curChRangeSet);
516                     SharedPreferences.Editor editor = sp.edit();
517                     editor.putString(CBR_CONFIG_UPDATED, stringConfigurationUpdated);
518                     editor.apply();
519 
520                     mConfigUpdatedCachedChannelSet.clear();
521                     mConfigUpdatedCachedChannelSet.addAll(curChRangeSet);
522 
523                     if (VDBG) {
524                         Log.d(TAG, "onConfigUpdated : roamingMccMnc is = " + roamingMccMnc
525                                 + " | channelRange is = " + stringConfigurationUpdated);
526                     }
527                 }
528             } catch (RuntimeException | IOException e) {
529                 Log.e(TAG, "Exception error occur " + e.getMessage());
530             }
531         }
532     }
533 
534     /**
535      * Create a new logMessageReported
536      *
537      * @param context  : Context
538      * @param type     : radio type
539      * @param source   : layer of reported message
540      * @param serialNo : unique identifier of message
541      * @param msgId    : service_category of message
542      */
logMessageReported(Context context, int type, int source, int serialNo, int msgId)543     void logMessageReported(Context context, int type, int source, int serialNo, int msgId) {
544         if (VDBG) {
545             Log.d(TAG,
546                     "logMessageReported : " + type + " " + source + " " + serialNo + " "
547                             + msgId);
548         }
549         CellBroadcastModuleStatsLog.write(CellBroadcastModuleStatsLog.CB_MESSAGE_REPORTED,
550                 type, source, serialNo, msgId);
551     }
552 
553     /**
554      * Create a new logMessageFiltered
555      *
556      * @param filterType : reason type of filtered
557      * @param msg        : sms cell broadcast message information
558      */
logMessageFiltered(int filterType, SmsCbMessage msg)559     void logMessageFiltered(int filterType, SmsCbMessage msg) {
560         int ratType = msg.getMessageFormat() == MESSAGE_FORMAT_3GPP ? FILTER_GSM : FILTER_CDMA;
561         if (VDBG) {
562             Log.d(TAG, "logMessageFiltered : " + ratType + " " + filterType + " "
563                     + msg.getSerialNumber() + " " + msg.getServiceCategory());
564         }
565         CellBroadcastModuleStatsLog.write(CellBroadcastModuleStatsLog.CB_MESSAGE_FILTERED,
566                 ratType, filterType, msg.getSerialNumber(), msg.getServiceCategory());
567     }
568 
569     /**
570      * Create a new logModuleError
571      *
572      * @param source    : where this log happened
573      * @param errorType : type of error
574      */
logModuleError(int source, int errorType)575     void logModuleError(int source, int errorType) {
576         if (VDBG) {
577             Log.d(TAG, "logModuleError : " + source + " " + errorType);
578         }
579         CellBroadcastModuleStatsLog.write(CellBroadcastModuleStatsLog.CB_MODULE_ERROR_REPORTED,
580                 source, errorType);
581     }
582 }
583