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