1 /* 2 * Copyright (C) 2011 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 com.android.cellbroadcastservice.CellBroadcastMetrics.ERRSRC_CBR; 20 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_PREFMIGRATION; 21 import static com.android.cellbroadcastservice.CellBroadcastMetrics.RPT_SPC; 22 import static com.android.cellbroadcastservice.CellBroadcastMetrics.SRC_CBR; 23 24 import android.annotation.NonNull; 25 import android.app.ActivityManager; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.ContentProviderClient; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.SharedPreferences; 32 import android.content.SharedPreferences.Editor; 33 import android.content.pm.ActivityInfo; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.PackageManager; 36 import android.content.res.Resources; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.RemoteException; 40 import android.os.SystemProperties; 41 import android.os.UserManager; 42 import android.provider.Telephony; 43 import android.provider.Telephony.CellBroadcasts; 44 import android.telephony.CarrierConfigManager; 45 import android.telephony.ServiceState; 46 import android.telephony.SubscriptionManager; 47 import android.telephony.TelephonyManager; 48 import android.telephony.cdma.CdmaSmsCbProgramData; 49 import android.text.TextUtils; 50 import android.util.ArrayMap; 51 import android.util.EventLog; 52 import android.util.Log; 53 import android.widget.Toast; 54 55 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 56 import androidx.preference.PreferenceManager; 57 58 import com.android.internal.annotations.VisibleForTesting; 59 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Locale; 63 import java.util.Map; 64 65 public class CellBroadcastReceiver extends BroadcastReceiver { 66 private static final String TAG = "CellBroadcastReceiver"; 67 static final boolean DBG = true; 68 static final boolean VDBG = false; // STOPSHIP: change to false before ship 69 70 // Key to access the shared preference of reminder interval default value. 71 @VisibleForTesting 72 public static final String CURRENT_INTERVAL_DEFAULT = "current_interval_default"; 73 74 // Key to access the shared preference of cell broadcast testing mode. 75 @VisibleForTesting 76 public static final String TESTING_MODE = "testing_mode"; 77 78 // Key to access the shared preference of service state. 79 private static final String SERVICE_STATE = "service_state"; 80 81 // Key to access the shared preference of roaming operator. 82 private static final String ROAMING_OPERATOR_SUPPORTED = "roaming_operator_supported"; 83 84 // shared preference under developer settings 85 private static final String ENABLE_ALERT_MASTER_PREF = "enable_alerts_master_toggle"; 86 87 // shared preference for alert reminder interval 88 private static final String ALERT_REMINDER_INTERVAL_PREF = "alert_reminder_interval"; 89 90 // SharedPreferences key used to store the last carrier 91 private static final String CARRIER_ID_FOR_DEFAULT_SUB_PREF = "carrier_id_for_default_sub"; 92 // initial value for saved carrier ID. This helps us detect newly updated users or first boot 93 private static final int NO_PREVIOUS_CARRIER_ID = -2; 94 95 public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; 96 public static final String EXTRA_VOICE_REG_STATE = "voiceRegState"; 97 98 // Intent actions and extras 99 public static final String CELLBROADCAST_START_CONFIG_ACTION = 100 "com.android.cellbroadcastreceiver.intent.START_CONFIG"; 101 public static final String ACTION_MARK_AS_READ = 102 "com.android.cellbroadcastreceiver.intent.action.MARK_AS_READ"; 103 public static final String EXTRA_DELIVERY_TIME = 104 "com.android.cellbroadcastreceiver.intent.extra.ID"; 105 public static final String EXTRA_NOTIF_ID = 106 "com.android.cellbroadcastreceiver.intent.extra.NOTIF_ID"; 107 108 public static final String ACTION_TESTING_MODE_CHANGED = 109 "com.android.cellbroadcastreceiver.intent.ACTION_TESTING_MODE_CHANGED"; 110 111 // System property to set roaming network config which can be multiple items split by 112 // comma, and matched in sequence. This config will insert before the overlay. 113 private static final String ROAMING_PLMN_SUPPORTED_PROPERTY_KEY = 114 "persist.cellbroadcast.roaming_plmn_supported"; 115 116 private static final String MOCK_MODEM_BASEBAND = "mock-modem-service"; 117 118 private Context mContext; 119 120 // This is to map the iso country code to the MCC string 121 private Map<String, String> mMccMap; 122 123 /** 124 * this method is to make this class unit-testable, because CellBroadcastSettings.getResources() 125 * is a static method and cannot be stubbed. 126 * 127 * @return resources 128 */ 129 @VisibleForTesting getResourcesMethod()130 public Resources getResourcesMethod() { 131 return CellBroadcastSettings.getResourcesForDefaultSubId(mContext); 132 } 133 134 @Override onReceive(Context context, Intent intent)135 public void onReceive(Context context, Intent intent) { 136 if (DBG) log("onReceive " + intent); 137 138 mContext = context.getApplicationContext(); 139 String action = intent.getAction(); 140 Resources res = getResourcesMethod(); 141 142 if (mMccMap == null) { 143 mMccMap = getMccMap(res); 144 } 145 146 if (ACTION_MARK_AS_READ.equals(action)) { 147 // The only way this'll be called is if someone tries to maliciously set something 148 // as read. Log an event. 149 EventLog.writeEvent(0x534e4554, "162741784", -1, null); 150 } else if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) { 151 if (!intent.getBooleanExtra( 152 "android.telephony.extra.REBROADCAST_ON_UNLOCK", false)) { 153 resetCellBroadcastChannelRanges(); 154 int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, 155 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 156 initializeSharedPreference(context, subId); 157 enableLauncher(); 158 startConfigServiceToEnableChannels(); 159 160 // Some OEMs do not have legacyMigrationProvider active during boot-up, thus we 161 // need to retry data migration from another trigger point. 162 boolean hasMigrated = getDefaultSharedPreferences() 163 .getBoolean(CellBroadcastDatabaseHelper.KEY_LEGACY_DATA_MIGRATION, false); 164 if (res.getBoolean(R.bool.retry_message_history_data_migration) && !hasMigrated) { 165 // migrate message history from legacy app on a background thread. 166 new CellBroadcastContentProvider.AsyncCellBroadcastTask( 167 mContext.getContentResolver()).execute( 168 (CellBroadcastContentProvider.CellBroadcastOperation) provider -> { 169 provider.call(CellBroadcastContentProvider.CALL_MIGRATION_METHOD, 170 null, null); 171 return true; 172 }); 173 } 174 } 175 } else if (ACTION_SERVICE_STATE.equals(action)) { 176 // lower layer clears channel configurations under APM, thus need to resend 177 // configurations once moving back from APM. This should be fixed in lower layer 178 // going forward. 179 int ss = intent.getIntExtra(EXTRA_VOICE_REG_STATE, ServiceState.STATE_IN_SERVICE); 180 onServiceStateChanged(context, res, ss); 181 } else if (SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED.equals(action)) { 182 if (!isMockModemRunning()) { 183 startConfigServiceToEnableChannels(); 184 } 185 } else if (Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED.equals(action) || 186 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { 187 intent.setClass(mContext, CellBroadcastAlertService.class); 188 mContext.startService(intent); 189 } else if (Telephony.Sms.Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION 190 .equals(action)) { 191 ArrayList<CdmaSmsCbProgramData> programDataList = 192 intent.getParcelableArrayListExtra("program_data"); 193 194 CellBroadcastReceiverMetrics.getInstance().logMessageReported(mContext, 195 RPT_SPC, SRC_CBR, 0, 0, "", ""); 196 197 if (programDataList != null) { 198 handleCdmaSmsCbProgramData(programDataList); 199 } else { 200 loge("SCPD intent received with no program_data"); 201 } 202 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 203 // rename registered notification channels on locale change 204 CellBroadcastAlertService.createNotificationChannels(mContext); 205 } else if (TelephonyManager.ACTION_SECRET_CODE.equals(action)) { 206 if (SystemProperties.getInt("ro.debuggable", 0) == 1 207 || res.getBoolean(R.bool.allow_testing_mode_on_user_build)) { 208 setTestingMode(!isTestingMode(mContext)); 209 int msgId = (isTestingMode(mContext)) ? R.string.testing_mode_enabled 210 : R.string.testing_mode_disabled; 211 CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(mContext) 212 .onChangedTestMode(isTestingMode(mContext)); 213 String msg = res.getString(msgId); 214 Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); 215 LocalBroadcastManager.getInstance(mContext) 216 .sendBroadcast(new Intent(ACTION_TESTING_MODE_CHANGED)); 217 log(msg); 218 } else { 219 if (!res.getBoolean(R.bool.allow_testing_mode_on_user_build)) { 220 CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(mContext) 221 .onChangedTestModeOnUserBuild(false); 222 } 223 } 224 } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { 225 new CellBroadcastContentProvider.AsyncCellBroadcastTask( 226 mContext.getContentResolver()).execute((CellBroadcastContentProvider 227 .CellBroadcastOperation) provider -> { 228 provider.resyncToSmsInbox(mContext); 229 return true; 230 }); 231 } else { 232 Log.w(TAG, "onReceive() unexpected action " + action); 233 } 234 } 235 236 237 /** 238 * Get SystemProperties values 239 * 240 * @param key string to use get the value 241 * @return the matched value, but default "" for unmatched case. 242 */ 243 @VisibleForTesting getSystemProperties(String key)244 public String getSystemProperties(String key) { 245 return SystemProperties.get(key, "").trim(); 246 } 247 onServiceStateChanged(Context context, Resources res, int ss)248 private void onServiceStateChanged(Context context, Resources res, int ss) { 249 logd("onServiceStateChanged, ss: " + ss); 250 // check whether to support roaming network 251 String roamingOperator = null; 252 if (ss != ServiceState.STATE_POWER_OFF) { 253 TelephonyManager tm = context.getSystemService(TelephonyManager.class); 254 String networkOperator = tm.getNetworkOperator(); 255 logd("networkOperator: " + networkOperator); 256 257 // check the mcc on emergency only mode 258 if (TextUtils.isEmpty(networkOperator)) { 259 String countryCode = null; 260 try { 261 countryCode = tm.getNetworkCountryIso(); 262 } catch (IllegalArgumentException e) { 263 loge("IllegalArgumentException while getting network country iso" + e); 264 } 265 if (mMccMap != null && !TextUtils.isEmpty(countryCode)) { 266 networkOperator = mMccMap.get(countryCode.toLowerCase(Locale.ROOT).trim()); 267 logd("networkOperator on emergency mode: " + networkOperator 268 + " for the country code: " + countryCode); 269 } 270 } 271 272 // check roaming config only if the network oprator is not empty as the config 273 // is based on operator numeric 274 if (!TextUtils.isEmpty(networkOperator)) { 275 // No roaming supported by default 276 roamingOperator = ""; 277 if ((tm.isNetworkRoaming() || ss != ServiceState.STATE_IN_SERVICE) 278 && !networkOperator.equals(tm.getSimOperator())) { 279 String propRoamingPlmn = 280 getSystemProperties(ROAMING_PLMN_SUPPORTED_PROPERTY_KEY); 281 String[] roamingNetworks = propRoamingPlmn.isEmpty() ? res.getStringArray( 282 R.array.cmas_roaming_network_strings) : propRoamingPlmn.split(","); 283 logd("roamingNetworks: " + Arrays.toString(roamingNetworks)); 284 285 for (String r : roamingNetworks) { 286 r = r.trim(); 287 if (r.equals("XXXXXX")) { 288 //match any roaming network, store mcc+mnc 289 roamingOperator = networkOperator; 290 break; 291 } else if (r.equals("XXX")) { 292 if (tm.getSimOperator() != null) { 293 String networkMcc = networkOperator.substring(0, 3); 294 // empty sim case or inserted sim but different mcc case 295 if (!tm.getSimOperator().startsWith(networkMcc)) { 296 //match any roaming network, only store mcc 297 roamingOperator = networkMcc; 298 } 299 } 300 break; 301 } else if (networkOperator.startsWith(r)) { 302 roamingOperator = r; 303 break; 304 } 305 } 306 } 307 } 308 } 309 310 if ((ss != ServiceState.STATE_POWER_OFF 311 && getServiceState(context) == ServiceState.STATE_POWER_OFF) 312 || (roamingOperator != null && !roamingOperator.equals( 313 getRoamingOperatorSupported(context)))) { 314 if (!isMockModemRunning()) { 315 startConfigServiceToEnableChannels(); 316 } 317 } 318 setServiceState(ss); 319 320 if (roamingOperator != null) { 321 log("update supported roaming operator as " + roamingOperator); 322 setRoamingOperatorSupported(roamingOperator); 323 } 324 CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(mContext) 325 .onChangedRoamingSupport(!TextUtils.isEmpty(roamingOperator) ? true : false); 326 } 327 328 /** 329 * Initialize the MCC mapping table 330 */ 331 @VisibleForTesting 332 @NonNull getMccMap(@onNull Resources res)333 public static Map<String, String> getMccMap(@NonNull Resources res) { 334 String[] arr = res.getStringArray(R.array.iso_country_code_mcc_table); 335 Map<String, String> map = new ArrayMap<>(arr.length); 336 337 for (String item : arr) { 338 String[] val = item.split(":"); 339 if (val.length > 1) { 340 map.put(val[0].toLowerCase(Locale.ROOT).trim(), val[1].trim()); 341 } 342 } 343 344 return map; 345 } 346 347 /** 348 * Send an intent to reset the users WEA settings if there is a new carrier on the default subId 349 * 350 * The settings will be reset only when a new carrier is detected on the default subId. So it 351 * tracks the previous carrier id, and ignores the case that the current carrier id is changed 352 * to invalid. In case of the first boot with a sim on the new device, FDR, or upgrade from Q, 353 * the carrier id will be stored as there is no previous carrier id, but the settings will not 354 * be reset. 355 * 356 * Do nothing in other cases: 357 * - SIM insertion for the non-default subId 358 * - SIM insertion/bootup with no new carrier 359 * - SIM removal 360 * - Device just received the update which adds this carrier tracking logic 361 * 362 * @param context the context 363 * @param subId subId of the carrier config event 364 */ resetSettingsAsNeeded(Context context, int subId)365 private void resetSettingsAsNeeded(Context context, int subId) { 366 final int defaultSubId = SubscriptionManager.getDefaultSubscriptionId(); 367 368 // subId may be -1 if carrier config broadcast is being sent on SIM removal 369 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 370 if (defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 371 Log.d(TAG, "ignoring carrier config broadcast because subId=-1 and it's not" 372 + " defaultSubId when device is support multi-sim"); 373 return; 374 } 375 376 if (getPreviousCarrierIdForDefaultSub() == NO_PREVIOUS_CARRIER_ID) { 377 // on first boot only, if no SIM is inserted we save the carrier ID -1. 378 // This allows us to detect the carrier change from -1 to the carrier of the first 379 // SIM when it is inserted. 380 saveCarrierIdForDefaultSub(TelephonyManager.UNKNOWN_CARRIER_ID); 381 } 382 Log.d(TAG, "ignoring carrier config broadcast because subId=-1"); 383 return; 384 } 385 386 Log.d(TAG, "subId=" + subId + " defaultSubId=" + defaultSubId); 387 if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 388 Log.d(TAG, "ignoring carrier config broadcast because defaultSubId=-1"); 389 return; 390 } 391 392 if (subId != defaultSubId) { 393 Log.d(TAG, "ignoring carrier config broadcast for subId=" + subId 394 + " because it does not match defaultSubId=" + defaultSubId); 395 return; 396 } 397 398 TelephonyManager tm = context.getSystemService(TelephonyManager.class); 399 // carrierId is loaded before carrier config, so this should be safe 400 int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId(); 401 if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 402 Log.e(TAG, "ignoring unknown carrier ID"); 403 return; 404 } 405 406 int previousCarrierId = getPreviousCarrierIdForDefaultSub(); 407 if (previousCarrierId == NO_PREVIOUS_CARRIER_ID) { 408 // on first boot if a SIM is inserted, assume it is not new and don't apply settings 409 Log.d(TAG, "ignoring carrier config broadcast for subId=" + subId 410 + " for first boot"); 411 saveCarrierIdForDefaultSub(carrierId); 412 return; 413 } 414 415 /** When user_build_mode is true and alow_testing_mode_on_user_build is false 416 * then testing_mode is not able to be true at all. 417 */ 418 Resources res = getResourcesMethod(); 419 if (!res.getBoolean(R.bool.allow_testing_mode_on_user_build) 420 && SystemProperties.getInt("ro.debuggable", 0) == 0 421 && CellBroadcastReceiver.isTestingMode(context)) { 422 CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(context) 423 .onChangedTestModeOnUserBuild(false); 424 Log.d(TAG, "it can't be testing_mode at all"); 425 setTestingMode(false); 426 } 427 428 if (carrierId != previousCarrierId) { 429 saveCarrierIdForDefaultSub(carrierId); 430 startConfigService(context, 431 CellBroadcastConfigService.ACTION_UPDATE_SETTINGS_FOR_CARRIER); 432 } else { 433 Log.d(TAG, "reset settings as needed for subId=" + subId + ", carrierId=" + carrierId); 434 Intent intent = new Intent(CellBroadcastConfigService.ACTION_RESET_SETTINGS_AS_NEEDED, 435 null, context, CellBroadcastConfigService.class); 436 intent.putExtra(CellBroadcastConfigService.EXTRA_SUB, subId); 437 context.startService(intent); 438 } 439 } 440 getPreviousCarrierIdForDefaultSub()441 private int getPreviousCarrierIdForDefaultSub() { 442 return getDefaultSharedPreferences() 443 .getInt(CARRIER_ID_FOR_DEFAULT_SUB_PREF, NO_PREVIOUS_CARRIER_ID); 444 } 445 446 447 /** 448 * store carrierId corresponding to the default subId. 449 */ 450 @VisibleForTesting saveCarrierIdForDefaultSub(int carrierId)451 public void saveCarrierIdForDefaultSub(int carrierId) { 452 getDefaultSharedPreferences().edit().putInt(CARRIER_ID_FOR_DEFAULT_SUB_PREF, carrierId) 453 .apply(); 454 } 455 456 /** 457 * Enable/disable cell broadcast receiver testing mode. 458 * 459 * @param on {@code true} if testing mode is on, otherwise off. 460 */ 461 @VisibleForTesting setTestingMode(boolean on)462 public void setTestingMode(boolean on) { 463 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 464 sp.edit().putBoolean(TESTING_MODE, on).commit(); 465 } 466 467 /** 468 * @return {@code true} if operating in testing mode, which enables some features for testing 469 * purposes. 470 */ isTestingMode(Context context)471 public static boolean isTestingMode(Context context) { 472 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 473 return sp.getBoolean(TESTING_MODE, false); 474 } 475 476 /** 477 * Store the current service state for voice registration. 478 * 479 * @param ss current voice registration service state. 480 */ setServiceState(int ss)481 private void setServiceState(int ss) { 482 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 483 sp.edit().putInt(SERVICE_STATE, ss).commit(); 484 } 485 486 /** 487 * Store the roaming operator 488 */ setRoamingOperatorSupported(String roamingOperator)489 private void setRoamingOperatorSupported(String roamingOperator) { 490 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 491 sp.edit().putString(ROAMING_OPERATOR_SUPPORTED, roamingOperator).commit(); 492 } 493 494 /** 495 * @return the stored voice registration service state 496 */ getServiceState(Context context)497 private static int getServiceState(Context context) { 498 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 499 return sp.getInt(SERVICE_STATE, ServiceState.STATE_IN_SERVICE); 500 } 501 502 /** 503 * @return the supported roaming operator 504 */ getRoamingOperatorSupported(Context context)505 public static String getRoamingOperatorSupported(Context context) { 506 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 507 return sp.getString(ROAMING_OPERATOR_SUPPORTED, ""); 508 } 509 510 /** 511 * update reminder interval 512 */ 513 @VisibleForTesting adjustReminderInterval()514 public void adjustReminderInterval() { 515 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 516 String currentIntervalDefault = sp.getString(CURRENT_INTERVAL_DEFAULT, "0"); 517 518 // If interval default changes, reset the interval to the new default value. 519 String newIntervalDefault = CellBroadcastSettings.getResourcesForDefaultSubId(mContext) 520 .getString(R.string.alert_reminder_interval_in_min_default); 521 if (!newIntervalDefault.equals(currentIntervalDefault)) { 522 Log.d(TAG, "Default interval changed from " + currentIntervalDefault + " to " + 523 newIntervalDefault); 524 525 Editor editor = sp.edit(); 526 // Reset the value to default. 527 editor.putString( 528 CellBroadcastSettings.KEY_ALERT_REMINDER_INTERVAL, newIntervalDefault); 529 // Save the new default value. 530 editor.putString(CURRENT_INTERVAL_DEFAULT, newIntervalDefault); 531 editor.commit(); 532 } else { 533 if (DBG) Log.d(TAG, "Default interval " + currentIntervalDefault + " did not change."); 534 } 535 } 536 537 /** 538 * This method's purpose is to enable unit testing 539 * 540 * @return sharedePreferences for mContext 541 */ 542 @VisibleForTesting getDefaultSharedPreferences()543 public SharedPreferences getDefaultSharedPreferences() { 544 return PreferenceManager.getDefaultSharedPreferences(mContext); 545 } 546 547 /** 548 * return if there are default values in shared preferences 549 * 550 * @return boolean 551 */ 552 @VisibleForTesting sharedPrefsHaveDefaultValues()553 public Boolean sharedPrefsHaveDefaultValues() { 554 return mContext.getSharedPreferences(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, 555 Context.MODE_PRIVATE).getBoolean(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, 556 false); 557 } 558 559 /** 560 * initialize shared preferences before starting services 561 */ 562 @VisibleForTesting initializeSharedPreference(Context context, int subId)563 public void initializeSharedPreference(Context context, int subId) { 564 if (isSystemUser()) { 565 Log.d(TAG, "initializeSharedPreference"); 566 567 resetSettingsAsNeeded(context, subId); 568 569 SharedPreferences sp = getDefaultSharedPreferences(); 570 571 if (!sharedPrefsHaveDefaultValues()) { 572 // Sets the default values of the shared preference if there isn't any. 573 PreferenceManager.setDefaultValues(mContext, R.xml.preferences, false); 574 575 sp.edit().putBoolean(CellBroadcastSettings.KEY_OVERRIDE_DND_SETTINGS_CHANGED, 576 false).apply(); 577 578 // migrate sharedpref from legacy app 579 migrateSharedPreferenceFromLegacy(); 580 581 // If the device is in test harness mode, we need to disable emergency alert by 582 // default. 583 if (ActivityManager.isRunningInUserTestHarness()) { 584 Log.d(TAG, "In test harness mode. Turn off emergency alert by default."); 585 sp.edit().putBoolean(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, 586 false).apply(); 587 } 588 } else { 589 Log.d(TAG, "Skip setting default values of shared preference."); 590 } 591 592 adjustReminderInterval(); 593 } else { 594 Log.e(TAG, "initializeSharedPreference: Not system user."); 595 } 596 } 597 598 /** 599 * migrate shared preferences from legacy content provider client 600 */ 601 @VisibleForTesting migrateSharedPreferenceFromLegacy()602 public void migrateSharedPreferenceFromLegacy() { 603 String[] PREF_KEYS = { 604 CellBroadcasts.Preference.ENABLE_CMAS_AMBER_PREF, 605 CellBroadcasts.Preference.ENABLE_AREA_UPDATE_INFO_PREF, 606 CellBroadcasts.Preference.ENABLE_TEST_ALERT_PREF, 607 CellBroadcasts.Preference.ENABLE_STATE_LOCAL_TEST_PREF, 608 CellBroadcasts.Preference.ENABLE_PUBLIC_SAFETY_PREF, 609 CellBroadcasts.Preference.ENABLE_CMAS_SEVERE_THREAT_PREF, 610 CellBroadcasts.Preference.ENABLE_CMAS_EXTREME_THREAT_PREF, 611 CellBroadcasts.Preference.ENABLE_CMAS_PRESIDENTIAL_PREF, 612 CellBroadcasts.Preference.ENABLE_EMERGENCY_PERF, 613 CellBroadcasts.Preference.ENABLE_ALERT_VIBRATION_PREF, 614 CellBroadcasts.Preference.ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF, 615 ENABLE_ALERT_MASTER_PREF, 616 ALERT_REMINDER_INTERVAL_PREF 617 }; 618 try (ContentProviderClient client = mContext.getContentResolver() 619 .acquireContentProviderClient(Telephony.CellBroadcasts.AUTHORITY_LEGACY)) { 620 if (client == null) { 621 Log.d(TAG, "No legacy provider available for sharedpreference migration"); 622 return; 623 } 624 SharedPreferences.Editor sp = PreferenceManager 625 .getDefaultSharedPreferences(mContext).edit(); 626 for (String key : PREF_KEYS) { 627 try { 628 Bundle pref = client.call( 629 CellBroadcasts.AUTHORITY_LEGACY, 630 CellBroadcasts.CALL_METHOD_GET_PREFERENCE, 631 key, null); 632 if (pref != null && pref.containsKey(key)) { 633 Object val = pref.get(key); 634 if (val == null) { 635 // noop - no value to set. 636 // Only support Boolean and String as preference types for now. 637 } else if (val instanceof Boolean) { 638 Log.d(TAG, "migrateSharedPreferenceFromLegacy: " + key + "val: " 639 + pref.getBoolean(key)); 640 sp.putBoolean(key, pref.getBoolean(key)); 641 } else if (val instanceof String) { 642 Log.d(TAG, "migrateSharedPreferenceFromLegacy: " + key + "val: " 643 + pref.getString(key)); 644 sp.putString(key, pref.getString(key)); 645 } 646 } else { 647 Log.d(TAG, "migrateSharedPreferenceFromLegacy: unsupported key: " + key); 648 } 649 } catch (RemoteException e) { 650 CellBroadcastReceiverMetrics.getInstance().logModuleError( 651 ERRSRC_CBR, ERRTYPE_PREFMIGRATION); 652 Log.e(TAG, "fails to get shared preference " + e); 653 } 654 } 655 sp.apply(); 656 } catch (Exception e) { 657 // We have to guard ourselves against any weird behavior of the 658 // legacy provider by trying to catch everything 659 loge("Failed migration from legacy provider: " + e); 660 } 661 } 662 663 /** 664 * Handle Service Category Program Data message. 665 * TODO: Send Service Category Program Results response message to sender 666 */ 667 @VisibleForTesting handleCdmaSmsCbProgramData(ArrayList<CdmaSmsCbProgramData> programDataList)668 public void handleCdmaSmsCbProgramData(ArrayList<CdmaSmsCbProgramData> programDataList) { 669 for (CdmaSmsCbProgramData programData : programDataList) { 670 switch (programData.getOperation()) { 671 case CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY: 672 tryCdmaSetCategory(mContext, programData.getCategory(), true); 673 break; 674 675 case CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY: 676 tryCdmaSetCategory(mContext, programData.getCategory(), false); 677 break; 678 679 case CdmaSmsCbProgramData.OPERATION_CLEAR_CATEGORIES: 680 tryCdmaSetCategory(mContext, 681 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT, false); 682 tryCdmaSetCategory(mContext, 683 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT, false); 684 tryCdmaSetCategory(mContext, 685 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, false); 686 tryCdmaSetCategory(mContext, 687 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE, false); 688 break; 689 690 default: 691 loge("Ignoring unknown SCPD operation " + programData.getOperation()); 692 } 693 } 694 } 695 696 /** 697 * set CDMA category in shared preferences 698 * @param context 699 * @param category CDMA category 700 * @param enable true for add category, false otherwise 701 */ 702 @VisibleForTesting tryCdmaSetCategory(Context context, int category, boolean enable)703 public void tryCdmaSetCategory(Context context, int category, boolean enable) { 704 SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); 705 706 switch (category) { 707 case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT: 708 sharedPrefs.edit().putBoolean( 709 CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, enable) 710 .apply(); 711 break; 712 713 case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT: 714 sharedPrefs.edit().putBoolean( 715 CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, enable) 716 .apply(); 717 break; 718 719 case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: 720 sharedPrefs.edit().putBoolean( 721 CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, enable).apply(); 722 break; 723 724 case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE: 725 sharedPrefs.edit().putBoolean( 726 CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, enable).apply(); 727 break; 728 729 default: 730 Log.w(TAG, "Ignoring SCPD command to " + (enable ? "enable" : "disable") 731 + " alerts in category " + category); 732 } 733 } 734 735 /** 736 * This method's purpose if to enable unit testing 737 * 738 * @return if the mContext user is a system user 739 */ isSystemUser()740 private boolean isSystemUser() { 741 return isSystemUser(mContext); 742 } 743 744 /** 745 * This method's purpose if to enable unit testing 746 */ 747 @VisibleForTesting startConfigServiceToEnableChannels()748 public void startConfigServiceToEnableChannels() { 749 startConfigService(mContext, CellBroadcastConfigService.ACTION_ENABLE_CHANNELS); 750 } 751 752 /** 753 * Check if user from context is system user 754 * @param context 755 * @return whether the user is system user 756 */ isSystemUser(Context context)757 private static boolean isSystemUser(Context context) { 758 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 759 return userManager.isSystemUser(); 760 } 761 762 /** 763 * Tell {@link CellBroadcastConfigService} to enable the CB channels. 764 * 765 * @param context the broadcast receiver context 766 */ startConfigService(Context context, String action)767 static void startConfigService(Context context, String action) { 768 if (isSystemUser(context)) { 769 Log.d(TAG, "Start Cell Broadcast configuration for intent=" + action); 770 context.startService(new Intent(action, null, context, 771 CellBroadcastConfigService.class)); 772 } else { 773 Log.e(TAG, "startConfigService: Not system user."); 774 } 775 } 776 777 /** 778 * Enable Launcher. 779 */ 780 @VisibleForTesting enableLauncher()781 public void enableLauncher() { 782 boolean enable = getResourcesMethod().getBoolean(R.bool.show_message_history_in_launcher); 783 final PackageManager pm = mContext.getPackageManager(); 784 // This alias presents the target activity, CellBroadcastListActivity, as a independent 785 // entity with its own intent filter for android.intent.category.LAUNCHER. 786 // This alias will be enabled/disabled at run-time based on resource overlay. Once enabled, 787 // it will appear in the Launcher as a top-level application 788 String aliasLauncherActivity = null; 789 try { 790 PackageInfo p = pm.getPackageInfo(mContext.getPackageName(), 791 PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS); 792 if (p != null) { 793 for (ActivityInfo activityInfo : p.activities) { 794 String targetActivity = activityInfo.targetActivity; 795 if (CellBroadcastListActivity.class.getName().equals(targetActivity)) { 796 aliasLauncherActivity = activityInfo.name; 797 break; 798 } 799 } 800 } 801 } catch (PackageManager.NameNotFoundException e) { 802 Log.e(TAG, e.toString()); 803 } 804 if (TextUtils.isEmpty(aliasLauncherActivity)) { 805 Log.e(TAG, "cannot find launcher activity"); 806 return; 807 } 808 809 if (enable) { 810 Log.d(TAG, "enable launcher activity: " + aliasLauncherActivity); 811 pm.setComponentEnabledSetting( 812 new ComponentName(mContext, aliasLauncherActivity), 813 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); 814 } else { 815 Log.d(TAG, "disable launcher activity: " + aliasLauncherActivity); 816 pm.setComponentEnabledSetting( 817 new ComponentName(mContext, aliasLauncherActivity), 818 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 819 } 820 } 821 822 /** 823 * Reset cached CellBroadcastChannelRanges 824 * 825 * This method's purpose is to enable unit testing 826 */ 827 @VisibleForTesting resetCellBroadcastChannelRanges()828 public void resetCellBroadcastChannelRanges() { 829 CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges(); 830 } 831 832 /** 833 * Check if mockmodem is running 834 * @return true if mockmodem service is running instead of real modem 835 */ 836 @VisibleForTesting isMockModemRunning()837 public boolean isMockModemRunning() { 838 return isMockModemBinded(); 839 } 840 841 /** 842 * Check if mockmodem is running 843 * @return true if mockmodem service is running instead of real modem 844 */ isMockModemBinded()845 public static boolean isMockModemBinded() { 846 String modem = Build.getRadioVersion(); 847 boolean isMockModem = modem != null ? modem.contains(MOCK_MODEM_BASEBAND) : false; 848 Log.d(TAG, "mockmodem is running? = " + isMockModem); 849 return isMockModem; 850 } 851 log(String msg)852 private static void log(String msg) { 853 Log.d(TAG, msg); 854 } 855 logd(String msg)856 private static void logd(String msg) { 857 if (DBG) Log.d(TAG, msg); 858 } 859 loge(String msg)860 private static void loge(String msg) { 861 Log.e(TAG, msg); 862 } 863 } 864