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