| /* |
| * 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); |
| } |
| } |