• 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.cellbroadcastreceiver.CellBroadcastReceiver.VDBG;
20 
21 import android.app.IntentService;
22 import android.app.Notification;
23 import android.app.NotificationManager;
24 import android.app.PendingIntent;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.res.Resources;
29 import android.preference.PreferenceManager;
30 import android.telephony.SmsManager;
31 import android.telephony.SubscriptionInfo;
32 import android.telephony.SubscriptionManager;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import androidx.annotation.NonNull;
37 
38 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.modules.utils.build.SdkLevel;
41 
42 import java.lang.reflect.Method;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.List;
46 
47 /**
48  * This service manages enabling and disabling ranges of message identifiers
49  * that the radio should listen for. It operates independently of the other
50  * services and runs at boot time and after exiting airplane mode.
51  *
52  * Note that the entire range of emergency channels is enabled. Test messages
53  * and lower priority broadcasts are filtered out in CellBroadcastAlertService
54  * if the user has not enabled them in settings.
55  *
56  * TODO: add notification to re-enable channels after a radio reset.
57  */
58 public class CellBroadcastConfigService extends IntentService {
59     private static final String TAG = "CellBroadcastConfigService";
60 
61     @VisibleForTesting
62     public static final String ACTION_ENABLE_CHANNELS = "ACTION_ENABLE_CHANNELS";
63     public static final String ACTION_UPDATE_SETTINGS_FOR_CARRIER = "UPDATE_SETTINGS_FOR_CARRIER";
64 
CellBroadcastConfigService()65     public CellBroadcastConfigService() {
66         super(TAG);          // use class name for worker thread name
67     }
68 
69     @Override
onHandleIntent(Intent intent)70     protected void onHandleIntent(Intent intent) {
71         if (ACTION_ENABLE_CHANNELS.equals(intent.getAction())) {
72             try {
73                 SubscriptionManager subManager = (SubscriptionManager) getApplicationContext()
74                         .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
75 
76                 if (subManager != null) {
77                     // Retrieve all the active subscription indice and enable cell broadcast
78                     // messages on all subs. The duplication detection will be done at the
79                     // frameworks.
80                     int[] subIds = getActiveSubIdList(subManager);
81                     if (subIds.length != 0) {
82                         for (int subId : subIds) {
83                             log("Enable CellBroadcast on sub " + subId);
84                             enableCellBroadcastChannels(subId);
85                             enableCellBroadcastRoamingChannelsAsNeeded(subId);
86                         }
87                     } else {
88                         // For no sim scenario.
89                         enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
90                         enableCellBroadcastRoamingChannelsAsNeeded(
91                                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
92                     }
93                 }
94             } catch (Exception ex) {
95                 Log.e(TAG, "exception enabling cell broadcast channels", ex);
96             }
97         } else if (ACTION_UPDATE_SETTINGS_FOR_CARRIER.equals(intent.getAction())) {
98             Context c = getApplicationContext();
99             if (CellBroadcastSettings.hasAnyPreferenceChanged(c)) {
100                 Log.d(TAG, "Preference has changed from user set, posting notification.");
101 
102                 CellBroadcastAlertService.createNotificationChannels(c);
103                 Intent settingsIntent = new Intent(c, CellBroadcastSettings.class);
104                 PendingIntent pi = PendingIntent.getActivity(c,
105                         CellBroadcastAlertService.SETTINGS_CHANGED_NOTIFICATION_ID, settingsIntent,
106                         PendingIntent.FLAG_ONE_SHOT
107                                 | PendingIntent.FLAG_UPDATE_CURRENT
108                                 | PendingIntent.FLAG_IMMUTABLE);
109 
110                 Notification.Builder builder = new Notification.Builder(c,
111                         CellBroadcastAlertService.NOTIFICATION_CHANNEL_SETTINGS_UPDATES)
112                         .setCategory(Notification.CATEGORY_SYSTEM)
113                         .setContentTitle(c.getString(R.string.notification_cb_settings_changed_title))
114                         .setContentText(c.getString(R.string.notification_cb_settings_changed_text))
115                         .setSmallIcon(R.drawable.ic_settings_gear_outline_24dp)
116                         .setContentIntent(pi)
117                         .setAutoCancel(true);
118                 NotificationManager notificationManager = c.getSystemService(
119                         NotificationManager.class);
120                 notificationManager.notify(
121                         CellBroadcastAlertService.SETTINGS_CHANGED_NOTIFICATION_ID,
122                         builder.build());
123             }
124             Log.e(TAG, "Reset all preferences");
125             CellBroadcastSettings.resetAllPreferences(getApplicationContext());
126         }
127     }
128 
129     @NonNull
getActiveSubIdList(SubscriptionManager subMgr)130     private int[] getActiveSubIdList(SubscriptionManager subMgr) {
131         List<SubscriptionInfo> subInfos = subMgr.getActiveSubscriptionInfoList();
132         int size = subInfos != null ? subInfos.size() : 0;
133         int[] subIds = new int[size];
134         for (int i = 0; i < size; i++) {
135             subIds[i] = subInfos.get(i).getSubscriptionId();
136         }
137         return subIds;
138     }
139 
resetCellBroadcastChannels(int subId)140     private void resetCellBroadcastChannels(int subId) {
141         SmsManager manager;
142         if (subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
143             manager = SmsManager.getSmsManagerForSubscriptionId(subId);
144         } else {
145             manager = SmsManager.getDefault();
146         }
147         // SmsManager.resetAllCellBroadcastRanges is a new @SystemAPI in S. We need to support
148         // backward compatibility as the module need to run on R build as well.
149         if (SdkLevel.isAtLeastS()) {
150             manager.resetAllCellBroadcastRanges();
151         } else {
152             try {
153                 Method method = SmsManager.class.getDeclaredMethod("resetAllCellBroadcastRanges");
154                 method.invoke(manager);
155             } catch (Exception e) {
156                 log("Can't reset cell broadcast ranges. e=" + e);
157             }
158         }
159     }
160 
161     /**
162      * Enable cell broadcast messages channels. Messages can be only received on the
163      * enabled channels.
164      *
165      * @param subId Subscription index
166      */
167     @VisibleForTesting
enableCellBroadcastChannels(int subId)168     public void enableCellBroadcastChannels(int subId) {
169         resetCellBroadcastChannels(subId);
170 
171         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
172         Resources res = getResources(subId, null);
173 
174         // boolean for each user preference checkbox, true for checked, false for unchecked
175         // Note: If enableAlertsMasterToggle is false, it disables ALL emergency broadcasts
176         // except for always-on alerts e.g, presidential. i.e. to receive CMAS severe alerts, both
177         // enableAlertsMasterToggle AND enableCmasSevereAlerts must be true.
178         boolean enableAlertsMasterToggle = prefs.getBoolean(
179                 CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
180 
181         boolean enableEtwsAlerts = enableAlertsMasterToggle;
182 
183         // CMAS Presidential must be always on (See 3GPP TS 22.268 Section 6.2) regardless
184         // user's preference
185         boolean enablePresidential = true;
186 
187         boolean enableCmasExtremeAlerts = enableAlertsMasterToggle && prefs.getBoolean(
188                 CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
189 
190         boolean enableCmasSevereAlerts = enableAlertsMasterToggle && prefs.getBoolean(
191                 CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
192 
193         boolean enableCmasAmberAlerts = enableAlertsMasterToggle && prefs.getBoolean(
194                 CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
195 
196         boolean enableTestAlerts = enableAlertsMasterToggle
197                 && CellBroadcastSettings.isTestAlertsToggleVisible(getApplicationContext())
198                 && prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false);
199 
200         boolean enableExerciseAlerts = enableAlertsMasterToggle
201                 && res.getBoolean(R.bool.show_separate_exercise_settings)
202                 && prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_EXERCISE_ALERTS, false);
203 
204         boolean enableOperatorDefined = enableAlertsMasterToggle
205                 && res.getBoolean(R.bool.show_separate_operator_defined_settings)
206                 && prefs.getBoolean(CellBroadcastSettings.KEY_OPERATOR_DEFINED_ALERTS, false);
207 
208         boolean enableAreaUpdateInfoAlerts = res.getBoolean(
209                 R.bool.config_showAreaUpdateInfoSettings)
210                 && prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_AREA_UPDATE_INFO_ALERTS,
211                 false);
212 
213         boolean enablePublicSafetyMessagesChannelAlerts = enableAlertsMasterToggle
214                 && prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES,
215                 true);
216         boolean enableStateLocalTestAlerts = enableAlertsMasterToggle
217                 && (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS,
218                 false)
219                 || (!res.getBoolean(R.bool.show_state_local_test_settings)
220                 && res.getBoolean(R.bool.state_local_test_alerts_enabled_default)));
221 
222         boolean enableEmergencyAlerts = enableAlertsMasterToggle && prefs.getBoolean(
223                 CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
224 
225         setCellBroadcastChannelsEnabled(subId, null, enableAlertsMasterToggle, enableEtwsAlerts,
226                 enablePresidential, enableCmasExtremeAlerts, enableCmasSevereAlerts,
227                 enableCmasAmberAlerts, enableTestAlerts, enableExerciseAlerts,
228                 enableOperatorDefined, enableAreaUpdateInfoAlerts,
229                 enablePublicSafetyMessagesChannelAlerts, enableStateLocalTestAlerts,
230                 enableEmergencyAlerts, true);
231     }
232 
setCellBroadcastChannelsEnabled(int subId, @NonNull String operator, boolean enableAlertsMasterToggle, boolean enableEtwsAlerts, boolean enablePresidential, boolean enableCmasExtremeAlerts, boolean enableCmasSevereAlerts, boolean enableCmasAmberAlerts, boolean enableTestAlerts, boolean enableExerciseAlerts, boolean enableOperatorDefined, boolean enableAreaUpdateInfoAlerts, boolean enablePublicSafetyMessagesChannelAlerts, boolean enableStateLocalTestAlerts, boolean enableEmergencyAlerts, boolean enableGeoFencingTriggerMessage)233     private void setCellBroadcastChannelsEnabled(int subId, @NonNull String operator,
234             boolean enableAlertsMasterToggle, boolean enableEtwsAlerts, boolean enablePresidential,
235             boolean enableCmasExtremeAlerts, boolean enableCmasSevereAlerts,
236             boolean enableCmasAmberAlerts, boolean enableTestAlerts, boolean enableExerciseAlerts,
237             boolean enableOperatorDefined, boolean enableAreaUpdateInfoAlerts,
238             boolean enablePublicSafetyMessagesChannelAlerts, boolean enableStateLocalTestAlerts,
239             boolean enableEmergencyAlerts, boolean enableGeoFencingTriggerMessage) {
240 
241         if (VDBG) {
242             log("setCellBroadcastChannelsEnabled for " + subId + ", operator: " + operator);
243             log("enableAlertsMasterToggle = " + enableAlertsMasterToggle);
244             log("enableEtwsAlerts = " + enableEtwsAlerts);
245             log("enablePresidential = " + enablePresidential);
246             log("enableCmasExtremeAlerts = " + enableCmasExtremeAlerts);
247             log("enableCmasSevereAlerts = " + enableCmasSevereAlerts);
248             log("enableCmasAmberAlerts = " + enableCmasAmberAlerts);
249             log("enableTestAlerts = " + enableTestAlerts);
250             log("enableExerciseAlerts = " + enableExerciseAlerts);
251             log("enableOperatorDefinedAlerts = " + enableOperatorDefined);
252             log("enableAreaUpdateInfoAlerts = " + enableAreaUpdateInfoAlerts);
253             log("enablePublicSafetyMessagesChannelAlerts = "
254                     + enablePublicSafetyMessagesChannelAlerts);
255             log("enableStateLocalTestAlerts = " + enableStateLocalTestAlerts);
256             log("enableEmergencyAlerts = " + enableEmergencyAlerts);
257             log("enableGeoFencingTriggerMessage = " + enableGeoFencingTriggerMessage);
258         }
259 
260         boolean isEnableOnly = !TextUtils.isEmpty(operator);
261         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
262                 getApplicationContext(), subId, operator);
263 
264         /** Enable CMAS series messages. */
265 
266         // Enable/Disable Presidential messages.
267         setCellBroadcastRange(subId, isEnableOnly, enablePresidential,
268                 channelManager.getCellBroadcastChannelRanges(
269                         R.array.cmas_presidential_alerts_channels_range_strings));
270 
271         // Enable/Disable CMAS extreme messages.
272         setCellBroadcastRange(subId, isEnableOnly, enableCmasExtremeAlerts,
273                 channelManager.getCellBroadcastChannelRanges(
274                         R.array.cmas_alert_extreme_channels_range_strings));
275 
276         // Enable/Disable CMAS severe messages.
277         setCellBroadcastRange(subId, isEnableOnly, enableCmasSevereAlerts,
278                 channelManager.getCellBroadcastChannelRanges(
279                         R.array.cmas_alerts_severe_range_strings));
280 
281         // Enable/Disable CMAS amber alert messages.
282         setCellBroadcastRange(subId, isEnableOnly, enableCmasAmberAlerts,
283                 channelManager.getCellBroadcastChannelRanges(
284                         R.array.cmas_amber_alerts_channels_range_strings));
285 
286         // Enable/Disable test messages.
287         setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts,
288                 channelManager.getCellBroadcastChannelRanges(
289                         R.array.required_monthly_test_range_strings));
290 
291         // Enable/Disable exercise test messages.
292         // This could either controlled by main test toggle or separate exercise test toggle.
293         setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts || enableExerciseAlerts,
294                 channelManager.getCellBroadcastChannelRanges(
295                         R.array.exercise_alert_range_strings));
296 
297         // Enable/Disable operator defined test messages.
298         // This could either controlled by main test toggle or separate operator defined test toggle
299         setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts || enableOperatorDefined,
300                 channelManager.getCellBroadcastChannelRanges(
301                         R.array.operator_defined_alert_range_strings));
302 
303         // Enable/Disable GSM ETWS messages.
304         setCellBroadcastRange(subId, isEnableOnly, enableEtwsAlerts,
305                 channelManager.getCellBroadcastChannelRanges(
306                         R.array.etws_alerts_range_strings));
307 
308         // Enable/Disable GSM ETWS test messages.
309         setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts,
310                 channelManager.getCellBroadcastChannelRanges(
311                         R.array.etws_test_alerts_range_strings));
312 
313         // Enable/Disable GSM public safety messages.
314         setCellBroadcastRange(subId, isEnableOnly, enablePublicSafetyMessagesChannelAlerts,
315                 channelManager.getCellBroadcastChannelRanges(
316                         R.array.public_safety_messages_channels_range_strings));
317 
318         // Enable/Disable GSM state/local test alerts.
319         setCellBroadcastRange(subId, isEnableOnly, enableStateLocalTestAlerts,
320                 channelManager.getCellBroadcastChannelRanges(
321                         R.array.state_local_test_alert_range_strings));
322 
323         // Enable/Disable GSM geo-fencing trigger messages.
324         setCellBroadcastRange(subId, isEnableOnly, enableGeoFencingTriggerMessage,
325                 channelManager.getCellBroadcastChannelRanges(
326                         R.array.geo_fencing_trigger_messages_range_strings));
327 
328         // Enable non-CMAS series messages.
329         setCellBroadcastRange(subId, isEnableOnly, enableEmergencyAlerts,
330                 channelManager.getCellBroadcastChannelRanges(
331                         R.array.emergency_alerts_channels_range_strings));
332 
333         // Enable/Disable additional channels based on carrier specific requirement.
334         List<CellBroadcastChannelRange> ranges =
335                 channelManager.getCellBroadcastChannelRanges(
336                         R.array.additional_cbs_channels_strings);
337 
338         for (CellBroadcastChannelRange range: ranges) {
339             boolean enableAlerts;
340             switch (range.mAlertType) {
341                 case AREA:
342                     enableAlerts = enableAreaUpdateInfoAlerts;
343                     break;
344                 case TEST:
345                     enableAlerts = enableTestAlerts;
346                     break;
347                 default:
348                     enableAlerts = enableAlertsMasterToggle;
349             }
350             setCellBroadcastRange(subId, isEnableOnly, enableAlerts,
351                     new ArrayList<>(Arrays.asList(range)));
352         }
353     }
354 
355     /**
356      * Enable cell broadcast messages channels. Messages can be only received on the
357      * enabled channels.
358      *
359      * @param subId Subscription index
360      */
361     @VisibleForTesting
enableCellBroadcastRoamingChannelsAsNeeded(int subId)362     public void enableCellBroadcastRoamingChannelsAsNeeded(int subId) {
363         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
364             subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
365         }
366 
367         String roamingOperator = CellBroadcastReceiver.getRoamingOperatorSupported(this);
368         if (roamingOperator.isEmpty()) {
369             return;
370         }
371 
372         log("enableCellBroadcastRoamingChannels for roaming network:" + roamingOperator);
373         Resources res = getResources(subId, roamingOperator);
374 
375         // Get default config for roaming network as the settings are based on sim
376         boolean enablePresidential = true;
377 
378         boolean enableAlertsMasterToggle = res.getBoolean(R.bool.master_toggle_enabled_default);
379 
380         boolean enableEtwsAlerts = enableAlertsMasterToggle;
381 
382         boolean enableCmasExtremeAlerts = enableAlertsMasterToggle && res.getBoolean(
383                 R.bool.extreme_threat_alerts_enabled_default);
384 
385         boolean enableCmasSevereAlerts = enableAlertsMasterToggle && res.getBoolean(
386                 R.bool.severe_threat_alerts_enabled_default);
387 
388         boolean enableCmasAmberAlerts = enableAlertsMasterToggle && res.getBoolean(
389                 R.bool.amber_alerts_enabled_default);
390 
391         boolean enableTestAlerts = enableAlertsMasterToggle && CellBroadcastSettings
392                 .isTestAlertsToggleVisible(getApplicationContext(), roamingOperator)
393                 && res.getBoolean(R.bool.test_alerts_enabled_default);
394 
395         boolean enableExerciseAlerts = enableAlertsMasterToggle
396                 && res.getBoolean(R.bool.show_separate_exercise_settings)
397                 && res.getBoolean(R.bool.test_exercise_alerts_enabled_default);
398 
399         boolean enableOperatorDefined = enableAlertsMasterToggle
400                 && res.getBoolean(R.bool.show_separate_operator_defined_settings)
401                 && res.getBoolean(R.bool.test_operator_defined_alerts_enabled_default);
402 
403         boolean enableAreaUpdateInfoAlerts = res.getBoolean(
404                 R.bool.config_showAreaUpdateInfoSettings)
405                 && res.getBoolean(R.bool.area_update_info_alerts_enabled_default);
406 
407         boolean enablePublicSafetyMessagesChannelAlerts = enableAlertsMasterToggle
408                 && res.getBoolean(R.bool.public_safety_messages_enabled_default);
409         boolean enableStateLocalTestAlerts = enableAlertsMasterToggle
410                 && res.getBoolean(R.bool.state_local_test_alerts_enabled_default);
411 
412         boolean enableEmergencyAlerts = enableAlertsMasterToggle && res.getBoolean(
413                 R.bool.emergency_alerts_enabled_default);
414 
415         setCellBroadcastChannelsEnabled(subId, roamingOperator, enableAlertsMasterToggle,
416                 enableEtwsAlerts, enablePresidential, enableCmasExtremeAlerts,
417                 enableCmasSevereAlerts, enableCmasAmberAlerts, enableTestAlerts,
418                 enableExerciseAlerts, enableOperatorDefined, enableAreaUpdateInfoAlerts,
419                 enablePublicSafetyMessagesChannelAlerts, enableStateLocalTestAlerts,
420                 enableEmergencyAlerts, true);
421     }
422 
423     /**
424      * Enable/disable cell broadcast with messages id range
425      * @param subId Subscription index
426      * @param isEnableOnly, True for enabling channel only for roaming network
427      * @param enable True for enabling cell broadcast with id range, otherwise for disabling
428      * @param ranges Cell broadcast id ranges
429      */
setCellBroadcastRange(int subId, boolean isEnableOnly, boolean enable, List<CellBroadcastChannelRange> ranges)430     private void setCellBroadcastRange(int subId, boolean isEnableOnly,
431             boolean enable, List<CellBroadcastChannelRange> ranges) {
432         SmsManager manager;
433         if (subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
434             manager = SmsManager.getSmsManagerForSubscriptionId(subId);
435         } else {
436             manager = SmsManager.getDefault();
437         }
438 
439         if (ranges != null) {
440             for (CellBroadcastChannelRange range: ranges) {
441                 if (range.mAlwaysOn) {
442                     log("mAlwaysOn is set to true, enable the range: " + range.mStartId
443                             + ":" + range.mEndId);
444                     enable = true;
445                 }
446 
447                 if (enable) {
448                     if (VDBG) {
449                         log("enableCellBroadcastRange[" + range.mStartId + "-" + range.mEndId
450                                 + "], type:" + range.mRanType);
451                     }
452                     manager.enableCellBroadcastRange(range.mStartId, range.mEndId, range.mRanType);
453                 } else if (!isEnableOnly) {
454                     if (VDBG) {
455                         log("disableCellBroadcastRange[" + range.mStartId + "-" + range.mEndId
456                                 + "], type:" + range.mRanType);
457                     }
458                     manager.disableCellBroadcastRange(range.mStartId, range.mEndId, range.mRanType);
459                 }
460             }
461         }
462     }
463 
464 
465     /**
466      * Get resource according to the operator or subId
467      * @param subId Subscription index
468      * @param operator Operator numeric, the resource will be retrieved by it if it is no null,
469      * otherwise, by the sub id.
470      */
471     @VisibleForTesting
getResources(int subId, String operator)472     public Resources getResources(int subId, String operator) {
473         if (operator == null) {
474             return CellBroadcastSettings.getResources(this, subId);
475         }
476         return CellBroadcastSettings.getResourcesByOperator(this, subId, operator);
477     }
478 
log(String msg)479     private static void log(String msg) {
480         Log.d(TAG, msg);
481     }
482 }
483