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.preference.PreferenceManager; 36 import android.provider.Telephony; 37 import android.provider.Telephony.CellBroadcasts; 38 import android.telephony.CarrierConfigManager; 39 import android.telephony.ServiceState; 40 import android.telephony.SubscriptionManager; 41 import android.telephony.TelephonyManager; 42 import android.telephony.cdma.CdmaSmsCbProgramData; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.widget.Toast; 46 47 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 51 import java.util.ArrayList; 52 53 public class CellBroadcastReceiver extends BroadcastReceiver { 54 private static final String TAG = "CellBroadcastReceiver"; 55 static final boolean DBG = true; 56 static final boolean VDBG = false; // STOPSHIP: change to false before ship 57 58 // Key to access the shared preference of reminder interval default value. 59 @VisibleForTesting 60 public static final String CURRENT_INTERVAL_DEFAULT = "current_interval_default"; 61 62 // Key to access the shared preference of cell broadcast testing mode. 63 @VisibleForTesting 64 public static final String TESTING_MODE = "testing_mode"; 65 66 // Key to access the shared preference of service state. 67 private static final String SERVICE_STATE = "service_state"; 68 69 public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; 70 public static final String EXTRA_VOICE_REG_STATE = "voiceRegState"; 71 72 // Intent actions and extras 73 public static final String CELLBROADCAST_START_CONFIG_ACTION = 74 "com.android.cellbroadcastreceiver.intent.START_CONFIG"; 75 public static final String ACTION_MARK_AS_READ = 76 "com.android.cellbroadcastreceiver.intent.action.MARK_AS_READ"; 77 public static final String EXTRA_DELIVERY_TIME = 78 "com.android.cellbroadcastreceiver.intent.extra.ID"; 79 80 public static final String ACTION_TESTING_MODE_CHANGED = 81 "com.android.cellbroadcastreceiver.intent.ACTION_TESTING_MODE_CHANGED"; 82 83 private Context mContext; 84 85 /** 86 * helper method for easier testing. To generate a new CellBroadcastTask 87 * @param deliveryTime message delivery time 88 */ 89 @VisibleForTesting getCellBroadcastTask(final long deliveryTime)90 public void getCellBroadcastTask(final long deliveryTime) { 91 new CellBroadcastContentProvider.AsyncCellBroadcastTask(mContext.getContentResolver()) 92 .execute(new CellBroadcastContentProvider.CellBroadcastOperation() { 93 @Override 94 public boolean execute(CellBroadcastContentProvider provider) { 95 return provider.markBroadcastRead(CellBroadcasts.DELIVERY_TIME, 96 deliveryTime); 97 } 98 }); 99 } 100 101 /** 102 * this method is to make this class unit-testable, because CellBroadcastSettings.getResources() 103 * is a static method and cannot be stubbed. 104 * @return resources 105 */ 106 @VisibleForTesting getResourcesMethod()107 public Resources getResourcesMethod() { 108 return CellBroadcastSettings.getResources(mContext, 109 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); 110 } 111 112 @Override onReceive(Context context, Intent intent)113 public void onReceive(Context context, Intent intent) { 114 if (DBG) log("onReceive " + intent); 115 116 mContext = context.getApplicationContext(); 117 String action = intent.getAction(); 118 Resources res = getResourcesMethod(); 119 120 if (ACTION_MARK_AS_READ.equals(action)) { 121 final long deliveryTime = intent.getLongExtra(EXTRA_DELIVERY_TIME, -1); 122 getCellBroadcastTask(deliveryTime); 123 } else if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) { 124 initializeSharedPreference(); 125 enableLauncher(); 126 startConfigService(); 127 } else if (ACTION_SERVICE_STATE.equals(action)) { 128 // lower layer clears channel configurations under APM, thus need to resend 129 // configurations once moving back from APM. This should be fixed in lower layer 130 // going forward. 131 int ss = intent.getIntExtra(EXTRA_VOICE_REG_STATE, ServiceState.STATE_IN_SERVICE); 132 if (ss != ServiceState.STATE_POWER_OFF 133 && getServiceState(context) == ServiceState.STATE_POWER_OFF) { 134 startConfigService(); 135 } 136 setServiceState(ss); 137 } else if (CELLBROADCAST_START_CONFIG_ACTION.equals(action) 138 || SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED.equals(action)) { 139 startConfigService(); 140 } else if (Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED.equals(action) || 141 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { 142 intent.setClass(mContext, CellBroadcastAlertService.class); 143 mContext.startService(intent); 144 } else if (Telephony.Sms.Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION 145 .equals(action)) { 146 ArrayList<CdmaSmsCbProgramData> programDataList = 147 intent.getParcelableArrayListExtra("program_data"); 148 if (programDataList != null) { 149 handleCdmaSmsCbProgramData(programDataList); 150 } else { 151 loge("SCPD intent received with no program_data"); 152 } 153 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 154 // rename registered notification channels on locale change 155 CellBroadcastAlertService.createNotificationChannels(mContext); 156 } else if (TelephonyManager.ACTION_SECRET_CODE.equals(action)) { 157 if (SystemProperties.getInt("ro.debuggable", 0) == 1 158 || res.getBoolean(R.bool.allow_testing_mode_on_user_build)) { 159 setTestingMode(!isTestingMode(mContext)); 160 int msgId = (isTestingMode(mContext)) ? R.string.testing_mode_enabled 161 : R.string.testing_mode_disabled; 162 String msg = res.getString(msgId); 163 Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); 164 LocalBroadcastManager.getInstance(mContext) 165 .sendBroadcast(new Intent(ACTION_TESTING_MODE_CHANGED)); 166 log(msg); 167 } 168 } else { 169 Log.w(TAG, "onReceive() unexpected action " + action); 170 } 171 } 172 173 /** 174 * Enable/disable cell broadcast receiver testing mode. 175 * 176 * @param on {@code true} if testing mode is on, otherwise off. 177 */ 178 @VisibleForTesting setTestingMode(boolean on)179 public void setTestingMode(boolean on) { 180 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 181 sp.edit().putBoolean(TESTING_MODE, on).commit(); 182 } 183 184 /** 185 * @return {@code true} if operating in testing mode, which enables some features for testing 186 * purposes. 187 */ isTestingMode(Context context)188 public static boolean isTestingMode(Context context) { 189 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 190 return sp.getBoolean(TESTING_MODE, false); 191 } 192 193 /** 194 * Store the current service state for voice registration. 195 * 196 * @param ss current voice registration service state. 197 */ setServiceState(int ss)198 private void setServiceState(int ss) { 199 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 200 sp.edit().putInt(SERVICE_STATE, ss).commit(); 201 } 202 203 /** 204 * @return the stored voice registration service state 205 */ getServiceState(Context context)206 private static int getServiceState(Context context) { 207 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 208 return sp.getInt(SERVICE_STATE, ServiceState.STATE_IN_SERVICE); 209 } 210 211 /** 212 * update reminder interval 213 */ 214 @VisibleForTesting adjustReminderInterval()215 public void adjustReminderInterval() { 216 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 217 String currentIntervalDefault = sp.getString(CURRENT_INTERVAL_DEFAULT, "0"); 218 219 // If interval default changes, reset the interval to the new default value. 220 String newIntervalDefault = CellBroadcastSettings.getResources(mContext, 221 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID).getString( 222 R.string.alert_reminder_interval_in_min_default); 223 if (!newIntervalDefault.equals(currentIntervalDefault)) { 224 Log.d(TAG, "Default interval changed from " + currentIntervalDefault + " to " + 225 newIntervalDefault); 226 227 Editor editor = sp.edit(); 228 // Reset the value to default. 229 editor.putString( 230 CellBroadcastSettings.KEY_ALERT_REMINDER_INTERVAL, newIntervalDefault); 231 // Save the new default value. 232 editor.putString(CURRENT_INTERVAL_DEFAULT, newIntervalDefault); 233 editor.commit(); 234 } else { 235 if (DBG) Log.d(TAG, "Default interval " + currentIntervalDefault + " did not change."); 236 } 237 } 238 /** 239 * This method's purpose if to enable unit testing 240 * @return sharedePreferences for mContext 241 */ 242 @VisibleForTesting getDefaultSharedPreferences()243 public SharedPreferences getDefaultSharedPreferences() { 244 return PreferenceManager.getDefaultSharedPreferences(mContext); 245 } 246 247 /** 248 * return if there are default values in shared preferences 249 * @return boolean 250 */ 251 @VisibleForTesting sharedPrefsHaveDefaultValues()252 public Boolean sharedPrefsHaveDefaultValues() { 253 return mContext.getSharedPreferences(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, 254 Context.MODE_PRIVATE).getBoolean(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, 255 false); 256 } 257 /** 258 * initialize shared preferences before starting services 259 */ 260 @VisibleForTesting initializeSharedPreference()261 public void initializeSharedPreference() { 262 if (isSystemUser()) { 263 Log.d(TAG, "initializeSharedPreference"); 264 SharedPreferences sp = getDefaultSharedPreferences(); 265 266 if (!sharedPrefsHaveDefaultValues()) { 267 // Sets the default values of the shared preference if there isn't any. 268 PreferenceManager.setDefaultValues(mContext, R.xml.preferences, false); 269 270 sp.edit().putBoolean(CellBroadcastSettings.KEY_OVERRIDE_DND_SETTINGS_CHANGED, 271 false).apply(); 272 273 // migrate sharedpref from legacy app 274 migrateSharedPreferenceFromLegacy(); 275 276 // If the device is in test harness mode, we need to disable emergency alert by 277 // default. 278 if (ActivityManager.isRunningInUserTestHarness()) { 279 Log.d(TAG, "In test harness mode. Turn off emergency alert by default."); 280 sp.edit().putBoolean(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, 281 false).apply(); 282 } 283 284 } else { 285 Log.d(TAG, "Skip setting default values of shared preference."); 286 } 287 288 adjustReminderInterval(); 289 } else { 290 Log.e(TAG, "initializeSharedPreference: Not system user."); 291 } 292 } 293 294 /** 295 * migrate shared preferences from legacy content provider client 296 */ 297 @VisibleForTesting migrateSharedPreferenceFromLegacy()298 public void migrateSharedPreferenceFromLegacy() { 299 String[] PREF_KEYS = { 300 CellBroadcasts.Preference.ENABLE_CMAS_AMBER_PREF, 301 CellBroadcasts.Preference.ENABLE_AREA_UPDATE_INFO_PREF, 302 CellBroadcasts.Preference.ENABLE_TEST_ALERT_PREF, 303 CellBroadcasts.Preference.ENABLE_STATE_LOCAL_TEST_PREF, 304 CellBroadcasts.Preference.ENABLE_PUBLIC_SAFETY_PREF, 305 CellBroadcasts.Preference.ENABLE_CMAS_SEVERE_THREAT_PREF, 306 CellBroadcasts.Preference.ENABLE_CMAS_EXTREME_THREAT_PREF, 307 CellBroadcasts.Preference.ENABLE_CMAS_PRESIDENTIAL_PREF, 308 CellBroadcasts.Preference.ENABLE_EMERGENCY_PERF, 309 CellBroadcasts.Preference.ENABLE_ALERT_VIBRATION_PREF, 310 CellBroadcasts.Preference.ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF, 311 }; 312 try (ContentProviderClient client = mContext.getContentResolver() 313 .acquireContentProviderClient(Telephony.CellBroadcasts.AUTHORITY_LEGACY)) { 314 if (client == null) { 315 Log.d(TAG, "No legacy provider available for sharedpreference migration"); 316 return; 317 } 318 SharedPreferences.Editor sp = PreferenceManager 319 .getDefaultSharedPreferences(mContext).edit(); 320 for (String key : PREF_KEYS) { 321 try { 322 Bundle pref = client.call( 323 CellBroadcasts.AUTHORITY_LEGACY, 324 CellBroadcasts.CALL_METHOD_GET_PREFERENCE, 325 key, null); 326 if (pref != null) { 327 Log.d(TAG, "migrateSharedPreferenceFromLegacy: " + key + "val: " 328 + pref.getBoolean(key)); 329 sp.putBoolean(key, pref.getBoolean(key)); 330 } else { 331 Log.d(TAG, "migrateSharedPreferenceFromLegacy: unsupported key: " + key); 332 } 333 } catch (RemoteException e) { 334 Log.e(TAG, "fails to get shared preference " + e); 335 } 336 } 337 sp.apply(); 338 } catch (Exception e) { 339 // We have to guard ourselves against any weird behavior of the 340 // legacy provider by trying to catch everything 341 loge("Failed migration from legacy provider: " + e); 342 } 343 } 344 345 /** 346 * Handle Service Category Program Data message. 347 * TODO: Send Service Category Program Results response message to sender 348 * 349 * @param programDataList 350 */ 351 @VisibleForTesting handleCdmaSmsCbProgramData(ArrayList<CdmaSmsCbProgramData> programDataList)352 public void handleCdmaSmsCbProgramData(ArrayList<CdmaSmsCbProgramData> programDataList) { 353 for (CdmaSmsCbProgramData programData : programDataList) { 354 switch (programData.getOperation()) { 355 case CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY: 356 tryCdmaSetCategory(mContext, programData.getCategory(), true); 357 break; 358 359 case CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY: 360 tryCdmaSetCategory(mContext, programData.getCategory(), false); 361 break; 362 363 case CdmaSmsCbProgramData.OPERATION_CLEAR_CATEGORIES: 364 tryCdmaSetCategory(mContext, 365 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT, false); 366 tryCdmaSetCategory(mContext, 367 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT, false); 368 tryCdmaSetCategory(mContext, 369 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, false); 370 tryCdmaSetCategory(mContext, 371 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE, false); 372 break; 373 374 default: 375 loge("Ignoring unknown SCPD operation " + programData.getOperation()); 376 } 377 } 378 } 379 380 /** 381 * set CDMA category in shared preferences 382 * @param context 383 * @param category CDMA category 384 * @param enable true for add category, false otherwise 385 */ 386 @VisibleForTesting tryCdmaSetCategory(Context context, int category, boolean enable)387 public void tryCdmaSetCategory(Context context, int category, boolean enable) { 388 SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); 389 390 switch (category) { 391 case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT: 392 sharedPrefs.edit().putBoolean( 393 CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, enable) 394 .apply(); 395 break; 396 397 case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT: 398 sharedPrefs.edit().putBoolean( 399 CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, enable) 400 .apply(); 401 break; 402 403 case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: 404 sharedPrefs.edit().putBoolean( 405 CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, enable).apply(); 406 break; 407 408 case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE: 409 sharedPrefs.edit().putBoolean( 410 CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, enable).apply(); 411 break; 412 413 default: 414 Log.w(TAG, "Ignoring SCPD command to " + (enable ? "enable" : "disable") 415 + " alerts in category " + category); 416 } 417 } 418 419 /** 420 * This method's purpose if to enable unit testing 421 * @return if the mContext user is a system user 422 */ 423 @VisibleForTesting isSystemUser()424 public boolean isSystemUser() { 425 return isSystemUser(mContext); 426 } 427 428 /** 429 * This method's purpose if to enable unit testing 430 */ 431 @VisibleForTesting startConfigService()432 public void startConfigService() { 433 startConfigService(mContext); 434 } 435 436 /** 437 * Check if user from context is system user 438 * @param context 439 * @return whether the user is system user 440 */ isSystemUser(Context context)441 private static boolean isSystemUser(Context context) { 442 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 443 return userManager.isSystemUser(); 444 } 445 446 /** 447 * Tell {@link CellBroadcastConfigService} to enable the CB channels. 448 * @param context the broadcast receiver context 449 */ startConfigService(Context context)450 static void startConfigService(Context context) { 451 if (isSystemUser(context)) { 452 Intent serviceIntent = new Intent(CellBroadcastConfigService.ACTION_ENABLE_CHANNELS, 453 null, context, CellBroadcastConfigService.class); 454 Log.d(TAG, "Start Cell Broadcast configuration."); 455 context.startService(serviceIntent); 456 } else { 457 Log.e(TAG, "startConfigService: Not system user."); 458 } 459 } 460 461 /** 462 * Enable Launcher. 463 */ 464 @VisibleForTesting enableLauncher()465 public void enableLauncher() { 466 boolean enable = getResourcesMethod().getBoolean(R.bool.show_message_history_in_launcher); 467 final PackageManager pm = mContext.getPackageManager(); 468 // This alias presents the target activity, CellBroadcastListActivity, as a independent 469 // entity with its own intent filter for android.intent.category.LAUNCHER. 470 // This alias will be enabled/disabled at run-time based on resource overlay. Once enabled, 471 // it will appear in the Launcher as a top-level application 472 String aliasLauncherActivity = null; 473 try { 474 PackageInfo p = pm.getPackageInfo(mContext.getPackageName(), 475 PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS); 476 if (p != null) { 477 for (ActivityInfo activityInfo : p.activities) { 478 String targetActivity = activityInfo.targetActivity; 479 if (CellBroadcastListActivity.class.getName().equals(targetActivity)) { 480 aliasLauncherActivity = activityInfo.name; 481 break; 482 } 483 } 484 } 485 } catch (PackageManager.NameNotFoundException e) { 486 Log.e(TAG, e.toString()); 487 } 488 if (TextUtils.isEmpty(aliasLauncherActivity)) { 489 Log.e(TAG, "cannot find launcher activity"); 490 return; 491 } 492 493 if (enable) { 494 Log.d(TAG, "enable launcher activity: " + aliasLauncherActivity); 495 pm.setComponentEnabledSetting( 496 new ComponentName(mContext, aliasLauncherActivity), 497 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); 498 } else { 499 Log.d(TAG, "disable launcher activity: " + aliasLauncherActivity); 500 pm.setComponentEnabledSetting( 501 new ComponentName(mContext, aliasLauncherActivity), 502 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 503 } 504 } 505 log(String msg)506 private static void log(String msg) { 507 Log.d(TAG, msg); 508 } 509 loge(String msg)510 private static void loge(String msg) { 511 Log.e(TAG, msg); 512 } 513 } 514