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