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