1 /*
2  * Copyright (C) 2013 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.cellbroadcastservice;
18 
19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
20 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21 
22 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_AMBIGUOUS;
23 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_DONT_SEND;
24 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_NO_COORDINATES;
25 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_SEND;
26 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_SENT;
27 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRSRC_CBS;
28 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_FOUND_MULTIPLECBRPKGS;
29 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_NOTFOUND_DEFAULTCBRPKGS;
30 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERR_UNEXPECTED_CDMA_MSG_FROM_FWK;
31 import static com.android.cellbroadcastservice.CellBroadcastMetrics.FILTER_DUPLICATE;
32 import static com.android.cellbroadcastservice.CellBroadcastMetrics.FILTER_GEOFENCED;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.SuppressLint;
37 import android.app.Activity;
38 import android.content.BroadcastReceiver;
39 import android.content.ContentValues;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.pm.ApplicationInfo;
44 import android.content.pm.PackageManager;
45 import android.content.pm.ResolveInfo;
46 import android.content.res.Resources;
47 import android.database.Cursor;
48 import android.location.Location;
49 import android.location.LocationListener;
50 import android.location.LocationManager;
51 import android.location.LocationRequest;
52 import android.net.Uri;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.Looper;
56 import android.os.Message;
57 import android.os.Process;
58 import android.os.SystemClock;
59 import android.os.SystemProperties;
60 import android.os.UserHandle;
61 import android.provider.Telephony;
62 import android.provider.Telephony.CellBroadcasts;
63 import android.telephony.CbGeoUtils.LatLng;
64 import android.telephony.CellBroadcastIntents;
65 import android.telephony.SmsCbMessage;
66 import android.telephony.SubscriptionManager;
67 import android.telephony.cdma.CdmaSmsCbProgramData;
68 import android.text.TextUtils;
69 import android.util.LocalLog;
70 import android.util.Log;
71 
72 import com.android.internal.annotations.VisibleForTesting;
73 import com.android.modules.utils.HandlerExecutor;
74 import com.android.modules.utils.build.SdkLevel;
75 
76 import java.io.File;
77 import java.io.FileDescriptor;
78 import java.io.PrintWriter;
79 import java.text.DateFormat;
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.HashMap;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Objects;
86 import java.util.concurrent.TimeUnit;
87 import java.util.stream.Collectors;
88 import java.util.stream.Stream;
89 
90 /**
91  * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
92  * completes and our result receiver is called.
93  */
94 public class CellBroadcastHandler extends WakeLockStateMachine {
95     private static final String TAG = "CellBroadcastHandler";
96 
97     /**
98      * CellBroadcast apex name
99      */
100     private static final String CB_APEX_NAME = "com.android.cellbroadcast";
101 
102     /**
103      * CellBroadcast app platform name
104      */
105     private static final String CB_APP_PLATFORM_NAME = "CellBroadcastAppPlatform";
106 
107     /**
108      * Path where CB apex is mounted (/apex/com.android.cellbroadcast)
109      */
110     private static final String CB_APEX_PATH = new File("/apex", CB_APEX_NAME).getAbsolutePath();
111 
112     private static final String EXTRA_MESSAGE = "message";
113 
114     /**
115      * To disable cell broadcast duplicate detection for debugging purposes
116      * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
117      * --ez enable false</code>
118      *
119      * To enable cell broadcast duplicate detection for debugging purposes
120      * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
121      * --ez enable true</code>
122      */
123     private static final String ACTION_DUPLICATE_DETECTION =
124             "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
125 
126     /**
127      * The extra for cell broadcast duplicate detection enable/disable
128      */
129     private static final String EXTRA_ENABLE = "enable";
130 
131     private final LocalLog mLocalLog = new LocalLog(100);
132 
133     private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
134 
135     /**
136      * Used to register receivers as exported to other apps on the device. The Context flag was
137      * introduced in T, and all previous releases use a default value of 0 to export the receiver.
138      */
139     protected static final int RECEIVER_EXPORTED =
140             SdkLevel.isAtLeastT() ? Context.RECEIVER_EXPORTED : 0;
141 
142     /** Uses to request the location update. */
143     private final LocationRequester mLocationRequester;
144 
145     /** Used to inject new calculators during unit testing */
146     @NonNull
147     protected final CbSendMessageCalculatorFactory mCbSendMessageCalculatorFactory;
148 
149     /** Timestamp of last airplane mode on */
150     protected long mLastAirplaneModeTime = 0;
151 
152     /** Resource cache used for test purpose, to be removed by b/223644462 */
153     protected final Map<Integer, Resources> mResourcesCache = new HashMap<>();
154 
155     /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
156     private boolean mEnableDuplicateDetection = true;
157 
158     /**
159      * Service category equivalent map. The key is the GSM service category, the value is the CDMA
160      * service category.
161      */
162     private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
163 
164     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
165         @Override
166         public void onReceive(Context context, Intent intent) {
167             switch (intent.getAction()) {
168                 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
169                     boolean airplaneModeOn = intent.getBooleanExtra("state", false);
170                     if (airplaneModeOn) {
171                         mLastAirplaneModeTime = System.currentTimeMillis();
172                         log("Airplane mode on.");
173                     }
174                     break;
175                 case ACTION_DUPLICATE_DETECTION:
176                     mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
177                             true);
178                     log("Duplicate detection " + (mEnableDuplicateDetection
179                             ? "enabled" : "disabled"));
180                     break;
181                 default:
182                     log("Unhandled broadcast " + intent.getAction());
183             }
184         }
185     };
186 
CellBroadcastHandler(Context context)187     private CellBroadcastHandler(Context context) {
188         this(CellBroadcastHandler.class.getSimpleName(), context, Looper.myLooper(),
189                 new CbSendMessageCalculatorFactory(), null);
190     }
191 
192     /**
193      * Allows tests to inject new calculators
194      */
195     @VisibleForTesting
196     public static class CbSendMessageCalculatorFactory {
CbSendMessageCalculatorFactory()197         public CbSendMessageCalculatorFactory() {
198         }
199 
200         /**
201          * Creates new calculator
202          *
203          * @param context context
204          * @param fences  the geo fences to use in the calculator
205          * @return a new instance of the calculator
206          */
createNew(@onNull final Context context, @NonNull final List<android.telephony.CbGeoUtils.Geometry> fences)207         public CbSendMessageCalculator createNew(@NonNull final Context context,
208                 @NonNull final List<android.telephony.CbGeoUtils.Geometry> fences) {
209             return new CbSendMessageCalculator(context, fences);
210         }
211     }
212 
213     @VisibleForTesting
CellBroadcastHandler(String debugTag, Context context, Looper looper, @NonNull final CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, @Nullable HandlerHelper handlerHelper)214     public CellBroadcastHandler(String debugTag, Context context, Looper looper,
215             @NonNull final CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory,
216             @Nullable HandlerHelper handlerHelper) {
217         super(debugTag, context, looper);
218 
219         if (handlerHelper == null) {
220             // Would have preferred to not have handlerHelper has nullable and pass this through the
221             // default ctor.  Had trouble doing this because getHander() can't be called until
222             // the type is fully constructed.
223             handlerHelper = new HandlerHelper(getHandler());
224         }
225         mCbSendMessageCalculatorFactory = cbSendMessageCalculatorFactory;
226         mLocationRequester = new LocationRequester(
227                 context,
228                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
229                 handlerHelper, getName());
230 
231         // Adding GSM / CDMA service category mapping.
232         mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
233                 // Presidential alert
234                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
235                         CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
236                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
237                         CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
238 
239                 // Extreme alert
240                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
241                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
242                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
243                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
244                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
245                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
246                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
247                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
248 
249                 // Severe alert
250                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
251                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
252                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
253                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
254                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
255                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
256                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
257                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
258                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
259                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
260                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
261                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
262                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
263                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
264                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
265                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
266                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
267                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
268                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
269                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
270                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
271                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
272                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
273                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
274 
275                 // Amber alert
276                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
277                         CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
278                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
279                         CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
280 
281                 // Monthly test alert
282                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
283                         CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
284                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
285                         CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
286         }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
287 
288         IntentFilter intentFilter = new IntentFilter();
289         intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
290         if (IS_DEBUGGABLE) {
291             intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
292         }
293 
294         mContext.registerReceiver(mReceiver, intentFilter, RECEIVER_EXPORTED);
295     }
296 
cleanup()297     public void cleanup() {
298         if (DBG) log("CellBroadcastHandler cleanup");
299         mContext.unregisterReceiver(mReceiver);
300     }
301 
302     /**
303      * Create a new CellBroadcastHandler.
304      * @param context the context to use for dispatching Intents
305      * @return the new handler
306      */
makeCellBroadcastHandler(Context context)307     public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
308         CellBroadcastHandler handler = new CellBroadcastHandler(context);
309         handler.start();
310         return handler;
311     }
312 
313     /**
314      * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
315      * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
316      *
317      * @param message the message to process
318      * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
319      */
320     @Override
handleSmsMessage(Message message)321     protected boolean handleSmsMessage(Message message) {
322         if (message.obj instanceof SmsCbMessage) {
323             if (!isDuplicate((SmsCbMessage) message.obj)) {
324                 handleBroadcastSms((SmsCbMessage) message.obj);
325                 return true;
326             } else {
327                 CellBroadcastServiceMetrics.getInstance()
328                         .logMessageFiltered(FILTER_DUPLICATE, (SmsCbMessage) message.obj);
329 
330             }
331             return false;
332         } else {
333             final String errorMessage =
334                     "handleSmsMessage got object of type: " + message.obj.getClass().getName();
335             loge(errorMessage);
336             CellBroadcastServiceMetrics.getInstance().logMessageError(
337                     ERR_UNEXPECTED_CDMA_MSG_FROM_FWK, errorMessage);
338             return false;
339         }
340     }
341 
342     /**
343      * Get the maximum time for waiting location.
344      *
345      * @param message Cell broadcast message
346      * @return The maximum waiting time in second
347      */
getMaxLocationWaitingTime(SmsCbMessage message)348     protected int getMaxLocationWaitingTime(SmsCbMessage message) {
349         int maximumTime = message.getMaximumWaitingDuration();
350         if (maximumTime == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
351             Resources res = getResources(message.getSubscriptionId());
352             maximumTime = res.getInteger(R.integer.max_location_waiting_time);
353         }
354         return maximumTime;
355     }
356 
357     /**
358      * Dispatch a Cell Broadcast message to listeners.
359      * @param message the Cell Broadcast to broadcast
360      */
handleBroadcastSms(SmsCbMessage message)361     protected void handleBroadcastSms(SmsCbMessage message) {
362         int slotIndex = message.getSlotIndex();
363 
364         // TODO: Database inserting can be time consuming, therefore this should be changed to
365         // asynchronous.
366         ContentValues cv = message.getContentValues();
367         Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
368 
369         if (message.needGeoFencingCheck()) {
370             int maximumWaitingTime = getMaxLocationWaitingTime(message);
371             if (DBG) {
372                 log("Requesting location for geo-fencing. serialNumber = "
373                         + message.getSerialNumber() + ", maximumWaitingTime = "
374                         + maximumWaitingTime);
375             }
376 
377             CbSendMessageCalculator calculator =
378                     mCbSendMessageCalculatorFactory.createNew(mContext, message.getGeometries());
379             requestLocationUpdate(new LocationUpdateCallback() {
380                 @Override
381                 public void onLocationUpdate(@NonNull LatLng location,
382                         float accuracy) {
383                     if (VDBG) {
384                         logd("onLocationUpdate: location=" + location
385                                 + ", acc=" + accuracy + ". "  + getMessageString(message));
386                     }
387                     performGeoFencing(message, uri, calculator, location, slotIndex,
388                             accuracy);
389                 }
390 
391                 @Override
392                 public boolean areAllMessagesHandled() {
393                     return !isMessageInAmbiguousState(calculator);
394                 }
395 
396                 @Override
397                 public void onLocationUnavailable() {
398                     CellBroadcastHandler.this.onLocationUnavailable(
399                             calculator, message, uri, slotIndex);
400                 }
401             }, maximumWaitingTime);
402         } else {
403             if (DBG) {
404                 log("Broadcast the message directly because no geo-fencing required, "
405                         + " needGeoFencing = " + message.needGeoFencingCheck() + ". "
406                         + getMessageString(message));
407             }
408             broadcastMessage(message, uri, slotIndex);
409         }
410     }
411 
412     /**
413      * Returns true if the message calculator is in a non-ambiguous state.
414      *
415      * </b>Note:</b> NO_COORDINATES is considered ambiguous because we received no information
416      * in this case.
417      * @param calculator the message calculator
418      * @return whether or not the message is handled
419      */
isMessageInAmbiguousState(CbSendMessageCalculator calculator)420     protected boolean isMessageInAmbiguousState(CbSendMessageCalculator calculator) {
421         return calculator.getAction() == SEND_MESSAGE_ACTION_AMBIGUOUS
422                 || calculator.getAction() == SEND_MESSAGE_ACTION_NO_COORDINATES;
423     }
424 
425     /**
426      * When location requester cannot send anymore updates, we look at the calculated action and
427      * determine whether or not we should send it.
428      *
429      * see: {@code CellBroadcastHandler.LocationUpdateCallback#onLocationUnavailable} for more info.
430      *
431      * @param calculator the send message calculator
432      * @param message the cell broadcast message received
433      * @param uri the message's uri
434      * @param slotIndex the slot
435      */
onLocationUnavailable(CbSendMessageCalculator calculator, SmsCbMessage message, Uri uri, int slotIndex)436     protected void onLocationUnavailable(CbSendMessageCalculator calculator, SmsCbMessage message,
437             Uri uri, int slotIndex) {
438         @CbSendMessageCalculator.SendMessageAction int action = calculator.getAction();
439         if (DBG) {
440             logd("onLocationUnavailable: action="
441                     + CbSendMessageCalculator.getActionString(action) + ". "
442                     + getMessageString(message));
443         }
444 
445         if (isMessageInAmbiguousState(calculator)) {
446             /* Case 1. If we reached the end of the location time out and we are still in an
447                        ambiguous state or no coordinates state, we send the message.
448                Case 2. If we don't have permissions, then no location was received and the
449                        calculator's action is NO_COORDINATES, which means we also send. */
450             broadcastGeofenceMessage(message, uri, slotIndex, calculator);
451         } else if (action == SEND_MESSAGE_ACTION_DONT_SEND) {
452             geofenceMessageNotRequired(message);
453         }
454     }
455 
456     /**
457      * Check the location based on geographical scope defined in 3GPP TS 23.041 section 9.4.1.2.1.
458      *
459      * The Geographical Scope (GS) indicates the geographical area over which the Message Code
460      * is unique, and the display mode. The CBS message is not necessarily broadcast by all cells
461      * within the geographical area. When two CBS messages are received with identical Serial
462      * Numbers/Message Identifiers in two different cells, the Geographical Scope may be used to
463      * determine if the CBS messages are indeed identical.
464      *
465      * @param message The current message
466      * @param messageToCheck The older message in the database to be checked
467      * @return {@code true} if within the same area, otherwise {@code false}, which should be
468      * be considered as a new message.
469      */
isSameLocation(SmsCbMessage message, SmsCbMessage messageToCheck)470     private boolean isSameLocation(SmsCbMessage message,
471             SmsCbMessage messageToCheck) {
472         if (message.getGeographicalScope() != messageToCheck.getGeographicalScope()) {
473             return false;
474         }
475 
476         // only cell wide (which means that if a message is displayed it is desirable that the
477         // message is removed from the screen when the UE selects the next cell and if any CBS
478         // message is received in the next cell it is to be regarded as "new").
479         if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE
480                 || message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE) {
481             return message.getLocation().isInLocationArea(messageToCheck.getLocation());
482         }
483 
484         // Service Area wide (which means that a CBS message with the same Message Code and Update
485         // Number may or may not be "new" in the next cell according to whether the next cell is
486         // in the same Service Area as the current cell)
487         if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE) {
488             if (!message.getLocation().getPlmn().equals(messageToCheck.getLocation().getPlmn())) {
489                 return false;
490             }
491 
492             return message.getLocation().getLac() != -1
493                     && message.getLocation().getLac() == messageToCheck.getLocation().getLac();
494         }
495 
496         // PLMN wide (which means that the Message Code and/or Update Number must change in the
497         // next cell, of the PLMN, for the CBS message to be "new". The CBS message is only relevant
498         // to the PLMN in which it is broadcast, so any change of PLMN (including a change to
499         // another PLMN which is an ePLMN) means the CBS message is "new")
500         if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE) {
501             return TextUtils.equals(message.getLocation().getPlmn(),
502                     messageToCheck.getLocation().getPlmn());
503         }
504 
505         return false;
506     }
507 
508     /**
509      * Check if the message is a duplicate
510      *
511      * @param message Cell broadcast message
512      * @return {@code true} if this message is a duplicate
513      */
514     @VisibleForTesting
isDuplicate(SmsCbMessage message)515     public boolean isDuplicate(SmsCbMessage message) {
516         if (!mEnableDuplicateDetection) {
517             log("Duplicate detection was disabled for debugging purposes.");
518             return false;
519         }
520 
521         // Find the cell broadcast message identify by the message identifier and serial number
522         // and is not broadcasted.
523         String where = CellBroadcasts.RECEIVED_TIME + ">?";
524 
525         Resources res = getResources(message.getSubscriptionId());
526 
527         // Only consider cell broadcast messages received within certain period.
528         // By default it's 24 hours.
529         long expirationDuration = res.getInteger(R.integer.message_expiration_time);
530         long dupCheckTime = System.currentTimeMillis() - expirationDuration;
531 
532         // Some carriers require reset duplication detection after airplane mode or reboot.
533         if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
534             dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
535             dupCheckTime = Long.max(dupCheckTime,
536                     System.currentTimeMillis() - SystemClock.elapsedRealtime());
537         }
538 
539         List<SmsCbMessage> cbMessages = new ArrayList<>();
540 
541         try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
542                 CellBroadcastProvider.QUERY_COLUMNS,
543                 where,
544                 new String[] {Long.toString(dupCheckTime)},
545                 null)) {
546             if (cursor != null) {
547                 while (cursor.moveToNext()) {
548                     cbMessages.add(SmsCbMessage.createFromCursor(cursor));
549                 }
550             }
551         }
552 
553         boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
554         boolean compareServiceCategory = res.getBoolean(R.bool.duplicate_compare_service_category);
555         boolean crossSimDuplicateDetection = res.getBoolean(R.bool.cross_sim_duplicate_detection);
556 
557         log("Found " + cbMessages.size() + " messages since "
558                 + DateFormat.getDateTimeInstance().format(dupCheckTime));
559         log("compareMessageBody=" + compareMessageBody + ", compareServiceCategory="
560                 + compareServiceCategory + ", crossSimDuplicateDetection="
561                 + crossSimDuplicateDetection);
562         for (SmsCbMessage messageToCheck : cbMessages) {
563             // If messages are from different slots, then we only compare the message body.
564             if (VDBG) log("Checking the message " + messageToCheck);
565             if (crossSimDuplicateDetection
566                     && message.getSubscriptionId() != messageToCheck.getSubscriptionId()) {
567                 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
568                     log("Duplicate message detected from different slot. " + message);
569                     return true;
570                 }
571                 if (VDBG) log("Not from the same slot.");
572             } else {
573                 // Check serial number if message is from the same carrier.
574                 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
575                     if (VDBG) log("Serial number does not match.");
576                     // Not a dup. Check next one.
577                     continue;
578                 }
579 
580                 // ETWS primary / secondary should be treated differently.
581                 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
582                         && message.getEtwsWarningInfo().isPrimary()
583                         != messageToCheck.getEtwsWarningInfo().isPrimary()) {
584                     if (VDBG) log("ETWS primary/secondary does not match.");
585                     // Not a dup. Check next one.
586                     continue;
587                 }
588 
589                 // Check if the message category is different.
590                 if (compareServiceCategory
591                         && message.getServiceCategory() != messageToCheck.getServiceCategory()
592                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
593                                 message.getServiceCategory()), messageToCheck.getServiceCategory())
594                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
595                                 messageToCheck.getServiceCategory()),
596                         message.getServiceCategory())) {
597                     if (VDBG) log("Category does not match.");
598                     // Not a dup. Check next one.
599                     continue;
600                 }
601 
602                 // Check if the message location is different. Note this is only applicable to
603                 // 3GPP format cell broadcast messages.
604                 if (message.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP
605                         && messageToCheck.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP
606                         && !isSameLocation(message, messageToCheck)) {
607                     if (VDBG) log("Location does not match.");
608                     // Not a dup. Check next one.
609                     continue;
610                 }
611 
612                 // Compare message body if needed.
613                 if (!compareMessageBody || TextUtils.equals(
614                         message.getMessageBody(), messageToCheck.getMessageBody())) {
615                     log("Duplicate message detected. " + message);
616                     return true;
617                 } else {
618                     if (VDBG) log("Body does not match.");
619                 }
620             }
621         }
622 
623         log("Not a duplicate message. " + message);
624         return false;
625     }
626 
627     /**
628      * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
629      * {@code location} is inside the {@code broadcastArea}.
630      * @param message the message need to geo-fencing check
631      * @param uri the message's uri
632      * @param calculator the message calculator
633      * @param location current location
634      * @param slotIndex the index of the slot
635      * @param accuracy the accuracy of the coordinate given in meters
636      */
performGeoFencing(SmsCbMessage message, Uri uri, CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy)637     protected void performGeoFencing(SmsCbMessage message, Uri uri,
638             CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy) {
639 
640         logd(calculator.toString() + ", current action="
641                 + CbSendMessageCalculator.getActionString(calculator.getAction()));
642 
643         if (calculator.getAction() == SEND_MESSAGE_ACTION_SENT) {
644             if (VDBG) {
645                 logd("performGeoFencing:" + getMessageString(message));
646             }
647             return;
648         }
649 
650         if (uri != null) {
651             ContentValues cv = new ContentValues();
652             cv.put(CellBroadcasts.LOCATION_CHECK_TIME, System.currentTimeMillis());
653             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
654                     CellBroadcasts._ID + "=?", new String[] {uri.getLastPathSegment()});
655         }
656 
657 
658         calculator.addCoordinate(location, accuracy);
659 
660         if (VDBG) {
661             logd("Device location new action = "
662                     + CbSendMessageCalculator.getActionString(calculator.getAction())
663                     + ", threshold = " + calculator.getThreshold()
664                     + ", geos=" + CbGeoUtils.encodeGeometriesToString(calculator.getFences())
665                     + ". " + getMessageString(message));
666         }
667 
668         if (calculator.getAction() == SEND_MESSAGE_ACTION_SEND) {
669             broadcastGeofenceMessage(message, uri, slotIndex, calculator);
670             return;
671         }
672     }
673 
geofenceMessageNotRequired(SmsCbMessage msg)674     protected void geofenceMessageNotRequired(SmsCbMessage msg) {
675         if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
676             CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg);
677         } else if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
678             CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg);
679         }
680         sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
681     }
682 
683     /**
684      * send message that broadcast is not required due to geo-fencing check
685      */
686     @VisibleForTesting
sendMessageBroadcastNotRequired()687     public void sendMessageBroadcastNotRequired() {
688         sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
689     }
690 
691     /**
692      * Requests a stream of updates for {@code maximumWaitTimeSec} seconds.
693      * @param callback the callback used to communicate back to the caller
694      * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
695      * within the maximum wait time, {@code callback#onLocationUnavailable()} will be called.
696      */
requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec)697     protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
698         mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
699     }
700 
701     /**
702      * Get the subscription id from the phone id.
703      *
704      * @param phoneId the phone id (i.e. logical SIM slot index)
705      *
706      * @return The associated subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
707      * if the phone does not have an active sub or when {@code phoneId} is not valid.
708      */
getSubIdForPhone(Context context, int phoneId)709     public static int getSubIdForPhone(Context context, int phoneId) {
710         if (SdkLevel.isAtLeastU()) {
711             return SubscriptionManager.getSubscriptionId(phoneId);
712         } else {
713             SubscriptionManager subMan = context.getSystemService(SubscriptionManager.class);
714             int[] subIds = subMan.getSubscriptionIds(phoneId);
715             if (subIds != null) {
716                 return subIds[0];
717             } else {
718                 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
719             }
720         }
721     }
722 
723     /**
724      * Put the phone ID and sub ID into an intent as extras.
725      */
putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId)726     public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
727         int subId = getSubIdForPhone(context, phoneId);
728         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
729             intent.putExtra("subscription", subId);
730             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
731         }
732         intent.putExtra("phone", phoneId);
733         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
734     }
735 
736     /**
737      * Call when dealing with messages that are checked against a geofence.
738      *
739      * @param message the message being broadcast
740      * @param messageUri the message uri
741      * @param slotIndex the slot index
742      * @param calculator the messages send message calculator
743      */
broadcastGeofenceMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex, CbSendMessageCalculator calculator)744     protected void broadcastGeofenceMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
745             int slotIndex, CbSendMessageCalculator calculator) {
746         // Check that the message wasn't already SENT
747         if (calculator.getAction() == CbSendMessageCalculator.SEND_MESSAGE_ACTION_SENT) {
748             return;
749         }
750 
751         if (VDBG) {
752             logd("broadcastGeofenceMessage: mark as sent. " + getMessageString(message));
753         }
754         // Mark the message as SENT
755         calculator.markAsSent();
756 
757         // Broadcast the message
758         broadcastMessage(message, messageUri, slotIndex);
759     }
760 
761     /**
762      * Broadcast the {@code message} to the applications.
763      * @param message a message need to broadcast
764      * @param messageUri message's uri
765      */
766     // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
767     @SuppressLint("NewApi")
broadcastMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex)768     protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
769             int slotIndex) {
770         String msg;
771         Intent intent;
772         if (VDBG) {
773             logd("broadcastMessage: " + getMessageString(message));
774         }
775 
776         if (message.isEmergencyMessage()) {
777             msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
778             log(msg);
779             mLocalLog.log(msg);
780             intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
781             //Emergency alerts need to be delivered with high priority
782             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
783 
784             intent.putExtra(EXTRA_MESSAGE, message);
785             putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
786 
787             if (IS_DEBUGGABLE) {
788                 // Send additional broadcast intent to the specified package. This is only for sl4a
789                 // automation tests.
790                 String[] testPkgs = mContext.getResources().getStringArray(
791                         R.array.test_cell_broadcast_receiver_packages);
792                 if (testPkgs != null) {
793                     Intent additionalIntent = new Intent(intent);
794                     for (String pkg : testPkgs) {
795                         additionalIntent.setPackage(pkg);
796                         mLocalLog.log("intent=" + intent + " package=" + pkg);
797                         mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
798                                 additionalIntent, null, (Bundle) null, null, getHandler(),
799                                 Activity.RESULT_OK, null, null);
800 
801                     }
802                 }
803             }
804 
805             List<String> pkgs = new ArrayList<>();
806             pkgs.add(getDefaultCBRPackageName(mContext, intent));
807             pkgs.addAll(Arrays.asList(mContext.getResources().getStringArray(
808                     R.array.additional_cell_broadcast_receiver_packages)));
809             if (pkgs != null) {
810                 mReceiverCount.addAndGet(pkgs.size());
811                 CellBroadcastServiceMetrics.getInstance().getFeatureMetrics(mContext)
812                         .onChangedAdditionalCbrPackage(pkgs.size() > 1 ? true : false);
813                 for (String pkg : pkgs) {
814                     // Explicitly send the intent to all the configured cell broadcast receivers.
815                     intent.setPackage(pkg);
816                     mLocalLog.log("intent=" + intent + " package=" + pkg);
817                     mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
818                             intent, null, (Bundle) null, mOrderedBroadcastReceiver, getHandler(),
819                             Activity.RESULT_OK, null, null);
820                 }
821             }
822         } else {
823             msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
824             log(msg);
825             mLocalLog.log(msg);
826             // Send implicit intent since there are various 3rd party carrier apps listen to
827             // this intent.
828 
829             mReceiverCount.incrementAndGet();
830             CellBroadcastIntents.sendSmsCbReceivedBroadcast(
831                     mContext, UserHandle.ALL, message, mOrderedBroadcastReceiver, getHandler(),
832                     Activity.RESULT_OK, slotIndex);
833         }
834 
835         if (messageUri != null) {
836             ContentValues cv = new ContentValues();
837             cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
838             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
839                     CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
840         }
841 
842         CellBroadcastServiceMetrics.getInstance().logFeatureChangedAsNeeded(mContext);
843     }
844 
845     /**
846      * Checks if the app's path starts with CB_APEX_PATH
847      */
isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo)848     private static boolean isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo) {
849         return appInfo.sourceDir.startsWith(CB_APEX_PATH) ||
850                appInfo.sourceDir.contains(CB_APP_PLATFORM_NAME);
851     }
852 
853     /**
854      * Find the name of the default CBR package. The criteria is that it belongs to CB apex and
855      * handles the given intent.
856      */
getDefaultCBRPackageName(Context context, Intent intent)857     static String getDefaultCBRPackageName(Context context, Intent intent) {
858         PackageManager packageManager = context.getPackageManager();
859         List<ResolveInfo> cbrPackages = packageManager.queryBroadcastReceivers(intent, 0);
860 
861         // remove apps that don't live in the CellBroadcast apex
862         cbrPackages.removeIf(info ->
863                 !isAppInCBApexOrAlternativeApp(info.activityInfo.applicationInfo));
864 
865         if (cbrPackages.isEmpty()) {
866             CellBroadcastServiceMetrics.getInstance().logModuleError(
867                     ERRSRC_CBS, ERRTYPE_NOTFOUND_DEFAULTCBRPKGS);
868             Log.e(TAG, "getCBRPackageNames: no package found");
869             return null;
870         }
871 
872         if (cbrPackages.size() > 1) {
873             // multiple apps found, log an error but continue
874             CellBroadcastServiceMetrics.getInstance().logModuleError(
875                     ERRSRC_CBS, ERRTYPE_FOUND_MULTIPLECBRPKGS);
876             Log.e(TAG, "Found > 1 APK in CB apex that can resolve " + intent.getAction() + ": "
877                     + cbrPackages.stream()
878                     .map(info -> info.activityInfo.applicationInfo.packageName)
879                     .collect(Collectors.joining(", ")));
880         }
881 
882         // Assume the first ResolveInfo is the one we're looking for
883         ResolveInfo info = cbrPackages.get(0);
884         return info.activityInfo.applicationInfo.packageName;
885     }
886 
887     /**
888      * Get the device resource based on SIM
889      *
890      * @param subId Subscription index
891      *
892      * @return The resource
893      */
getResources(int subId)894     public @NonNull Resources getResources(int subId) {
895         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
896                 || !SubscriptionManager.isValidSubscriptionId(subId)) {
897             return mContext.getResources();
898         }
899 
900         if (mResourcesCache.containsKey(subId)) {
901             return mResourcesCache.get(subId);
902         }
903 
904         return SubscriptionManager.getResourcesForSubId(mContext, subId);
905     }
906 
907     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)908     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
909         pw.println("CellBroadcastHandler:");
910         mLocalLog.dump(fd, pw, args);
911         pw.flush();
912         try {
913             super.dump(fd, pw, args);
914         } catch (NullPointerException e) {
915             // StateMachine.dump() throws a NPE if there is no current state in the stack. Since
916             // StateMachine is defined in the framework and CBS is updated through mailine, we
917             // catch the NPE here as well as fixing the exception in the framework.
918             pw.println("StateMachine: no state info");
919         }
920     }
921 
922     /** The callback interface of a location request. */
923     public interface LocationUpdateCallback {
924         /**
925          * Called when the location update is available.
926          * @param location a location in (latitude, longitude) format.
927          * @param accuracy the accuracy of the location given from location manager.  Given in
928          *                 meters.
929          */
onLocationUpdate(@onNull LatLng location, float accuracy)930         void onLocationUpdate(@NonNull LatLng location, float accuracy);
931 
932         /**
933          * This is called in the following scenarios:
934          *   1. The max time limit of the LocationRequester was reached, and consequently,
935          *      no more location updates will be sent.
936          *   2. The service does not have permission to request a location update.
937          *   3. The LocationRequester was explicitly cancelled.
938          */
onLocationUnavailable()939         void onLocationUnavailable();
940 
941         /**
942          * Returns true if all messages are handled.
943          */
areAllMessagesHandled()944         boolean areAllMessagesHandled();
945     }
946 
947     private static final class LocationRequester {
948         /**
949          * Fused location provider, which means GPS plus network based providers (cell, wifi, etc..)
950          */
951         //TODO: Should make LocationManager.FUSED_PROVIDER system API in S.
952         private static final String FUSED_PROVIDER = "fused";
953 
954         /**
955          * The interval in which location requests will be sent.
956          * see more: <code>LocationRequest#setInterval</code>
957          */
958         private static final long LOCATION_REQUEST_INTERVAL_MILLIS = 1000;
959 
960         private final LocationManager mLocationManager;
961         private final List<LocationUpdateCallback> mCallbacks;
962         private final HandlerHelper mHandlerHelper;
963         private final Context mContext;
964         private final LocationListener mLocationListener;
965 
966         private boolean mLocationUpdateInProgress;
967         private final Runnable mLocationUnavailable;
968         private final String mDebugTag;
969 
LocationRequester(Context context, LocationManager locationManager, HandlerHelper handlerHelper, String debugTag)970         LocationRequester(Context context, LocationManager locationManager,
971                 HandlerHelper handlerHelper, String debugTag) {
972             mLocationManager = locationManager;
973             mCallbacks = new ArrayList<>();
974             mContext = context;
975             mHandlerHelper = handlerHelper;
976             mLocationUpdateInProgress = false;
977             mLocationUnavailable = this::onLocationUnavailable;
978 
979             // Location request did not cancel itself when using this::onLocationListener
980             mLocationListener = this::onLocationUpdate;
981             mDebugTag = debugTag;
982         }
983 
984         /**
985          * Requests a stream of updates for {@code maximumWaitTimeSec} seconds.
986          * @param callback the callback used to communicate back to the caller
987          * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
988          *                           updated within the maximum wait time,
989          *                           {@code callback#onLocationUnavailable()} will be called.
990          */
requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)991         void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
992                 int maximumWaitTimeSec) {
993             mHandlerHelper.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeSec));
994         }
995 
onLocationUpdate(@onNull Location location)996         private void onLocationUpdate(@NonNull Location location) {
997             if (location == null) {
998                 /* onLocationUpdate should neverreceive a null location, but, covering all of our
999                    bases here. */
1000                 Log.wtf(mDebugTag, "Location is never supposed to be null");
1001                 return;
1002             }
1003 
1004             LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
1005             float accuracy = location.getAccuracy();
1006             if (DBG) {
1007                 Log.d(mDebugTag, "onLocationUpdate: received location update");
1008             }
1009 
1010             boolean canCancel = true;
1011             for (LocationUpdateCallback callback : mCallbacks) {
1012                 callback.onLocationUpdate(latLng, accuracy);
1013                 canCancel = canCancel && callback.areAllMessagesHandled();
1014             }
1015             if (canCancel) {
1016                 Log.d(mDebugTag, "call cancel because all messages are handled.");
1017                 cancel();
1018             }
1019         }
1020 
onLocationUnavailable()1021         private void onLocationUnavailable() {
1022             Log.d(mDebugTag, "onLocationUnavailable: called");
1023             locationRequesterCycleComplete();
1024         }
1025 
1026         /* This should only be called if all of the messages are handled. */
cancel()1027         public void cancel() {
1028             if (mLocationUpdateInProgress) {
1029                 Log.d(mDebugTag, "cancel: location update in progress");
1030                 mHandlerHelper.removeCallbacks(mLocationUnavailable);
1031                 locationRequesterCycleComplete();
1032             } else {
1033                 Log.d(mDebugTag, "cancel: location update NOT in progress");
1034             }
1035         }
1036 
locationRequesterCycleComplete()1037         private void locationRequesterCycleComplete() {
1038             try {
1039                 for (LocationUpdateCallback callback : mCallbacks) {
1040                     callback.onLocationUnavailable();
1041                 }
1042             } finally {
1043                 mLocationManager.removeUpdates(mLocationListener);
1044                 // Reset the state of location requester for the next request
1045                 mCallbacks.clear();
1046                 mLocationUpdateInProgress = false;
1047             }
1048         }
1049 
requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)1050         private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
1051                 int maximumWaitTimeS) {
1052             if (DBG) Log.d(mDebugTag, "requestLocationUpdate");
1053             if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) {
1054                 if (DBG) {
1055                     Log.e(mDebugTag,
1056                             "Can't request location update because of no location permission");
1057                 }
1058                 callback.onLocationUnavailable();
1059                 return;
1060             }
1061 
1062             if (!mLocationUpdateInProgress) {
1063                 try {
1064                     // If the user does not turn on location, immediately report location
1065                     // unavailable.
1066                     if (!mLocationManager.isLocationEnabled()) {
1067                         Log.d(mDebugTag, "Location is turned off.");
1068                         callback.onLocationUnavailable();
1069                         return;
1070                     }
1071 
1072                     /* We will continue to send updates until the location timeout is reached. The
1073                     location timeout case is handled through onLocationUnavailable. */
1074                     LocationRequest request = LocationRequest.create()
1075                             .setProvider(FUSED_PROVIDER)
1076                             .setQuality(LocationRequest.ACCURACY_FINE)
1077                             .setInterval(LOCATION_REQUEST_INTERVAL_MILLIS);
1078                     if (DBG) {
1079                         Log.d(mDebugTag, "Location request=" + request);
1080                     }
1081                     mLocationManager.requestLocationUpdates(request,
1082                             new HandlerExecutor(mHandlerHelper.getHandler()),
1083                             mLocationListener);
1084 
1085                     // TODO: Remove the following workaround in S. We need to enforce the timeout
1086                     // before location manager adds the support for timeout value which is less
1087                     // than 30 seconds. After that we can rely on location manager's timeout
1088                     // mechanism.
1089                     mHandlerHelper.postDelayed(mLocationUnavailable,
1090                             TimeUnit.SECONDS.toMillis(maximumWaitTimeS));
1091                 } catch (IllegalArgumentException e) {
1092                     Log.e(mDebugTag, "Cannot get current location. e=" + e);
1093                     callback.onLocationUnavailable();
1094                     return;
1095                 }
1096                 mLocationUpdateInProgress = true;
1097             }
1098             mCallbacks.add(callback);
1099         }
1100 
hasPermission(String permission)1101         private boolean hasPermission(String permission) {
1102             // TODO: remove the check. This will always return true because cell broadcast service
1103             // is running under the UID Process.NETWORK_STACK_UID, which is below 10000. It will be
1104             // automatically granted with all runtime permissions.
1105             return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
1106                     == PackageManager.PERMISSION_GRANTED;
1107         }
1108     }
1109 
1110     /**
1111      * Provides message identifiers that are helpful when logging messages.
1112      *
1113      * @param message the message to log
1114      * @return a helpful message
1115      */
getMessageString(SmsCbMessage message)1116     protected static String getMessageString(SmsCbMessage message) {
1117         return "msg=("
1118                 + message.getServiceCategory() + ","
1119                 + message.getSerialNumber() + ")";
1120     }
1121 
1122 
1123     /**
1124      * Wraps the {@code Handler} in order to mock the methods.
1125      */
1126     @VisibleForTesting
1127     public static class HandlerHelper {
1128 
1129         private final Handler mHandler;
1130 
HandlerHelper(@onNull final Handler handler)1131         public HandlerHelper(@NonNull final Handler handler) {
1132             mHandler = handler;
1133         }
1134 
1135         /**
1136          * Posts {@code r} to {@code handler} with a delay of {@code delayMillis}
1137          *
1138          * @param r the runnable callback
1139          * @param delayMillis the number of milliseconds to delay
1140          */
postDelayed(Runnable r, long delayMillis)1141         public void postDelayed(Runnable r, long delayMillis) {
1142             mHandler.postDelayed(r, delayMillis);
1143         }
1144 
1145         /**
1146          * Posts {@code r} to the underlying handler
1147          *
1148          * @param r the runnable callback
1149          */
post(Runnable r)1150         public void post(Runnable r) {
1151             mHandler.post(r);
1152         }
1153 
1154         /**
1155          * Gets the underlying handler
1156          * @return the handler
1157          */
getHandler()1158         public Handler getHandler() {
1159             return mHandler;
1160         }
1161 
1162         /**
1163          * Remove any pending posts of Runnable r that are in the message queue.
1164          */
removeCallbacks(Runnable r)1165         public void removeCallbacks(Runnable r) {
1166             mHandler.removeCallbacks(r);
1167         }
1168     }
1169 }
1170