blob: cfdcc0480c083d78061c3b5c7873190b69d725f5 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cellbroadcastreceiver;
import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRSRC_CBR;
import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_PREFMIGRATION;
import static com.android.cellbroadcastservice.CellBroadcastMetrics.RPT_SPC;
import static com.android.cellbroadcastservice.CellBroadcastMetrics.SRC_CBR;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Telephony;
import android.provider.Telephony.CellBroadcasts;
import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
public class CellBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "CellBroadcastReceiver";
static final boolean DBG = true;
static final boolean VDBG = false; // STOPSHIP: change to false before ship
// Key to access the shared preference of reminder interval default value.
@VisibleForTesting
public static final String CURRENT_INTERVAL_DEFAULT = "current_interval_default";
// Key to access the shared preference of cell broadcast testing mode.
@VisibleForTesting
public static final String TESTING_MODE = "testing_mode";
// Key to access the shared preference of service state.
private static final String SERVICE_STATE = "service_state";
// Key to access the shared preference of roaming operator.
private static final String ROAMING_OPERATOR_SUPPORTED = "roaming_operator_supported";
// shared preference under developer settings
private static final String ENABLE_ALERT_MASTER_PREF = "enable_alerts_master_toggle";
// shared preference for alert reminder interval
private static final String ALERT_REMINDER_INTERVAL_PREF = "alert_reminder_interval";
// SharedPreferences key used to store the last carrier
private static final String CARRIER_ID_FOR_DEFAULT_SUB_PREF = "carrier_id_for_default_sub";
// initial value for saved carrier ID. This helps us detect newly updated users or first boot
private static final int NO_PREVIOUS_CARRIER_ID = -2;
public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
public static final String EXTRA_VOICE_REG_STATE = "voiceRegState";
// Intent actions and extras
public static final String CELLBROADCAST_START_CONFIG_ACTION =
"com.android.cellbroadcastreceiver.intent.START_CONFIG";
public static final String ACTION_MARK_AS_READ =
"com.android.cellbroadcastreceiver.intent.action.MARK_AS_READ";
public static final String EXTRA_DELIVERY_TIME =
"com.android.cellbroadcastreceiver.intent.extra.ID";
public static final String EXTRA_NOTIF_ID =
"com.android.cellbroadcastreceiver.intent.extra.NOTIF_ID";
public static final String ACTION_TESTING_MODE_CHANGED =
"com.android.cellbroadcastreceiver.intent.ACTION_TESTING_MODE_CHANGED";
// System property to set roaming network config which can be multiple items split by
// comma, and matched in sequence. This config will insert before the overlay.
private static final String ROAMING_PLMN_SUPPORTED_PROPERTY_KEY =
"persist.cellbroadcast.roaming_plmn_supported";
private static final String MOCK_MODEM_BASEBAND = "mock-modem-service";
private Context mContext;
/**
* this method is to make this class unit-testable, because CellBroadcastSettings.getResources()
* is a static method and cannot be stubbed.
*
* @return resources
*/
@VisibleForTesting
public Resources getResourcesMethod() {
return CellBroadcastSettings.getResourcesForDefaultSubId(mContext);
}
@Override
public void onReceive(Context context, Intent intent) {
if (DBG) log("onReceive " + intent);
mContext = context.getApplicationContext();
String action = intent.getAction();
Resources res = getResourcesMethod();
if (ACTION_MARK_AS_READ.equals(action)) {
// The only way this'll be called is if someone tries to maliciously set something
// as read. Log an event.
EventLog.writeEvent(0x534e4554, "162741784", -1, null);
} else if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
if (!intent.getBooleanExtra(
"android.telephony.extra.REBROADCAST_ON_UNLOCK", false)) {
resetCellBroadcastChannelRanges();
int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
initializeSharedPreference(context, subId);
enableLauncher();
startConfigServiceToEnableChannels();
// Some OEMs do not have legacyMigrationProvider active during boot-up, thus we
// need to retry data migration from another trigger point.
boolean hasMigrated = getDefaultSharedPreferences()
.getBoolean(CellBroadcastDatabaseHelper.KEY_LEGACY_DATA_MIGRATION, false);
if (res.getBoolean(R.bool.retry_message_history_data_migration) && !hasMigrated) {
// migrate message history from legacy app on a background thread.
new CellBroadcastContentProvider.AsyncCellBroadcastTask(
mContext.getContentResolver()).execute(
(CellBroadcastContentProvider.CellBroadcastOperation) provider -> {
provider.call(CellBroadcastContentProvider.CALL_MIGRATION_METHOD,
null, null);
return true;
});
}
}
} else if (ACTION_SERVICE_STATE.equals(action)) {
// lower layer clears channel configurations under APM, thus need to resend
// configurations once moving back from APM. This should be fixed in lower layer
// going forward.
int ss = intent.getIntExtra(EXTRA_VOICE_REG_STATE, ServiceState.STATE_IN_SERVICE);
onServiceStateChanged(context, res, ss);
} else if (SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED.equals(action)) {
if (!isMockModemRunning()) {
startConfigServiceToEnableChannels();
}
} else if (Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED.equals(action) ||
Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) {
intent.setClass(mContext, CellBroadcastAlertService.class);
mContext.startService(intent);
} else if (Telephony.Sms.Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION
.equals(action)) {
ArrayList<CdmaSmsCbProgramData> programDataList =
intent.getParcelableArrayListExtra("program_data");
CellBroadcastReceiverMetrics.getInstance().logMessageReported(mContext,
RPT_SPC, SRC_CBR, 0, 0);
if (programDataList != null) {
handleCdmaSmsCbProgramData(programDataList);
} else {
loge("SCPD intent received with no program_data");
}
} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// rename registered notification channels on locale change
CellBroadcastAlertService.createNotificationChannels(mContext);
} else if (TelephonyManager.ACTION_SECRET_CODE.equals(action)) {
if (SystemProperties.getInt("ro.debuggable", 0) == 1
|| res.getBoolean(R.bool.allow_testing_mode_on_user_build)) {
setTestingMode(!isTestingMode(mContext));
int msgId = (isTestingMode(mContext)) ? R.string.testing_mode_enabled
: R.string.testing_mode_disabled;
CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(mContext)
.onChangedTestMode(isTestingMode(mContext));
String msg = res.getString(msgId);
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
LocalBroadcastManager.getInstance(mContext)
.sendBroadcast(new Intent(ACTION_TESTING_MODE_CHANGED));
log(msg);
} else {
if (!res.getBoolean(R.bool.allow_testing_mode_on_user_build)) {
CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(mContext)
.onChangedTestModeOnUserBuild(false);
}
}
} else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
new CellBroadcastContentProvider.AsyncCellBroadcastTask(
mContext.getContentResolver()).execute((CellBroadcastContentProvider
.CellBroadcastOperation) provider -> {
provider.resyncToSmsInbox(mContext);
return true;
});
} else {
Log.w(TAG, "onReceive() unexpected action " + action);
}
}
private void onServiceStateChanged(Context context, Resources res, int ss) {
logd("onServiceStateChanged, ss: " + ss);
// check whether to support roaming network
String roamingOperator = null;
if (ss == ServiceState.STATE_IN_SERVICE || ss == ServiceState.STATE_EMERGENCY_ONLY) {
TelephonyManager tm = context.getSystemService(TelephonyManager.class);
String networkOperator = tm.getNetworkOperator();
logd("networkOperator: " + networkOperator);
// check roaming config only if the network oprator is not empty as the config
// is based on operator numeric
if (!networkOperator.isEmpty()) {
// No roaming supported by default
roamingOperator = "";
if ((tm.isNetworkRoaming() || ss == ServiceState.STATE_EMERGENCY_ONLY)
&& !networkOperator.equals(tm.getSimOperator())) {
String propRoamingPlmn = SystemProperties.get(
ROAMING_PLMN_SUPPORTED_PROPERTY_KEY, "").trim();
String[] roamingNetworks = propRoamingPlmn.isEmpty() ? res.getStringArray(
R.array.cmas_roaming_network_strings) : propRoamingPlmn.split(",");
logd("roamingNetworks: " + Arrays.toString(roamingNetworks));
for (String r : roamingNetworks) {
r = r.trim();
if (r.equals("XXXXXX")) {
//match any roaming network, store mcc+mnc
roamingOperator = networkOperator;
break;
} else if (r.equals("XXX")) {
//match any roaming network, only store mcc
roamingOperator = networkOperator.substring(0, 3);
break;
} else if (networkOperator.startsWith(r)) {
roamingOperator = r;
break;
}
}
}
}
}
if ((ss != ServiceState.STATE_POWER_OFF
&& getServiceState(context) == ServiceState.STATE_POWER_OFF)
|| (roamingOperator != null && !roamingOperator.equals(
getRoamingOperatorSupported(context)))) {
if (!isMockModemRunning()) {
startConfigServiceToEnableChannels();
}
}
setServiceState(ss);
if (roamingOperator != null) {
log("update supported roaming operator as " + roamingOperator);
setRoamingOperatorSupported(roamingOperator);
}
CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(mContext)
.onChangedRoamingSupport(!TextUtils.isEmpty(roamingOperator) ? true : false);
}
/**
* Send an intent to reset the users WEA settings if there is a new carrier on the default subId
*
* The settings will be reset only when a new carrier is detected on the default subId. So it
* tracks the previous carrier id, and ignores the case that the current carrier id is changed
* to invalid. In case of the first boot with a sim on the new device, FDR, or upgrade from Q,
* the carrier id will be stored as there is no previous carrier id, but the settings will not
* be reset.
*
* Do nothing in other cases:
* - SIM insertion for the non-default subId
* - SIM insertion/bootup with no new carrier
* - SIM removal
* - Device just received the update which adds this carrier tracking logic
*
* @param context the context
* @param subId subId of the carrier config event
*/
private void resetSettingsAsNeeded(Context context, int subId) {
final int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
// subId may be -1 if carrier config broadcast is being sent on SIM removal
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
if (defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.d(TAG, "ignoring carrier config broadcast because subId=-1 and it's not"
+ " defaultSubId when device is support multi-sim");
return;
}
if (getPreviousCarrierIdForDefaultSub() == NO_PREVIOUS_CARRIER_ID) {
// on first boot only, if no SIM is inserted we save the carrier ID -1.
// This allows us to detect the carrier change from -1 to the carrier of the first
// SIM when it is inserted.
saveCarrierIdForDefaultSub(TelephonyManager.UNKNOWN_CARRIER_ID);
}
Log.d(TAG, "ignoring carrier config broadcast because subId=-1");
return;
}
Log.d(TAG, "subId=" + subId + " defaultSubId=" + defaultSubId);
if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.d(TAG, "ignoring carrier config broadcast because defaultSubId=-1");
return;
}
if (subId != defaultSubId) {
Log.d(TAG, "ignoring carrier config broadcast for subId=" + subId
+ " because it does not match defaultSubId=" + defaultSubId);
return;
}
TelephonyManager tm = context.getSystemService(TelephonyManager.class);
// carrierId is loaded before carrier config, so this should be safe
int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId();
if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
Log.e(TAG, "ignoring unknown carrier ID");
return;
}
int previousCarrierId = getPreviousCarrierIdForDefaultSub();
if (previousCarrierId == NO_PREVIOUS_CARRIER_ID) {
// on first boot if a SIM is inserted, assume it is not new and don't apply settings
Log.d(TAG, "ignoring carrier config broadcast for subId=" + subId
+ " for first boot");
saveCarrierIdForDefaultSub(carrierId);
return;
}
/** When user_build_mode is true and alow_testing_mode_on_user_build is false
* then testing_mode is not able to be true at all.
*/
Resources res = getResourcesMethod();
if (!res.getBoolean(R.bool.allow_testing_mode_on_user_build)
&& SystemProperties.getInt("ro.debuggable", 0) == 0
&& CellBroadcastReceiver.isTestingMode(context)) {
CellBroadcastReceiverMetrics.getInstance().getFeatureMetrics(context)
.onChangedTestModeOnUserBuild(false);
Log.d(TAG, "it can't be testing_mode at all");
setTestingMode(false);
}
if (carrierId != previousCarrierId) {
saveCarrierIdForDefaultSub(carrierId);
startConfigService(context,
CellBroadcastConfigService.ACTION_UPDATE_SETTINGS_FOR_CARRIER);
} else {
Log.d(TAG, "reset settings as needed for subId=" + subId + ", carrierId=" + carrierId);
Intent intent = new Intent(CellBroadcastConfigService.ACTION_RESET_SETTINGS_AS_NEEDED,
null, context, CellBroadcastConfigService.class);
intent.putExtra(CellBroadcastConfigService.EXTRA_SUB, subId);
context.startService(intent);
}
}
private int getPreviousCarrierIdForDefaultSub() {
return getDefaultSharedPreferences()
.getInt(CARRIER_ID_FOR_DEFAULT_SUB_PREF, NO_PREVIOUS_CARRIER_ID);
}
/**
* store carrierId corresponding to the default subId.
*/
@VisibleForTesting
public void saveCarrierIdForDefaultSub(int carrierId) {
getDefaultSharedPreferences().edit().putInt(CARRIER_ID_FOR_DEFAULT_SUB_PREF, carrierId)
.apply();
}
/**
* Enable/disable cell broadcast receiver testing mode.
*
* @param on {@code true} if testing mode is on, otherwise off.
*/
@VisibleForTesting
public void setTestingMode(boolean on) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
sp.edit().putBoolean(TESTING_MODE, on).commit();
}
/**
* @return {@code true} if operating in testing mode, which enables some features for testing
* purposes.
*/
public static boolean isTestingMode(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getBoolean(TESTING_MODE, false);
}
/**
* Store the current service state for voice registration.
*
* @param ss current voice registration service state.
*/
private void setServiceState(int ss) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
sp.edit().putInt(SERVICE_STATE, ss).commit();
}
/**
* Store the roaming operator
*/
private void setRoamingOperatorSupported(String roamingOperator) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
sp.edit().putString(ROAMING_OPERATOR_SUPPORTED, roamingOperator).commit();
}
/**
* @return the stored voice registration service state
*/
private static int getServiceState(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getInt(SERVICE_STATE, ServiceState.STATE_IN_SERVICE);
}
/**
* @return the supported roaming operator
*/
public static String getRoamingOperatorSupported(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getString(ROAMING_OPERATOR_SUPPORTED, "");
}
/**
* update reminder interval
*/
@VisibleForTesting
public void adjustReminderInterval() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
String currentIntervalDefault = sp.getString(CURRENT_INTERVAL_DEFAULT, "0");
// If interval default changes, reset the interval to the new default value.
String newIntervalDefault = CellBroadcastSettings.getResourcesForDefaultSubId(mContext)
.getString(R.string.alert_reminder_interval_in_min_default);
if (!newIntervalDefault.equals(currentIntervalDefault)) {
Log.d(TAG, "Default interval changed from " + currentIntervalDefault + " to " +
newIntervalDefault);
Editor editor = sp.edit();
// Reset the value to default.
editor.putString(
CellBroadcastSettings.KEY_ALERT_REMINDER_INTERVAL, newIntervalDefault);
// Save the new default value.
editor.putString(CURRENT_INTERVAL_DEFAULT, newIntervalDefault);
editor.commit();
} else {
if (DBG) Log.d(TAG, "Default interval " + currentIntervalDefault + " did not change.");
}
}
/**
* This method's purpose is to enable unit testing
*
* @return sharedePreferences for mContext
*/
@VisibleForTesting
public SharedPreferences getDefaultSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(mContext);
}
/**
* return if there are default values in shared preferences
*
* @return boolean
*/
@VisibleForTesting
public Boolean sharedPrefsHaveDefaultValues() {
return mContext.getSharedPreferences(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES,
Context.MODE_PRIVATE).getBoolean(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES,
false);
}
/**
* initialize shared preferences before starting services
*/
@VisibleForTesting
public void initializeSharedPreference(Context context, int subId) {
if (isSystemUser()) {
Log.d(TAG, "initializeSharedPreference");
resetSettingsAsNeeded(context, subId);
SharedPreferences sp = getDefaultSharedPreferences();
if (!sharedPrefsHaveDefaultValues()) {
// Sets the default values of the shared preference if there isn't any.
PreferenceManager.setDefaultValues(mContext, R.xml.preferences, false);
sp.edit().putBoolean(CellBroadcastSettings.KEY_OVERRIDE_DND_SETTINGS_CHANGED,
false).apply();
// migrate sharedpref from legacy app
migrateSharedPreferenceFromLegacy();
// If the device is in test harness mode, we need to disable emergency alert by
// default.
if (ActivityManager.isRunningInUserTestHarness()) {
Log.d(TAG, "In test harness mode. Turn off emergency alert by default.");
sp.edit().putBoolean(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE,
false).apply();
}
} else {
Log.d(TAG, "Skip setting default values of shared preference.");
}
adjustReminderInterval();
} else {
Log.e(TAG, "initializeSharedPreference: Not system user.");
}
}
/**
* migrate shared preferences from legacy content provider client
*/
@VisibleForTesting
public void migrateSharedPreferenceFromLegacy() {
String[] PREF_KEYS = {
CellBroadcasts.Preference.ENABLE_CMAS_AMBER_PREF,
CellBroadcasts.Preference.ENABLE_AREA_UPDATE_INFO_PREF,
CellBroadcasts.Preference.ENABLE_TEST_ALERT_PREF,
CellBroadcasts.Preference.ENABLE_STATE_LOCAL_TEST_PREF,
CellBroadcasts.Preference.ENABLE_PUBLIC_SAFETY_PREF,
CellBroadcasts.Preference.ENABLE_CMAS_SEVERE_THREAT_PREF,
CellBroadcasts.Preference.ENABLE_CMAS_EXTREME_THREAT_PREF,
CellBroadcasts.Preference.ENABLE_CMAS_PRESIDENTIAL_PREF,
CellBroadcasts.Preference.ENABLE_EMERGENCY_PERF,
CellBroadcasts.Preference.ENABLE_ALERT_VIBRATION_PREF,
CellBroadcasts.Preference.ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF,
ENABLE_ALERT_MASTER_PREF,
ALERT_REMINDER_INTERVAL_PREF
};
try (ContentProviderClient client = mContext.getContentResolver()
.acquireContentProviderClient(Telephony.CellBroadcasts.AUTHORITY_LEGACY)) {
if (client == null) {
Log.d(TAG, "No legacy provider available for sharedpreference migration");
return;
}
SharedPreferences.Editor sp = PreferenceManager
.getDefaultSharedPreferences(mContext).edit();
for (String key : PREF_KEYS) {
try {
Bundle pref = client.call(
CellBroadcasts.AUTHORITY_LEGACY,
CellBroadcasts.CALL_METHOD_GET_PREFERENCE,
key, null);
if (pref != null && pref.containsKey(key)) {
Object val = pref.get(key);
if (val == null) {
// noop - no value to set.
// Only support Boolean and String as preference types for now.
} else if (val instanceof Boolean) {
Log.d(TAG, "migrateSharedPreferenceFromLegacy: " + key + "val: "
+ pref.getBoolean(key));
sp.putBoolean(key, pref.getBoolean(key));
} else if (val instanceof String) {
Log.d(TAG, "migrateSharedPreferenceFromLegacy: " + key + "val: "
+ pref.getString(key));
sp.putString(key, pref.getString(key));
}
} else {
Log.d(TAG, "migrateSharedPreferenceFromLegacy: unsupported key: " + key);
}
} catch (RemoteException e) {
CellBroadcastReceiverMetrics.getInstance().logModuleError(
ERRSRC_CBR, ERRTYPE_PREFMIGRATION);
Log.e(TAG, "fails to get shared preference " + e);
}
}
sp.apply();
} catch (Exception e) {
// We have to guard ourselves against any weird behavior of the
// legacy provider by trying to catch everything
loge("Failed migration from legacy provider: " + e);
}
}
/**
* Handle Service Category Program Data message.
* TODO: Send Service Category Program Results response message to sender
*/
@VisibleForTesting
public void handleCdmaSmsCbProgramData(ArrayList<CdmaSmsCbProgramData> programDataList) {
for (CdmaSmsCbProgramData programData : programDataList) {
switch (programData.getOperation()) {
case CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY:
tryCdmaSetCategory(mContext, programData.getCategory(), true);
break;
case CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY:
tryCdmaSetCategory(mContext, programData.getCategory(), false);
break;
case CdmaSmsCbProgramData.OPERATION_CLEAR_CATEGORIES:
tryCdmaSetCategory(mContext,
CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT, false);
tryCdmaSetCategory(mContext,
CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT, false);
tryCdmaSetCategory(mContext,
CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, false);
tryCdmaSetCategory(mContext,
CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE, false);
break;
default:
loge("Ignoring unknown SCPD operation " + programData.getOperation());
}
}
}
/**
* set CDMA category in shared preferences
* @param context
* @param category CDMA category
* @param enable true for add category, false otherwise
*/
@VisibleForTesting
public void tryCdmaSetCategory(Context context, int category, boolean enable) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
switch (category) {
case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT:
sharedPrefs.edit().putBoolean(
CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, enable)
.apply();
break;
case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT:
sharedPrefs.edit().putBoolean(
CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, enable)
.apply();
break;
case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
sharedPrefs.edit().putBoolean(
CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, enable).apply();
break;
case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE:
sharedPrefs.edit().putBoolean(
CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, enable).apply();
break;
default:
Log.w(TAG, "Ignoring SCPD command to " + (enable ? "enable" : "disable")
+ " alerts in category " + category);
}
}
/**
* This method's purpose if to enable unit testing
*
* @return if the mContext user is a system user
*/
private boolean isSystemUser() {
return isSystemUser(mContext);
}
/**
* This method's purpose if to enable unit testing
*/
@VisibleForTesting
public void startConfigServiceToEnableChannels() {
startConfigService(mContext, CellBroadcastConfigService.ACTION_ENABLE_CHANNELS);
}
/**
* Check if user from context is system user
* @param context
* @return whether the user is system user
*/
private static boolean isSystemUser(Context context) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
return userManager.isSystemUser();
}
/**
* Tell {@link CellBroadcastConfigService} to enable the CB channels.
*
* @param context the broadcast receiver context
*/
static void startConfigService(Context context, String action) {
if (isSystemUser(context)) {
Log.d(TAG, "Start Cell Broadcast configuration for intent=" + action);
context.startService(new Intent(action, null, context,
CellBroadcastConfigService.class));
} else {
Log.e(TAG, "startConfigService: Not system user.");
}
}
/**
* Enable Launcher.
*/
@VisibleForTesting
public void enableLauncher() {
boolean enable = getResourcesMethod().getBoolean(R.bool.show_message_history_in_launcher);
final PackageManager pm = mContext.getPackageManager();
// This alias presents the target activity, CellBroadcastListActivity, as a independent
// entity with its own intent filter for android.intent.category.LAUNCHER.
// This alias will be enabled/disabled at run-time based on resource overlay. Once enabled,
// it will appear in the Launcher as a top-level application
String aliasLauncherActivity = null;
try {
PackageInfo p = pm.getPackageInfo(mContext.getPackageName(),
PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS);
if (p != null) {
for (ActivityInfo activityInfo : p.activities) {
String targetActivity = activityInfo.targetActivity;
if (CellBroadcastListActivity.class.getName().equals(targetActivity)) {
aliasLauncherActivity = activityInfo.name;
break;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, e.toString());
}
if (TextUtils.isEmpty(aliasLauncherActivity)) {
Log.e(TAG, "cannot find launcher activity");
return;
}
if (enable) {
Log.d(TAG, "enable launcher activity: " + aliasLauncherActivity);
pm.setComponentEnabledSetting(
new ComponentName(mContext, aliasLauncherActivity),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
} else {
Log.d(TAG, "disable launcher activity: " + aliasLauncherActivity);
pm.setComponentEnabledSetting(
new ComponentName(mContext, aliasLauncherActivity),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
}
/**
* Reset cached CellBroadcastChannelRanges
*
* This method's purpose is to enable unit testing
*/
@VisibleForTesting
public void resetCellBroadcastChannelRanges() {
CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
}
/**
* Check if mockmodem is running
* @return true if mockmodem service is running instead of real modem
*/
@VisibleForTesting
public boolean isMockModemRunning() {
return isMockModemBinded();
}
/**
* Check if mockmodem is running
* @return true if mockmodem service is running instead of real modem
*/
public static boolean isMockModemBinded() {
String modem = Build.getRadioVersion();
boolean isMockModem = modem != null ? modem.contains(MOCK_MODEM_BASEBAND) : false;
Log.d(TAG, "mockmodem is running? = " + isMockModem);
return isMockModem;
}
private static void log(String msg) {
Log.d(TAG, msg);
}
private static void logd(String msg) {
if (DBG) Log.d(TAG, msg);
}
private static void loge(String msg) {
Log.e(TAG, msg);
}
}