• 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"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.cellbroadcastreceiver;
18 
19 import android.annotation.NonNull;
20 import android.app.ActionBar;
21 import android.app.ActivityManager;
22 import android.app.Fragment;
23 import android.app.backup.BackupManager;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.SharedPreferences;
29 import android.content.pm.PackageManager;
30 import android.content.res.Configuration;
31 import android.content.res.Resources;
32 import android.os.Bundle;
33 import android.os.UserManager;
34 import android.os.Vibrator;
35 import android.telephony.SubscriptionManager;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.MenuItem;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.Switch;
42 
43 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
44 import androidx.preference.ListPreference;
45 import androidx.preference.Preference;
46 import androidx.preference.PreferenceCategory;
47 import androidx.preference.PreferenceFragment;
48 import androidx.preference.PreferenceManager;
49 import androidx.preference.PreferenceScreen;
50 import androidx.preference.TwoStatePreference;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.modules.utils.build.SdkLevel;
54 import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
55 import com.android.settingslib.widget.MainSwitchPreference;
56 import com.android.settingslib.widget.OnMainSwitchChangeListener;
57 
58 import java.util.HashMap;
59 import java.util.Map;
60 
61 /**
62  * Settings activity for the cell broadcast receiver.
63  */
64 public class CellBroadcastSettings extends CollapsingToolbarBaseActivity {
65 
66     private static final String TAG = "CellBroadcastSettings";
67 
68     private static final boolean DBG = false;
69 
70     /**
71      * Keys for user preferences.
72      * When adding a new preference, make sure to clear its value in resetAllPreferences.
73      */
74     // Preference key for alert header (A text view, not clickable).
75     public static final String KEY_ALERTS_HEADER = "alerts_header";
76 
77     // Preference key for a main toggle to enable/disable all alerts message (default enabled).
78     public static final String KEY_ENABLE_ALERTS_MASTER_TOGGLE = "enable_alerts_master_toggle";
79 
80     // Preference key for whether to enable public safety messages (default enabled).
81     public static final String KEY_ENABLE_PUBLIC_SAFETY_MESSAGES = "enable_public_safety_messages";
82 
83     // Preference key for whether to show full-screen public safety message (pop-up dialog), If set
84     // to false, only display from message history and sms inbox if enabled. A foreground
85     // notification might also be shown if enabled.
86     public static final String KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN =
87             "enable_public_safety_messages_full_screen";
88 
89     // Preference key for whether to enable emergency alerts (default enabled).
90     public static final String KEY_ENABLE_EMERGENCY_ALERTS = "enable_emergency_alerts";
91 
92     // Enable vibration on alert (unless main volume is silent).
93     public static final String KEY_ENABLE_ALERT_VIBRATE = "enable_alert_vibrate";
94 
95     // Speak contents of alert after playing the alert sound.
96     public static final String KEY_ENABLE_ALERT_SPEECH = "enable_alert_speech";
97 
98     // Play alert sound in full volume regardless Do Not Disturb is on.
99     public static final String KEY_OVERRIDE_DND = "override_dnd";
100 
101     public static final String KEY_OVERRIDE_DND_SETTINGS_CHANGED =
102             "override_dnd_settings_changed";
103 
104     // Preference category for emergency alert and CMAS settings.
105     public static final String KEY_CATEGORY_EMERGENCY_ALERTS = "category_emergency_alerts";
106 
107     // Preference category for alert preferences.
108     public static final String KEY_CATEGORY_ALERT_PREFERENCES = "category_alert_preferences";
109 
110     // Show checkbox for Presidential alerts in settings
111     // Whether to display CMAS presidential alert notifications (always enabled).
112     public static final String KEY_ENABLE_CMAS_PRESIDENTIAL_ALERTS =
113             "enable_cmas_presidential_alerts";
114 
115     // Whether to display CMAS extreme threat notifications (default is enabled).
116     public static final String KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS =
117             "enable_cmas_extreme_threat_alerts";
118 
119     // Whether to display CMAS severe threat notifications (default is enabled).
120     public static final String KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS =
121             "enable_cmas_severe_threat_alerts";
122 
123     // Whether to display CMAS amber alert messages (default is enabled).
124     public static final String KEY_ENABLE_CMAS_AMBER_ALERTS = "enable_cmas_amber_alerts";
125 
126     // Whether to display monthly test messages (default is disabled).
127     public static final String KEY_ENABLE_TEST_ALERTS = "enable_test_alerts";
128 
129     // Whether to display exercise test alerts.
130     public static final String KEY_ENABLE_EXERCISE_ALERTS = "enable_exercise_alerts";
131 
132     // Whether to display operator defined test alerts
133     public static final String KEY_OPERATOR_DEFINED_ALERTS = "enable_operator_defined_alerts";
134 
135     // Whether to display state/local test messages (default disabled).
136     public static final String KEY_ENABLE_STATE_LOCAL_TEST_ALERTS =
137             "enable_state_local_test_alerts";
138 
139     // Preference key for whether to enable area update information notifications
140     // Enabled by default for phones sold in Brazil and India, otherwise this setting may be hidden.
141     public static final String KEY_ENABLE_AREA_UPDATE_INFO_ALERTS =
142             "enable_area_update_info_alerts";
143 
144     // Preference key for initial opt-in/opt-out dialog.
145     public static final String KEY_SHOW_CMAS_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
146 
147     // Alert reminder interval ("once" = single 2 minute reminder).
148     public static final String KEY_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
149 
150     // Preference key for emergency alerts history
151     public static final String KEY_EMERGENCY_ALERT_HISTORY = "emergency_alert_history";
152 
153     // For top introduction info
154     private static final String KEY_PREFS_TOP_INTRO = "alert_prefs_top_intro";
155 
156     // Whether to receive alert in second language code
157     public static final String KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE =
158             "receive_cmas_in_second_language";
159 
160     /* End of user preferences keys section. */
161 
162     // Key for shared preference which represents whether user has changed any preference
163     @VisibleForTesting
164     public static final String ANY_PREFERENCE_CHANGED_BY_USER = "any_preference_changed_by_user";
165 
166     // Resource cache per operator
167     private static final Map<String, Resources> sResourcesCacheByOperator = new HashMap<>();
168     private static final Object sCacheLock = new Object();
169 
170     // Intent sent from cellbroadcastreceiver to notify cellbroadcastservice that area info update
171     // is disabled/enabled.
172     private static final String AREA_INFO_UPDATE_ACTION =
173             "com.android.cellbroadcastreceiver.action.AREA_UPDATE_INFO_ENABLED";
174     private static final String AREA_INFO_UPDATE_ENABLED_EXTRA = "enable";
175 
176     /**
177      * This permission is only granted to the cellbroadcast mainline module and thus can be
178      * used for permission check within CBR and CBS.
179      */
180     private static final String CBR_MODULE_PERMISSION =
181             "com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY";
182 
183     @Override
onCreate(Bundle savedInstanceState)184     public void onCreate(Bundle savedInstanceState) {
185         boolean isWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
186         // for backward compatibility on R devices or wearable devices due to small screen device.
187         boolean hideToolbar = !SdkLevel.isAtLeastS() || isWatch;
188         if (hideToolbar) {
189             setCustomizeContentView(R.layout.cell_broadcast_list_collapsing_no_toobar);
190         }
191 
192         super.onCreate(savedInstanceState);
193 
194         if (hideToolbar) {
195             ActionBar actionBar = getActionBar();
196             if (actionBar != null) {
197                 // android.R.id.home will be triggered in onOptionsItemSelected()
198                 actionBar.setDisplayHomeAsUpEnabled(true);
199             }
200         }
201 
202         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
203         if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS)) {
204             setContentView(R.layout.cell_broadcast_disallowed_preference_screen);
205             return;
206         }
207 
208         // We only add new CellBroadcastSettingsFragment if no fragment is restored.
209         Fragment fragment = getFragmentManager().findFragmentById(
210                 com.android.settingslib.widget.R.id.content_frame);
211         if (fragment == null) {
212             fragment = new CellBroadcastSettingsFragment();
213             getFragmentManager()
214                     .beginTransaction()
215                     .add(com.android.settingslib.widget.R.id.content_frame, fragment)
216                     .commit();
217         }
218     }
219 
220     @Override
onStart()221     public void onStart() {
222         super.onStart();
223         getWindow().addSystemFlags(
224                 android.view.WindowManager.LayoutParams
225                         .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
226     }
227 
228     @Override
onOptionsItemSelected(MenuItem item)229     public boolean onOptionsItemSelected(MenuItem item) {
230         switch (item.getItemId()) {
231             // Respond to the action bar's Up/Home button
232             case android.R.id.home:
233                 finish();
234                 return true;
235         }
236         return super.onOptionsItemSelected(item);
237     }
238 
239     /**
240      * Reset all user values for preferences (stored in shared preferences).
241      *
242      * @param c the application context
243      */
resetAllPreferences(Context c)244     public static void resetAllPreferences(Context c) {
245         SharedPreferences.Editor e = PreferenceManager.getDefaultSharedPreferences(c).edit();
246         e.remove(KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS)
247                 .remove(KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS)
248                 .remove(KEY_ENABLE_CMAS_AMBER_ALERTS)
249                 .remove(KEY_ENABLE_PUBLIC_SAFETY_MESSAGES)
250                 .remove(KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN)
251                 .remove(KEY_ENABLE_EMERGENCY_ALERTS)
252                 .remove(KEY_ALERT_REMINDER_INTERVAL)
253                 .remove(KEY_ENABLE_ALERT_SPEECH)
254                 .remove(KEY_OVERRIDE_DND)
255                 .remove(KEY_ENABLE_AREA_UPDATE_INFO_ALERTS)
256                 .remove(KEY_ENABLE_TEST_ALERTS)
257                 .remove(KEY_ENABLE_STATE_LOCAL_TEST_ALERTS)
258                 .remove(KEY_ENABLE_ALERT_VIBRATE)
259                 .remove(KEY_ENABLE_CMAS_PRESIDENTIAL_ALERTS)
260                 .remove(KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE)
261                 .remove(KEY_ENABLE_EXERCISE_ALERTS)
262                 .remove(KEY_OPERATOR_DEFINED_ALERTS);
263         // If the device is in test harness mode, reset main toggle should only happen on the
264         // first boot.
265         if (!ActivityManager.isRunningInUserTestHarness()) {
266           Log.d(TAG, "In not test harness mode. reset main toggle.");
267           e.remove(KEY_ENABLE_ALERTS_MASTER_TOGGLE);
268         }
269         e.commit();
270 
271         PackageManager pm = c.getPackageManager();
272         if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
273             PreferenceManager.setDefaultValues(c, R.xml.watch_preferences, true);
274         } else {
275             PreferenceManager.setDefaultValues(c, R.xml.preferences, true);
276         }
277         setPreferenceChanged(c, false);
278     }
279 
280     /**
281      * Return true if user has modified any preference manually.
282      * @param c the application context
283      * @return
284      */
hasAnyPreferenceChanged(Context c)285     public static boolean hasAnyPreferenceChanged(Context c) {
286         return PreferenceManager.getDefaultSharedPreferences(c)
287                 .getBoolean(ANY_PREFERENCE_CHANGED_BY_USER, false);
288     }
289 
setPreferenceChanged(Context c, boolean changed)290     private static void setPreferenceChanged(Context c, boolean changed) {
291         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(c);
292         sp.edit().putBoolean(ANY_PREFERENCE_CHANGED_BY_USER, changed).apply();
293     }
294 
295     /**
296      * New fragment-style implementation of preferences.
297      */
298     public static class CellBroadcastSettingsFragment extends PreferenceFragment {
299 
300         private TwoStatePreference mExtremeCheckBox;
301         private TwoStatePreference mSevereCheckBox;
302         private TwoStatePreference mAmberCheckBox;
303         private TwoStatePreference mMasterToggle;
304         private TwoStatePreference mPublicSafetyMessagesChannelCheckBox;
305         private TwoStatePreference mPublicSafetyMessagesChannelFullScreenCheckBox;
306         private TwoStatePreference mEmergencyAlertsCheckBox;
307         private ListPreference mReminderInterval;
308         private TwoStatePreference mSpeechCheckBox;
309         private TwoStatePreference mOverrideDndCheckBox;
310         private TwoStatePreference mAreaUpdateInfoCheckBox;
311         private TwoStatePreference mTestCheckBox;
312         private TwoStatePreference mExerciseTestCheckBox;
313         private TwoStatePreference mOperatorDefinedCheckBox;
314         private TwoStatePreference mStateLocalTestCheckBox;
315         private TwoStatePreference mEnableVibrateCheckBox;
316         private Preference mAlertHistory;
317         private Preference mAlertsHeader;
318         private PreferenceCategory mAlertCategory;
319         private PreferenceCategory mAlertPreferencesCategory;
320         private boolean mDisableSevereWhenExtremeDisabled = true;
321 
322         // Show checkbox for Presidential alerts in settings
323         private TwoStatePreference mPresidentialCheckBox;
324 
325         // on/off switch in settings for receiving alert in second language code
326         private TwoStatePreference mReceiveCmasInSecondLanguageCheckBox;
327 
328         // Show the top introduction
329         private Preference mTopIntroPreference;
330 
331         private final BroadcastReceiver mTestingModeChangedReceiver = new BroadcastReceiver() {
332             @Override
333             public void onReceive(Context context, Intent intent) {
334                 switch (intent.getAction()) {
335                     case CellBroadcastReceiver.ACTION_TESTING_MODE_CHANGED:
336                         updatePreferenceVisibility();
337                         break;
338                 }
339             }
340         };
341 
initPreferences()342         private void initPreferences() {
343             mExtremeCheckBox = (TwoStatePreference)
344                     findPreference(KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS);
345             mSevereCheckBox = (TwoStatePreference)
346                     findPreference(KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS);
347             mAmberCheckBox = (TwoStatePreference)
348                     findPreference(KEY_ENABLE_CMAS_AMBER_ALERTS);
349             mMasterToggle = (TwoStatePreference)
350                     findPreference(KEY_ENABLE_ALERTS_MASTER_TOGGLE);
351             mPublicSafetyMessagesChannelCheckBox = (TwoStatePreference)
352                     findPreference(KEY_ENABLE_PUBLIC_SAFETY_MESSAGES);
353             mPublicSafetyMessagesChannelFullScreenCheckBox = (TwoStatePreference)
354                     findPreference(KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN);
355             mEmergencyAlertsCheckBox = (TwoStatePreference)
356                     findPreference(KEY_ENABLE_EMERGENCY_ALERTS);
357             mReminderInterval = (ListPreference)
358                     findPreference(KEY_ALERT_REMINDER_INTERVAL);
359             mSpeechCheckBox = (TwoStatePreference)
360                     findPreference(KEY_ENABLE_ALERT_SPEECH);
361             mOverrideDndCheckBox = (TwoStatePreference)
362                     findPreference(KEY_OVERRIDE_DND);
363             mAreaUpdateInfoCheckBox = (TwoStatePreference)
364                     findPreference(KEY_ENABLE_AREA_UPDATE_INFO_ALERTS);
365             mTestCheckBox = (TwoStatePreference)
366                     findPreference(KEY_ENABLE_TEST_ALERTS);
367             mExerciseTestCheckBox = (TwoStatePreference) findPreference(KEY_ENABLE_EXERCISE_ALERTS);
368             mOperatorDefinedCheckBox = (TwoStatePreference)
369                     findPreference(KEY_OPERATOR_DEFINED_ALERTS);
370             mStateLocalTestCheckBox = (TwoStatePreference)
371                     findPreference(KEY_ENABLE_STATE_LOCAL_TEST_ALERTS);
372             mAlertHistory = findPreference(KEY_EMERGENCY_ALERT_HISTORY);
373             mAlertsHeader = findPreference(KEY_ALERTS_HEADER);
374             mReceiveCmasInSecondLanguageCheckBox = (TwoStatePreference) findPreference
375                     (KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE);
376             mEnableVibrateCheckBox = findPreference(KEY_ENABLE_ALERT_VIBRATE);
377 
378             // Show checkbox for Presidential alerts in settings
379             mPresidentialCheckBox = (TwoStatePreference)
380                     findPreference(KEY_ENABLE_CMAS_PRESIDENTIAL_ALERTS);
381 
382             PackageManager pm = getActivity().getPackageManager();
383             if (!pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
384                 mAlertPreferencesCategory = (PreferenceCategory)
385                         findPreference(KEY_CATEGORY_ALERT_PREFERENCES);
386                 mAlertCategory = (PreferenceCategory)
387                         findPreference(KEY_CATEGORY_EMERGENCY_ALERTS);
388             }
389             mTopIntroPreference = findPreference(KEY_PREFS_TOP_INTRO);
390         }
391 
392         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)393         public View onCreateView(LayoutInflater inflater, ViewGroup container,
394                 Bundle savedInstanceState) {
395             View root = super.onCreateView(inflater, container, savedInstanceState);
396             PackageManager pm = getActivity().getPackageManager();
397             if (pm != null
398                     && pm.hasSystemFeature(
399                     PackageManager.FEATURE_WATCH)) {
400                 ViewGroup.LayoutParams layoutParams = getListView().getLayoutParams();
401                 if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
402                     int watchMarginInPixel = (int) getResources().getDimension(
403                             R.dimen.pref_top_margin);
404                     ((ViewGroup.MarginLayoutParams) layoutParams).topMargin = watchMarginInPixel;
405                     ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = watchMarginInPixel;
406                     getListView().setLayoutParams(layoutParams);
407                 }
408             }
409             return root;
410         }
411 
412         @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)413         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
414 
415             LocalBroadcastManager.getInstance(getContext())
416                     .registerReceiver(mTestingModeChangedReceiver, new IntentFilter(
417                             CellBroadcastReceiver.ACTION_TESTING_MODE_CHANGED));
418 
419             // Load the preferences from an XML resource
420             PackageManager pm = getActivity().getPackageManager();
421             if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
422                 addPreferencesFromResource(R.xml.watch_preferences);
423             } else {
424                 addPreferencesFromResource(R.xml.preferences);
425             }
426 
427             initPreferences();
428 
429             Resources res = CellBroadcastSettings.getResourcesForDefaultSubId(getContext());
430 
431             mDisableSevereWhenExtremeDisabled = res.getBoolean(
432                     R.bool.disable_severe_when_extreme_disabled);
433 
434             // Handler for settings that require us to reconfigure enabled channels in radio
435             Preference.OnPreferenceChangeListener startConfigServiceListener =
436                     new Preference.OnPreferenceChangeListener() {
437                         @Override
438                         public boolean onPreferenceChange(Preference pref, Object newValue) {
439                             if (mDisableSevereWhenExtremeDisabled) {
440                                 if (pref.getKey().equals(KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS)) {
441                                     boolean isExtremeAlertChecked = (Boolean) newValue;
442                                     if (mSevereCheckBox != null) {
443                                         mSevereCheckBox.setEnabled(isExtremeAlertChecked);
444                                         mSevereCheckBox.setChecked(false);
445                                     }
446                                 }
447                             }
448 
449                             // check if area update was disabled
450                             if (pref.getKey().equals(KEY_ENABLE_AREA_UPDATE_INFO_ALERTS)) {
451                                 boolean isEnabledAlert = (Boolean) newValue;
452                                 notifyAreaInfoUpdate(isEnabledAlert);
453                             }
454 
455                             onPreferenceChangedByUser(getContext());
456                             return true;
457                         }
458                     };
459 
460             initReminderIntervalList();
461 
462             if (mMasterToggle != null) {
463                 if (mMasterToggle instanceof MainSwitchPreference) {
464                     MainSwitchPreference mainSwitchPreference =
465                             (MainSwitchPreference) mMasterToggle;
466                     final OnMainSwitchChangeListener mainSwitchListener =
467                             new OnMainSwitchChangeListener() {
468                         @Override
469                         public void onSwitchChanged(Switch switchView, boolean isChecked) {
470                             setAlertsEnabled(isChecked);
471                             onPreferenceChangedByUser(getContext());
472                         }
473                     };
474                     mainSwitchPreference.addOnSwitchChangeListener(mainSwitchListener);
475                 } else {
476                     Preference.OnPreferenceChangeListener mainSwitchListener =
477                             new Preference.OnPreferenceChangeListener() {
478                                 @Override
479                                 public boolean onPreferenceChange(
480                                         Preference pref, Object newValue) {
481                                     setAlertsEnabled((Boolean) newValue);
482                                     onPreferenceChangedByUser(getContext());
483                                     return true;
484                                 }
485                             };
486                     mMasterToggle.setOnPreferenceChangeListener(mainSwitchListener);
487                 }
488                 // If allow alerts are disabled, we turn all sub-alerts off. If it's enabled, we
489                 // leave them as they are.
490                 if (!mMasterToggle.isChecked()) {
491                     setAlertsEnabled(false);
492                 }
493             }
494             // note that mPresidentialCheckBox does not use the startConfigServiceListener because
495             // the user is never allowed to change the preference
496             if (mAreaUpdateInfoCheckBox != null) {
497                 mAreaUpdateInfoCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
498             }
499             if (mExtremeCheckBox != null) {
500                 mExtremeCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
501             }
502             if (mPublicSafetyMessagesChannelCheckBox != null) {
503                 mPublicSafetyMessagesChannelCheckBox.setOnPreferenceChangeListener(
504                         startConfigServiceListener);
505             }
506             if (mPublicSafetyMessagesChannelFullScreenCheckBox != null) {
507                 mPublicSafetyMessagesChannelFullScreenCheckBox.setOnPreferenceChangeListener(
508                         startConfigServiceListener);
509             }
510             if (mEmergencyAlertsCheckBox != null) {
511                 mEmergencyAlertsCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
512             }
513             if (mSevereCheckBox != null) {
514                 mSevereCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
515                 if (mDisableSevereWhenExtremeDisabled) {
516                     if (mExtremeCheckBox != null) {
517                         mSevereCheckBox.setEnabled(mExtremeCheckBox.isChecked());
518                     }
519                 }
520             }
521             if (mAmberCheckBox != null) {
522                 mAmberCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
523             }
524             if (mTestCheckBox != null) {
525                 mTestCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
526             }
527             if (mExerciseTestCheckBox != null) {
528                 mExerciseTestCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
529             }
530             if (mOperatorDefinedCheckBox != null) {
531                 mOperatorDefinedCheckBox.setOnPreferenceChangeListener(startConfigServiceListener);
532             }
533             if (mStateLocalTestCheckBox != null) {
534                 mStateLocalTestCheckBox.setOnPreferenceChangeListener(
535                         startConfigServiceListener);
536             }
537 
538             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
539 
540             if (mOverrideDndCheckBox != null) {
541                 if (!sp.getBoolean(KEY_OVERRIDE_DND_SETTINGS_CHANGED, false)) {
542                     // If the user hasn't changed this settings yet, use the default settings
543                     // from resource overlay.
544                     mOverrideDndCheckBox.setChecked(res.getBoolean(R.bool.override_dnd_default));
545                 }
546                 mOverrideDndCheckBox.setOnPreferenceChangeListener(
547                         (pref, newValue) -> {
548                             sp.edit().putBoolean(KEY_OVERRIDE_DND_SETTINGS_CHANGED,
549                                     true).apply();
550                             updateVibrationPreference((boolean) newValue);
551                             return true;
552                         });
553             }
554 
555             if (mAlertHistory != null) {
556                 mAlertHistory.setOnPreferenceClickListener(
557                         preference -> {
558                             final Intent intent = new Intent(getContext(),
559                                     CellBroadcastListActivity.class);
560                             startActivity(intent);
561                             return true;
562                         });
563             }
564 
565             updateVibrationPreference(sp.getBoolean(CellBroadcastSettings.KEY_OVERRIDE_DND,
566                     false));
567             updatePreferenceVisibility();
568         }
569 
570         /**
571          * Update the vibration preference based on override DND. If DND is overridden, then do
572          * not allow users to turn off vibration.
573          *
574          * @param overrideDnd {@code true} if the alert will be played at full volume, regardless
575          * DND settings.
576          */
updateVibrationPreference(boolean overrideDnd)577         private void updateVibrationPreference(boolean overrideDnd) {
578             if (mEnableVibrateCheckBox != null) {
579                 if (overrideDnd) {
580                     // If DND is enabled, always enable vibration.
581                     mEnableVibrateCheckBox.setChecked(true);
582                 }
583                 // Grey out the preference if DND is overridden.
584                 mEnableVibrateCheckBox.setEnabled(!overrideDnd);
585             }
586         }
587 
588         /**
589          * Dynamically update each preference's visibility based on configuration.
590          */
updatePreferenceVisibility()591         private void updatePreferenceVisibility() {
592             Resources res = CellBroadcastSettings.getResourcesForDefaultSubId(getContext());
593 
594             // The settings should be based on the config by the subscription
595             CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
596                     getContext(), SubscriptionManager.getDefaultSubscriptionId(), null);
597 
598             PreferenceScreen preferenceScreen = getPreferenceScreen();
599             boolean isWatch = getActivity().getPackageManager().hasSystemFeature(
600                     PackageManager.FEATURE_WATCH);
601 
602             if (mMasterToggle != null) {
603                 mMasterToggle.setVisible(res.getBoolean(R.bool.show_main_switch_settings));
604             }
605 
606             if (mPresidentialCheckBox != null) {
607                 mPresidentialCheckBox.setVisible(
608                         res.getBoolean(R.bool.show_presidential_alerts_settings));
609                 if (isWatch && !mPresidentialCheckBox.isVisible()) {
610                     preferenceScreen.removePreference(mPresidentialCheckBox);
611                 }
612             }
613 
614             if (mExtremeCheckBox != null) {
615                 mExtremeCheckBox.setVisible(res.getBoolean(R.bool.show_extreme_alert_settings)
616                         && !channelManager.getCellBroadcastChannelRanges(
617                                 R.array.cmas_alert_extreme_channels_range_strings).isEmpty());
618                 if (isWatch && !mExtremeCheckBox.isVisible()) {
619                     preferenceScreen.removePreference(mExtremeCheckBox);
620                 }
621             }
622 
623             if (mSevereCheckBox != null) {
624                 mSevereCheckBox.setVisible(res.getBoolean(R.bool.show_severe_alert_settings)
625                         && !channelManager.getCellBroadcastChannelRanges(
626                                 R.array.cmas_alerts_severe_range_strings).isEmpty());
627                 if (isWatch && !mSevereCheckBox.isVisible()) {
628                     preferenceScreen.removePreference(mSevereCheckBox);
629                 }
630             }
631 
632             if (mAmberCheckBox != null) {
633                 mAmberCheckBox.setVisible(res.getBoolean(R.bool.show_amber_alert_settings)
634                         && !channelManager.getCellBroadcastChannelRanges(
635                                 R.array.cmas_amber_alerts_channels_range_strings).isEmpty());
636                 if (isWatch && !mAmberCheckBox.isVisible()) {
637                     preferenceScreen.removePreference(mAmberCheckBox);
638                 }
639             }
640 
641             if (mPublicSafetyMessagesChannelCheckBox != null) {
642                 mPublicSafetyMessagesChannelCheckBox.setVisible(
643                         res.getBoolean(R.bool.show_public_safety_settings)
644                                 && !channelManager.getCellBroadcastChannelRanges(
645                                         R.array.public_safety_messages_channels_range_strings)
646                                 .isEmpty());
647                 if (isWatch && !mPublicSafetyMessagesChannelCheckBox.isVisible()) {
648                     preferenceScreen.removePreference(mPublicSafetyMessagesChannelCheckBox);
649                 }
650             }
651             // this is the matching full screen settings for public safety toggle. shown only if
652             // public safety toggle is displayed.
653             if (mPublicSafetyMessagesChannelFullScreenCheckBox != null) {
654                 mPublicSafetyMessagesChannelFullScreenCheckBox.setVisible(
655                         res.getBoolean(R.bool.show_public_safety_full_screen_settings)
656                                 && (mPublicSafetyMessagesChannelCheckBox != null
657                                 && mPublicSafetyMessagesChannelCheckBox.isVisible()));
658             }
659 
660             if (mTestCheckBox != null) {
661                 mTestCheckBox.setVisible(isTestAlertsToggleVisible(getContext()));
662             }
663 
664             if (mExerciseTestCheckBox != null) {
665                 boolean visible = false;
666                 if (res.getBoolean(R.bool.show_separate_exercise_settings)) {
667                     if (res.getBoolean(R.bool.show_exercise_settings)
668                             || CellBroadcastReceiver.isTestingMode(getContext())) {
669                         if (!channelManager.getCellBroadcastChannelRanges(
670                                 R.array.exercise_alert_range_strings).isEmpty()) {
671                             visible = true;
672                         }
673                     }
674                 }
675                 mExerciseTestCheckBox.setVisible(visible);
676             }
677 
678             if (mOperatorDefinedCheckBox != null) {
679                 boolean visible = false;
680                 if (res.getBoolean(R.bool.show_separate_operator_defined_settings)) {
681                     if (res.getBoolean(R.bool.show_operator_defined_settings)
682                             || CellBroadcastReceiver.isTestingMode(getContext())) {
683                         if (!channelManager.getCellBroadcastChannelRanges(
684                                 R.array.operator_defined_alert_range_strings).isEmpty()) {
685                             visible = true;
686                         }
687                     }
688                 }
689                 mOperatorDefinedCheckBox.setVisible(visible);
690             }
691 
692             if (mEmergencyAlertsCheckBox != null) {
693                 mEmergencyAlertsCheckBox.setVisible(!channelManager.getCellBroadcastChannelRanges(
694                         R.array.emergency_alerts_channels_range_strings).isEmpty());
695                 if (isWatch && !mEmergencyAlertsCheckBox.isVisible()) {
696                     preferenceScreen.removePreference(mEmergencyAlertsCheckBox);
697                 }
698             }
699 
700             if (mStateLocalTestCheckBox != null) {
701                 mStateLocalTestCheckBox.setVisible(
702                         res.getBoolean(R.bool.show_state_local_test_settings)
703                                 && !channelManager.getCellBroadcastChannelRanges(
704                                         R.array.state_local_test_alert_range_strings).isEmpty());
705                 if (isWatch && !mStateLocalTestCheckBox.isVisible()) {
706                     preferenceScreen.removePreference(mStateLocalTestCheckBox);
707                 }
708             }
709 
710             if (mReceiveCmasInSecondLanguageCheckBox != null) {
711                 mReceiveCmasInSecondLanguageCheckBox.setVisible(!res.getString(
712                         R.string.emergency_alert_second_language_code).isEmpty());
713                 if (isWatch && !mReceiveCmasInSecondLanguageCheckBox.isVisible()) {
714                     preferenceScreen.removePreference(mReceiveCmasInSecondLanguageCheckBox);
715                 }
716             }
717 
718             if (mAreaUpdateInfoCheckBox != null) {
719                 mAreaUpdateInfoCheckBox.setVisible(
720                         res.getBoolean(R.bool.config_showAreaUpdateInfoSettings));
721                 if (isWatch && !mAreaUpdateInfoCheckBox.isVisible()) {
722                     preferenceScreen.removePreference(mAreaUpdateInfoCheckBox);
723                 }
724             }
725 
726             if (mOverrideDndCheckBox != null) {
727                 mOverrideDndCheckBox.setVisible(res.getBoolean(R.bool.show_override_dnd_settings));
728                 if (isWatch && !mOverrideDndCheckBox.isVisible()) {
729                     preferenceScreen.removePreference(mOverrideDndCheckBox);
730                 }
731             }
732 
733             if (mEnableVibrateCheckBox != null) {
734                 // Only show vibrate toggle when override DND toggle is available to users, or when
735                 // override DND default is turned off.
736                 // In some countries, override DND is always on, which means vibration is always on.
737                 // In that case, no need to show vibration toggle for users.
738                 mEnableVibrateCheckBox.setVisible(isVibrationToggleVisible(getContext(), res));
739                 if (isWatch && !mEnableVibrateCheckBox.isVisible()) {
740                     preferenceScreen.removePreference(mEnableVibrateCheckBox);
741                 }
742             }
743             if (mAlertsHeader != null) {
744                 mAlertsHeader.setVisible(
745                         !getContext().getString(R.string.alerts_header_summary).isEmpty());
746                 if (isWatch && !mAlertsHeader.isVisible()) {
747                     preferenceScreen.removePreference(mAlertsHeader);
748                 }
749             }
750 
751             if (mSpeechCheckBox != null) {
752                 mSpeechCheckBox.setVisible(res.getBoolean(R.bool.show_alert_speech_setting)
753                         || getActivity().getPackageManager()
754                         .hasSystemFeature(PackageManager.FEATURE_WATCH));
755             }
756 
757             if (mTopIntroPreference != null) {
758                 mTopIntroPreference.setTitle(getTopIntroduction());
759             }
760         }
761 
getTopIntroduction()762         private int getTopIntroduction() {
763             // Only set specific top introduction for roaming support now
764             if (!CellBroadcastReceiver.getRoamingOperatorSupported(getContext()).isEmpty()) {
765                 return R.string.top_intro_roaming_text;
766             }
767             return R.string.top_intro_default_text;
768         }
769 
initReminderIntervalList()770         private void initReminderIntervalList() {
771             Resources res = CellBroadcastSettings.getResourcesForDefaultSubId(getContext());
772 
773             String[] activeValues =
774                     res.getStringArray(R.array.alert_reminder_interval_active_values);
775             String[] allEntries = res.getStringArray(R.array.alert_reminder_interval_entries);
776             String[] newEntries = new String[activeValues.length];
777 
778             // Only add active interval to the list
779             for (int i = 0; i < activeValues.length; i++) {
780                 int index = mReminderInterval.findIndexOfValue(activeValues[i]);
781                 if (index != -1) {
782                     newEntries[i] = allEntries[index];
783                     if (DBG) Log.d(TAG, "Added " + allEntries[index]);
784                 } else {
785                     Log.e(TAG, "Can't find " + activeValues[i]);
786                 }
787             }
788 
789             mReminderInterval.setEntries(newEntries);
790             mReminderInterval.setEntryValues(activeValues);
791             mReminderInterval.setSummary(mReminderInterval.getEntry());
792             mReminderInterval.setOnPreferenceChangeListener(
793                     new Preference.OnPreferenceChangeListener() {
794                         @Override
795                         public boolean onPreferenceChange(Preference pref, Object newValue) {
796                             final ListPreference listPref = (ListPreference) pref;
797                             final int idx = listPref.findIndexOfValue((String) newValue);
798                             listPref.setSummary(listPref.getEntries()[idx]);
799                             return true;
800                         }
801                     });
802         }
803 
804 
setAlertsEnabled(boolean alertsEnabled)805         private void setAlertsEnabled(boolean alertsEnabled) {
806             if (mSevereCheckBox != null) {
807                 mSevereCheckBox.setEnabled(alertsEnabled);
808                 mSevereCheckBox.setChecked(alertsEnabled);
809             }
810             if (mExtremeCheckBox != null) {
811                 mExtremeCheckBox.setEnabled(alertsEnabled);
812                 mExtremeCheckBox.setChecked(alertsEnabled);
813             }
814             if (mAmberCheckBox != null) {
815                 mAmberCheckBox.setEnabled(alertsEnabled);
816                 mAmberCheckBox.setChecked(alertsEnabled);
817             }
818             if (mAreaUpdateInfoCheckBox != null) {
819                 mAreaUpdateInfoCheckBox.setEnabled(alertsEnabled);
820                 mAreaUpdateInfoCheckBox.setChecked(alertsEnabled);
821                 notifyAreaInfoUpdate(alertsEnabled);
822             }
823             if (mEmergencyAlertsCheckBox != null) {
824                 mEmergencyAlertsCheckBox.setEnabled(alertsEnabled);
825                 mEmergencyAlertsCheckBox.setChecked(alertsEnabled);
826             }
827             if (mPublicSafetyMessagesChannelCheckBox != null) {
828                 mPublicSafetyMessagesChannelCheckBox.setEnabled(alertsEnabled);
829                 mPublicSafetyMessagesChannelCheckBox.setChecked(alertsEnabled);
830             }
831             if (mStateLocalTestCheckBox != null) {
832                 mStateLocalTestCheckBox.setEnabled(alertsEnabled);
833                 mStateLocalTestCheckBox.setChecked(alertsEnabled);
834             }
835             if (mTestCheckBox != null) {
836                 mTestCheckBox.setEnabled(alertsEnabled);
837                 mTestCheckBox.setChecked(alertsEnabled);
838             }
839             if (mExerciseTestCheckBox != null) {
840                 mExerciseTestCheckBox.setEnabled(alertsEnabled);
841                 mExerciseTestCheckBox.setChecked(alertsEnabled);
842             }
843             if (mOperatorDefinedCheckBox != null) {
844                 mOperatorDefinedCheckBox.setEnabled(alertsEnabled);
845                 mOperatorDefinedCheckBox.setChecked(alertsEnabled);
846             }
847         }
848 
notifyAreaInfoUpdate(boolean enabled)849         private void notifyAreaInfoUpdate(boolean enabled) {
850             Intent areaInfoIntent = new Intent(AREA_INFO_UPDATE_ACTION);
851             areaInfoIntent.putExtra(AREA_INFO_UPDATE_ENABLED_EXTRA, enabled);
852             // sending broadcast protected by the permission which is only
853             // granted for CBR mainline module.
854             getContext().sendBroadcast(areaInfoIntent, CBR_MODULE_PERMISSION);
855         }
856 
857 
858         @Override
onResume()859         public void onResume() {
860             super.onResume();
861             updatePreferenceVisibility();
862         }
863 
864         @Override
onDestroy()865         public void onDestroy() {
866             super.onDestroy();
867             LocalBroadcastManager.getInstance(getContext())
868                     .unregisterReceiver(mTestingModeChangedReceiver);
869         }
870 
871         /**
872          * Callback to be called when preference or master toggle is changed by user
873          *
874          * @param context Context to use
875          */
onPreferenceChangedByUser(Context context)876         public void onPreferenceChangedByUser(Context context) {
877             CellBroadcastReceiver.startConfigService(context,
878                     CellBroadcastConfigService.ACTION_ENABLE_CHANNELS);
879             setPreferenceChanged(context, true);
880 
881             // Notify backup manager a backup pass is needed.
882             new BackupManager(context).dataChanged();
883         }
884     }
885 
886     /**
887      * Check whether vibration toggle is visible
888      * @param context Context
889      * @param res resources
890      */
isVibrationToggleVisible(Context context, Resources res)891     public static boolean isVibrationToggleVisible(Context context, Resources res) {
892         Vibrator vibrator = context.getSystemService(Vibrator.class);
893         boolean supportVibration = (vibrator != null) && vibrator.hasVibrator();
894         boolean isVibrationToggleVisible = supportVibration
895                 && (res.getBoolean(R.bool.show_override_dnd_settings)
896                 || !res.getBoolean(R.bool.override_dnd));
897         return isVibrationToggleVisible;
898     }
899 
isTestAlertsToggleVisible(Context context)900     public static boolean isTestAlertsToggleVisible(Context context) {
901         return isTestAlertsToggleVisible(context, null);
902     }
903 
904     /**
905      * Check whether test alert toggle is visible
906      * @param context Context
907      * @param operator Opeator numeric
908      */
isTestAlertsToggleVisible(Context context, String operator)909     public static boolean isTestAlertsToggleVisible(Context context, String operator) {
910         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(context,
911                 SubscriptionManager.getDefaultSubscriptionId(), operator);
912         Resources res = operator == null ? getResourcesForDefaultSubId(context)
913                 : getResourcesByOperator(context,
914                         SubscriptionManager.getDefaultSubscriptionId(), operator);
915         boolean isTestAlertsAvailable = !channelManager.getCellBroadcastChannelRanges(
916                 R.array.required_monthly_test_range_strings).isEmpty()
917                 || (!channelManager.getCellBroadcastChannelRanges(
918                 R.array.exercise_alert_range_strings).isEmpty()
919                 /** exercise toggle is controlled under the main test toggle */
920                 && (!res.getBoolean(R.bool.show_separate_exercise_settings)))
921                 || (!channelManager.getCellBroadcastChannelRanges(
922                 R.array.operator_defined_alert_range_strings).isEmpty()
923                 /** operator defined toggle is controlled under the main test toggle */
924                 && (!res.getBoolean(R.bool.show_separate_operator_defined_settings)))
925                 || !channelManager.getCellBroadcastChannelRanges(
926                 R.array.etws_test_alerts_range_strings).isEmpty();
927 
928         return (res.getBoolean(R.bool.show_test_settings)
929                 || CellBroadcastReceiver.isTestingMode(context))
930                 && isTestAlertsAvailable;
931     }
932 
933     /**
934      * Get the device resource based on SIM
935      *
936      * @param context Context
937      * @param subId Subscription index
938      *
939      * @return The resource
940      */
getResources(@onNull Context context, int subId)941     public static @NonNull Resources getResources(@NonNull Context context, int subId) {
942 
943         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
944                 || !SubscriptionManager.isValidSubscriptionId(subId)) {
945             return context.getResources();
946         }
947 
948         return SubscriptionManager.getResourcesForSubId(context, subId);
949     }
950 
951     /**
952      * Get the resources using the default subscription ID.
953      * @param context Context
954      * @return the Resources for the default subscription ID, or if there is no default subscription
955      * from SubscriptionManager, the resources for the latest loaded SIM.
956      */
getResourcesForDefaultSubId(@onNull Context context)957     public static @NonNull Resources getResourcesForDefaultSubId(@NonNull Context context) {
958         return getResources(context, SubscriptionManager.getDefaultSubscriptionId());
959     }
960 
961     /**
962      * Get the resources per network operator
963      * @param context Context
964      * @param operator Opeator numeric
965      * @return the Resources based on network operator
966      */
getResourcesByOperator( @onNull Context context, int subId, @NonNull String operator)967     public static @NonNull Resources getResourcesByOperator(
968             @NonNull Context context, int subId, @NonNull String operator) {
969         if (operator == null || operator.isEmpty()) {
970             return getResources(context, subId);
971         }
972 
973         synchronized (sCacheLock) {
974             Resources res = sResourcesCacheByOperator.get(operator);
975             if (res != null) {
976                 return res;
977             }
978 
979             Configuration overrideConfig = new Configuration();
980             try {
981                 int mcc = Integer.parseInt(operator.substring(0, 3));
982                 int mnc = operator.length() > 3 ? Integer.parseInt(operator.substring(3))
983                         : Configuration.MNC_ZERO;
984 
985                 overrideConfig.mcc = mcc;
986                 overrideConfig.mnc = mnc;
987             } catch (NumberFormatException e) {
988                 // should not happen
989                 Log.e(TAG, "invalid operator: " + operator);
990                 return context.getResources();
991             }
992 
993             Context newContext = context.createConfigurationContext(overrideConfig);
994             res = newContext.getResources();
995 
996             sResourcesCacheByOperator.put(operator, res);
997             return res;
998         }
999     }
1000 
1001     /**
1002      * Get the resources id which is used for the default value of the preference
1003      * @param key the preference key
1004      * @return a valid resources id if the key is valid and the default value is
1005      * defined, otherwise 0
1006      */
getResourcesIdForDefaultPrefValue(String key)1007     public static int getResourcesIdForDefaultPrefValue(String key) {
1008         switch (key) {
1009             case KEY_ENABLE_ALERTS_MASTER_TOGGLE:
1010                 return R.bool.master_toggle_enabled_default;
1011             case KEY_ENABLE_PUBLIC_SAFETY_MESSAGES:
1012                 return R.bool.public_safety_messages_enabled_default;
1013             case KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN:
1014                 return R.bool.public_safety_messages_full_screen_enabled_default;
1015             case KEY_ENABLE_EMERGENCY_ALERTS:
1016                 return R.bool.emergency_alerts_enabled_default;
1017             case KEY_ENABLE_ALERT_SPEECH:
1018                 return R.bool.enable_alert_speech_default;
1019             case KEY_OVERRIDE_DND:
1020                 return R.bool.override_dnd_default;
1021             case KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS:
1022                 return R.bool.extreme_threat_alerts_enabled_default;
1023             case KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS:
1024                 return R.bool.severe_threat_alerts_enabled_default;
1025             case KEY_ENABLE_CMAS_AMBER_ALERTS:
1026                 return R.bool.amber_alerts_enabled_default;
1027             case KEY_ENABLE_TEST_ALERTS:
1028                 return R.bool.test_alerts_enabled_default;
1029             case KEY_ENABLE_EXERCISE_ALERTS:
1030                 return R.bool.test_exercise_alerts_enabled_default;
1031             case KEY_OPERATOR_DEFINED_ALERTS:
1032                 return R.bool.test_operator_defined_alerts_enabled_default;
1033             case KEY_ENABLE_STATE_LOCAL_TEST_ALERTS:
1034                 return R.bool.state_local_test_alerts_enabled_default;
1035             case KEY_ENABLE_AREA_UPDATE_INFO_ALERTS:
1036                 return R.bool.area_update_info_alerts_enabled_default;
1037             default:
1038                 return 0;
1039         }
1040     }
1041 
1042     /**
1043      * Reset the resources cache.
1044      */
1045     @VisibleForTesting
resetResourcesCache()1046     public static void resetResourcesCache() {
1047         synchronized (sCacheLock) {
1048             sResourcesCacheByOperator.clear();
1049         }
1050     }
1051 }
1052