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 */ getInstance()68 public static CellBroadcastReceiverMetrics getInstance() { 69 if (sCbrMetrics == null) { 70 sCbrMetrics = new CellBroadcastReceiverMetrics(); 71 } 72 return sCbrMetrics; 73 } 74 75 /** 76 * set cached feature metrics for current status 77 */ 78 @VisibleForTesting setFeatureMetrics( FeatureMetrics featureMetrics)79 public void setFeatureMetrics( 80 FeatureMetrics featureMetrics) { 81 mFeatureMetrics = featureMetrics; 82 } 83 84 /** 85 * get cached feature metrics for shared preferences 86 */ 87 @VisibleForTesting getFeatureMetricsSharedPreferences()88 public FeatureMetrics getFeatureMetricsSharedPreferences() { 89 return mFeatureMetricsSharedPreferences; 90 } 91 92 /** 93 * Set featureMetricsSharedPreferences 94 * 95 * @param featureMetricsSharedPreferences : Cbr features information 96 */ 97 @VisibleForTesting setFeatureMetricsSharedPreferences( FeatureMetrics featureMetricsSharedPreferences)98 public void setFeatureMetricsSharedPreferences( 99 FeatureMetrics featureMetricsSharedPreferences) { 100 mFeatureMetricsSharedPreferences = featureMetricsSharedPreferences; 101 } 102 103 /** 104 * Get current configuration channel set status 105 */ 106 @VisibleForTesting getCachedChannelSet()107 public HashSet<Pair<Integer, Integer>> getCachedChannelSet() { 108 return mConfigUpdatedCachedChannelSet; 109 } 110 111 /** 112 * CellbroadcastReceiverMetrics 113 * Logging featureUpdated as needed when alert message is received or channel range is updated 114 */ 115 public class FeatureMetrics implements Cloneable { 116 public static final String ALERT_IN_CALL = "enable_alert_handling_during_call"; 117 public static final String OVERRIDE_DND = "override_dnd"; 118 public static final String ROAMING_SUPPORT = "cmas_roaming_network_strings"; 119 public static final String STORE_SMS = "enable_write_alerts_to_sms_inbox"; 120 public static final String TEST_MODE = "testing_mode"; 121 public static final String TTS_MODE = "enable_alert_speech_default"; 122 public static final String TEST_MODE_ON_USER_BUILD = "allow_testing_mode_on_user_build"; 123 124 private boolean mAlertDuringCall; 125 private HashSet<Pair<Integer, Integer>> mDnDChannelSet; 126 private boolean mRoamingSupport; 127 private boolean mStoreSms; 128 private boolean mTestMode; 129 private boolean mEnableAlertSpeech; 130 private boolean mTestModeOnUserBuild; 131 132 private Context mContext; 133 FeatureMetrics(Context context)134 FeatureMetrics(Context context) { 135 mContext = context; 136 SharedPreferences sp = mContext.getSharedPreferences(CBR_METRIC_PREF, MODE_PRIVATE); 137 mAlertDuringCall = sp.getBoolean(ALERT_IN_CALL, false); 138 String strOverrideDnD = sp.getString(OVERRIDE_DND, String.valueOf(0)); 139 mDnDChannelSet = getChannelSetFromString(strOverrideDnD); 140 mRoamingSupport = sp.getBoolean(ROAMING_SUPPORT, false); 141 mStoreSms = sp.getBoolean(STORE_SMS, false); 142 mTestMode = sp.getBoolean(TEST_MODE, false); 143 mEnableAlertSpeech = sp.getBoolean(TTS_MODE, true); 144 mTestModeOnUserBuild = sp.getBoolean(TEST_MODE_ON_USER_BUILD, true); 145 } 146 147 @Override hashCode()148 public int hashCode() { 149 return Objects.hash(mDnDChannelSet, mAlertDuringCall, mRoamingSupport, mStoreSms, 150 mTestMode, mEnableAlertSpeech, mTestModeOnUserBuild); 151 } 152 153 @Override equals(Object object)154 public boolean equals(Object object) { 155 if (object instanceof FeatureMetrics) { 156 FeatureMetrics features = (FeatureMetrics) object; 157 return (this.mAlertDuringCall == features.mAlertDuringCall 158 && this.mDnDChannelSet.equals(features.mDnDChannelSet) 159 && this.mRoamingSupport == features.mRoamingSupport 160 && this.mStoreSms == features.mStoreSms 161 && this.mTestMode == features.mTestMode 162 && this.mEnableAlertSpeech == features.mEnableAlertSpeech 163 && this.mTestModeOnUserBuild == features.mTestModeOnUserBuild); 164 } 165 return false; 166 } 167 168 @Override clone()169 public Object clone() throws CloneNotSupportedException { 170 FeatureMetrics copy = (FeatureMetrics) super.clone(); 171 copy.mDnDChannelSet = new HashSet<>(); 172 copy.mDnDChannelSet.addAll(this.mDnDChannelSet); 173 return copy; 174 } 175 176 /** 177 * Get current status whether alert during call is enabled 178 */ 179 @VisibleForTesting isAlertDuringCall()180 public boolean isAlertDuringCall() { 181 return mAlertDuringCall; 182 } 183 184 /** 185 * Get current do not disturb channels set 186 */ 187 @VisibleForTesting getDnDChannelSet()188 public HashSet<Pair<Integer, Integer>> getDnDChannelSet() { 189 return mDnDChannelSet; 190 } 191 192 /** 193 * Get whether currently roaming supported 194 */ 195 @VisibleForTesting isRoamingSupport()196 public boolean isRoamingSupport() { 197 return mRoamingSupport; 198 } 199 200 /** 201 * Get whether alert messages are saved inbox 202 */ 203 @VisibleForTesting isStoreSms()204 public boolean isStoreSms() { 205 return mStoreSms; 206 } 207 208 /** 209 * Get whether test mode is enabled 210 */ 211 @VisibleForTesting isTestMode()212 public boolean isTestMode() { 213 return mTestMode; 214 } 215 216 /** 217 * Get whether alert message support text to speech 218 */ 219 @VisibleForTesting isEnableAlertSpeech()220 public boolean isEnableAlertSpeech() { 221 return mEnableAlertSpeech; 222 } 223 224 /** 225 * Get whether test mode is not supporting in user build status 226 */ 227 @VisibleForTesting isTestModeOnUserBuild()228 public boolean isTestModeOnUserBuild() { 229 return mTestModeOnUserBuild; 230 } 231 232 /** 233 * Set alert during call 234 * 235 * @param current : current status of alert during call 236 */ 237 @VisibleForTesting onChangedAlertDuringCall(boolean current)238 public void onChangedAlertDuringCall(boolean current) { 239 mAlertDuringCall = current; 240 } 241 242 /** 243 * Set alert during call 244 * 245 * @param channelManager : channel manager to get channel range supporting override dnd 246 * @param overAllDnD : whether override dnd is fully supported or not 247 */ 248 @VisibleForTesting onChangedOverrideDnD( CellBroadcastChannelManager channelManager, boolean overAllDnD)249 public void onChangedOverrideDnD( 250 CellBroadcastChannelManager channelManager, boolean overAllDnD) { 251 mDnDChannelSet.clear(); 252 if (overAllDnD) { 253 mDnDChannelSet.add(new Pair(Integer.MAX_VALUE, Integer.MAX_VALUE)); 254 } else { 255 channelManager.getAllCellBroadcastChannelRanges().forEach(r -> { 256 if (r.mOverrideDnd) { 257 mDnDChannelSet.add(new Pair(r.mStartId, r.mEndId)); 258 } 259 }); 260 if (mDnDChannelSet.size() == 0) { 261 mDnDChannelSet.add(new Pair(0, 0)); 262 } 263 } 264 } 265 266 /** 267 * Set roaming support 268 * 269 * @param current : current status of roaming support 270 */ 271 @VisibleForTesting onChangedRoamingSupport(boolean current)272 public void onChangedRoamingSupport(boolean current) { 273 mRoamingSupport = current; 274 } 275 276 /** 277 * Set current status of storing alert message inbox 278 * 279 * @param current : current status value of storing inbox 280 */ 281 @VisibleForTesting onChangedStoreSms(boolean current)282 public void onChangedStoreSms(boolean current) { 283 mStoreSms = current; 284 } 285 286 /** 287 * Set current status of test-mode 288 * 289 * @param current : current status value of test-mode 290 */ 291 @VisibleForTesting onChangedTestMode(boolean current)292 public void onChangedTestMode(boolean current) { 293 mTestMode = current; 294 } 295 296 /** 297 * Set whether text to speech is supported for alert message 298 * 299 * @param current : current status tts 300 */ 301 @VisibleForTesting onChangedEnableAlertSpeech(boolean current)302 public void onChangedEnableAlertSpeech(boolean current) { 303 mEnableAlertSpeech = current; 304 } 305 306 /** 307 * Set whether test mode on user build is supported 308 * 309 * @param current : current status of test mode on user build 310 */ onChangedTestModeOnUserBuild(boolean current)311 public void onChangedTestModeOnUserBuild(boolean current) { 312 mTestModeOnUserBuild = current; 313 } 314 315 /** 316 * Calling check-in method for CB_SERVICE_FEATURE 317 */ 318 @VisibleForTesting logFeatureChanged()319 public void logFeatureChanged() { 320 try { 321 CellBroadcastModuleStatsLog.write( 322 CellBroadcastModuleStatsLog.CB_RECEIVER_FEATURE_CHANGED, 323 mAlertDuringCall, 324 convertToProtoBuffer(mDnDChannelSet), 325 mRoamingSupport, 326 mStoreSms, 327 mTestMode, 328 mEnableAlertSpeech, 329 mTestModeOnUserBuild); 330 } catch (IOException e) { 331 Log.e(TAG, "IOException while encoding array byte from channel set" + e); 332 } 333 if (VDBG) Log.d(TAG, this.toString()); 334 } 335 336 /** 337 * Update preferences for receiver feature metrics 338 */ updateSharedPreferences()339 public void updateSharedPreferences() { 340 SharedPreferences sp = 341 mContext.getSharedPreferences(CBR_METRIC_PREF, MODE_PRIVATE); 342 SharedPreferences.Editor editor = sp.edit(); 343 editor.putBoolean(ALERT_IN_CALL, mAlertDuringCall); 344 editor.putString(OVERRIDE_DND, getStringFromChannelSet(mDnDChannelSet)); 345 editor.putBoolean(ROAMING_SUPPORT, mRoamingSupport); 346 editor.putBoolean(STORE_SMS, mStoreSms); 347 editor.putBoolean(TEST_MODE, mTestMode); 348 editor.putBoolean(TTS_MODE, mEnableAlertSpeech); 349 editor.putBoolean(TEST_MODE_ON_USER_BUILD, mTestModeOnUserBuild); 350 editor.apply(); 351 } 352 353 @Override toString()354 public String toString() { 355 return "CellBroadcast_Receiver_Feature : " 356 + "mAlertDuringCall = " + mAlertDuringCall + " | " 357 + "mOverrideDnD = " + getStringFromChannelSet(mDnDChannelSet) + " | " 358 + "mRoamingSupport = " + mRoamingSupport + " | " 359 + "mStoreSms = " + mStoreSms + " | " 360 + "mTestMode = " + mTestMode + " | " 361 + "mEnableAlertSpeech = " + mEnableAlertSpeech + " | " 362 + "mTestModeOnUserBuild = " + mTestModeOnUserBuild; 363 } 364 } 365 366 /** 367 * Get current feature metrics 368 * 369 * @param context : Context 370 */ 371 @VisibleForTesting getFeatureMetrics(Context context)372 public FeatureMetrics getFeatureMetrics(Context context) { 373 if (mFeatureMetrics == null) { 374 mFeatureMetrics = new FeatureMetrics(context); 375 mFeatureMetricsSharedPreferences = new FeatureMetrics(context); 376 } 377 return mFeatureMetrics; 378 } 379 380 381 /** 382 * Convert ChannelSet to ProtoBuffer 383 * 384 * @param rangeList : channel range set 385 */ 386 @VisibleForTesting convertToProtoBuffer(HashSet<Pair<Integer, Integer>> rangeList)387 public byte[] convertToProtoBuffer(HashSet<Pair<Integer, Integer>> rangeList) 388 throws IOException { 389 Cellbroadcastmetric.CellBroadcastChannelRangesProto.Builder rangeListBuilder = 390 Cellbroadcastmetric.CellBroadcastChannelRangesProto.newBuilder(); 391 rangeList.stream().sorted((o1, o2) -> Objects.equals(o1.first, o2.first) 392 ? 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