• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                     && message.getSlotIndex() != messageToCheck.getSlotIndex()) {
568                 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
569                     log("Duplicate message detected from different slot and subId " + message);
570                     return true;
571                 }
572                 if (VDBG) log("Not from the same slot.");
573             } else {
574                 // Check serial number if message is from the same carrier.
575                 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
576                     if (VDBG) log("Serial number does not match.");
577                     // Not a dup. Check next one.
578                     continue;
579                 }
580 
581                 // ETWS primary / secondary should be treated differently.
582                 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
583                         && message.getEtwsWarningInfo().isPrimary()
584                         != messageToCheck.getEtwsWarningInfo().isPrimary()) {
585                     if (VDBG) log("ETWS primary/secondary does not match.");
586                     // Not a dup. Check next one.
587                     continue;
588                 }
589 
590                 // Check if the message category is different.
591                 if (compareServiceCategory
592                         && message.getServiceCategory() != messageToCheck.getServiceCategory()
593                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
594                                 message.getServiceCategory()), messageToCheck.getServiceCategory())
595                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
596                                 messageToCheck.getServiceCategory()),
597                         message.getServiceCategory())) {
598                     if (VDBG) log("Category does not match.");
599                     // Not a dup. Check next one.
600                     continue;
601                 }
602 
603                 // Check if the message location is different. Note this is only applicable to
604                 // 3GPP format cell broadcast messages.
605                 if (message.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP
606                         && messageToCheck.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP
607                         && !isSameLocation(message, messageToCheck)) {
608                     if (VDBG) log("Location does not match.");
609                     // Not a dup. Check next one.
610                     continue;
611                 }
612 
613                 // Compare message body if needed.
614                 if (!compareMessageBody || TextUtils.equals(
615                         message.getMessageBody(), messageToCheck.getMessageBody())) {
616                     log("Duplicate message detected. " + message);
617                     return true;
618                 } else {
619                     if (VDBG) log("Body does not match.");
620                 }
621             }
622         }
623 
624         log("Not a duplicate message. " + message);
625         return false;
626     }
627 
628     /**
629      * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
630      * {@code location} is inside the {@code broadcastArea}.
631      * @param message the message need to geo-fencing check
632      * @param uri the message's uri
633      * @param calculator the message calculator
634      * @param location current location
635      * @param slotIndex the index of the slot
636      * @param accuracy the accuracy of the coordinate given in meters
637      */
performGeoFencing(SmsCbMessage message, Uri uri, CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy)638     protected void performGeoFencing(SmsCbMessage message, Uri uri,
639             CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy) {
640 
641         logd(calculator.toString() + ", current action="
642                 + CbSendMessageCalculator.getActionString(calculator.getAction()));
643 
644         if (calculator.getAction() == SEND_MESSAGE_ACTION_SENT) {
645             if (VDBG) {
646                 logd("performGeoFencing:" + getMessageString(message));
647             }
648             return;
649         }
650 
651         if (uri != null) {
652             ContentValues cv = new ContentValues();
653             cv.put(CellBroadcasts.LOCATION_CHECK_TIME, System.currentTimeMillis());
654             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
655                     CellBroadcasts._ID + "=?", new String[] {uri.getLastPathSegment()});
656         }
657 
658 
659         calculator.addCoordinate(location, accuracy);
660 
661         if (VDBG) {
662             logd("Device location new action = "
663                     + CbSendMessageCalculator.getActionString(calculator.getAction())
664                     + ", threshold = " + calculator.getThreshold()
665                     + ", geos=" + CbGeoUtils.encodeGeometriesToString(calculator.getFences())
666                     + ". " + getMessageString(message));
667         }
668 
669         if (calculator.getAction() == SEND_MESSAGE_ACTION_SEND) {
670             broadcastGeofenceMessage(message, uri, slotIndex, calculator);
671             return;
672         }
673     }
674 
geofenceMessageNotRequired(SmsCbMessage msg)675     protected void geofenceMessageNotRequired(SmsCbMessage msg) {
676         if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
677             CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg);
678         } else if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
679             CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg);
680         }
681         sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
682     }
683 
684     /**
685      * send message that broadcast is not required due to geo-fencing check
686      */
687     @VisibleForTesting
sendMessageBroadcastNotRequired()688     public void sendMessageBroadcastNotRequired() {
689         sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
690     }
691 
692     /**
693      * Requests a stream of updates for {@code maximumWaitTimeSec} seconds.
694      * @param callback the callback used to communicate back to the caller
695      * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
696      * within the maximum wait time, {@code callback#onLocationUnavailable()} will be called.
697      */
requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec)698     protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
699         mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
700     }
701 
702     /**
703      * Get the subscription id from the phone id.
704      *
705      * @param phoneId the phone id (i.e. logical SIM slot index)
706      *
707      * @return The associated subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
708      * if the phone does not have an active sub or when {@code phoneId} is not valid.
709      */
getSubIdForPhone(Context context, int phoneId)710     public static int getSubIdForPhone(Context context, int phoneId) {
711         if (SdkLevel.isAtLeastU()) {
712             return SubscriptionManager.getSubscriptionId(phoneId);
713         } else {
714             SubscriptionManager subMan = context.getSystemService(SubscriptionManager.class);
715             int[] subIds = subMan.getSubscriptionIds(phoneId);
716             if (subIds != null) {
717                 return subIds[0];
718             } else {
719                 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
720             }
721         }
722     }
723 
724     /**
725      * Put the phone ID and sub ID into an intent as extras.
726      */
putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId)727     public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
728         int subId = getSubIdForPhone(context, phoneId);
729         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
730             intent.putExtra("subscription", subId);
731             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
732         }
733         intent.putExtra("phone", phoneId);
734         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
735     }
736 
737     /**
738      * Call when dealing with messages that are checked against a geofence.
739      *
740      * @param message the message being broadcast
741      * @param messageUri the message uri
742      * @param slotIndex the slot index
743      * @param calculator the messages send message calculator
744      */
broadcastGeofenceMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex, CbSendMessageCalculator calculator)745     protected void broadcastGeofenceMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
746             int slotIndex, CbSendMessageCalculator calculator) {
747         // Check that the message wasn't already SENT
748         if (calculator.getAction() == CbSendMessageCalculator.SEND_MESSAGE_ACTION_SENT) {
749             return;
750         }
751 
752         if (VDBG) {
753             logd("broadcastGeofenceMessage: mark as sent. " + getMessageString(message));
754         }
755         // Mark the message as SENT
756         calculator.markAsSent();
757 
758         // Broadcast the message
759         broadcastMessage(message, messageUri, slotIndex);
760     }
761 
762     /**
763      * Broadcast the {@code message} to the applications.
764      * @param message a message need to broadcast
765      * @param messageUri message's uri
766      */
767     // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
768     @SuppressLint("NewApi")
broadcastMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex)769     protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
770             int slotIndex) {
771         String msg;
772         Intent intent;
773         if (VDBG) {
774             logd("broadcastMessage: " + getMessageString(message));
775         }
776 
777         if (message.isEmergencyMessage()) {
778             msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
779             log(msg);
780             mLocalLog.log(msg);
781             intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
782             //Emergency alerts need to be delivered with high priority
783             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
784 
785             intent.putExtra(EXTRA_MESSAGE, message);
786             putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
787 
788             if (IS_DEBUGGABLE) {
789                 // Send additional broadcast intent to the specified package. This is only for sl4a
790                 // automation tests.
791                 String[] testPkgs = mContext.getResources().getStringArray(
792                         R.array.test_cell_broadcast_receiver_packages);
793                 if (testPkgs != null) {
794                     Intent additionalIntent = new Intent(intent);
795                     for (String pkg : testPkgs) {
796                         additionalIntent.setPackage(pkg);
797                         mLocalLog.log("intent=" + intent + " package=" + pkg);
798                         mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
799                                 additionalIntent, null, (Bundle) null, null, getHandler(),
800                                 Activity.RESULT_OK, null, null);
801 
802                     }
803                 }
804             }
805 
806             List<String> pkgs = new ArrayList<>();
807             pkgs.add(getDefaultCBRPackageName(mContext, intent));
808             pkgs.addAll(Arrays.asList(mContext.getResources().getStringArray(
809                     R.array.additional_cell_broadcast_receiver_packages)));
810             if (pkgs != null) {
811                 mReceiverCount.addAndGet(pkgs.size());
812                 CellBroadcastServiceMetrics.getInstance().getFeatureMetrics(mContext)
813                         .onChangedAdditionalCbrPackage(pkgs.size() > 1 ? true : false);
814                 for (String pkg : pkgs) {
815                     // Explicitly send the intent to all the configured cell broadcast receivers.
816                     intent.setPackage(pkg);
817                     mLocalLog.log("intent=" + intent + " package=" + pkg);
818                     mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
819                             intent, null, (Bundle) null, mOrderedBroadcastReceiver, getHandler(),
820                             Activity.RESULT_OK, null, null);
821                 }
822             }
823         } else {
824             msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
825             log(msg);
826             mLocalLog.log(msg);
827             // Send implicit intent since there are various 3rd party carrier apps listen to
828             // this intent.
829 
830             mReceiverCount.incrementAndGet();
831             CellBroadcastIntents.sendSmsCbReceivedBroadcast(
832                     mContext, UserHandle.ALL, message, mOrderedBroadcastReceiver, getHandler(),
833                     Activity.RESULT_OK, slotIndex);
834         }
835 
836         if (messageUri != null) {
837             ContentValues cv = new ContentValues();
838             cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
839             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
840                     CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
841         }
842 
843         CellBroadcastServiceMetrics.getInstance().logFeatureChangedAsNeeded(mContext);
844     }
845 
846     /**
847      * Checks if the app's path starts with CB_APEX_PATH
848      */
isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo)849     private static boolean isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo) {
850         return appInfo.sourceDir.startsWith(CB_APEX_PATH) ||
851                appInfo.sourceDir.contains(CB_APP_PLATFORM_NAME);
852     }
853 
854     /**
855      * Find the name of the default CBR package. The criteria is that it belongs to CB apex and
856      * handles the given intent.
857      */
858     @VisibleForTesting
getDefaultCBRPackageName(Context context, Intent intent)859     public static String getDefaultCBRPackageName(Context context, Intent intent) {
860         PackageManager packageManager = context.getPackageManager();
861         List<ResolveInfo> cbrPackages = packageManager.queryBroadcastReceivers(intent,
862                 PackageManager.MATCH_SYSTEM_ONLY);
863 
864         // remove apps that don't live in the CellBroadcast apex
865         cbrPackages.removeIf(info ->
866                 !isAppInCBApexOrAlternativeApp(info.activityInfo.applicationInfo));
867 
868         if (cbrPackages.isEmpty()) {
869             CellBroadcastServiceMetrics.getInstance().logModuleError(
870                     ERRSRC_CBS, ERRTYPE_NOTFOUND_DEFAULTCBRPKGS);
871             Log.e(TAG, "getCBRPackageNames: no package found");
872             return null;
873         }
874 
875         if (cbrPackages.size() > 1) {
876             // multiple apps found, log an error but continue
877             CellBroadcastServiceMetrics.getInstance().logModuleError(
878                     ERRSRC_CBS, ERRTYPE_FOUND_MULTIPLECBRPKGS);
879             Log.e(TAG, "Found > 1 APK in CB apex that can resolve " + intent.getAction() + ": "
880                     + cbrPackages.stream()
881                     .map(info -> info.activityInfo.applicationInfo.packageName)
882                     .collect(Collectors.joining(", ")));
883         }
884 
885         // Assume the first ResolveInfo is the one we're looking for
886         ResolveInfo info = cbrPackages.get(0);
887         return info.activityInfo.applicationInfo.packageName;
888     }
889 
890     /**
891      * Get the device resource based on SIM
892      *
893      * @param subId Subscription index
894      *
895      * @return The resource
896      */
getResources(int subId)897     public @NonNull Resources getResources(int subId) {
898         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
899                 || !SubscriptionManager.isValidSubscriptionId(subId)) {
900             return mContext.getResources();
901         }
902 
903         if (mResourcesCache.containsKey(subId)) {
904             return mResourcesCache.get(subId);
905         }
906 
907         return SubscriptionManager.getResourcesForSubId(mContext, subId);
908     }
909 
910     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)911     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
912         pw.println("CellBroadcastHandler:");
913         mLocalLog.dump(fd, pw, args);
914         pw.flush();
915         try {
916             super.dump(fd, pw, args);
917         } catch (NullPointerException e) {
918             // StateMachine.dump() throws a NPE if there is no current state in the stack. Since
919             // StateMachine is defined in the framework and CBS is updated through mailine, we
920             // catch the NPE here as well as fixing the exception in the framework.
921             pw.println("StateMachine: no state info");
922         }
923     }
924 
925     /** The callback interface of a location request. */
926     public interface LocationUpdateCallback {
927         /**
928          * Called when the location update is available.
929          * @param location a location in (latitude, longitude) format.
930          * @param accuracy the accuracy of the location given from location manager.  Given in
931          *                 meters.
932          */
onLocationUpdate(@onNull LatLng location, float accuracy)933         void onLocationUpdate(@NonNull LatLng location, float accuracy);
934 
935         /**
936          * This is called in the following scenarios:
937          *   1. The max time limit of the LocationRequester was reached, and consequently,
938          *      no more location updates will be sent.
939          *   2. The service does not have permission to request a location update.
940          *   3. The LocationRequester was explicitly cancelled.
941          */
onLocationUnavailable()942         void onLocationUnavailable();
943 
944         /**
945          * Returns true if all messages are handled.
946          */
areAllMessagesHandled()947         boolean areAllMessagesHandled();
948     }
949 
950     private static final class LocationRequester {
951         /**
952          * Fused location provider, which means GPS plus network based providers (cell, wifi, etc..)
953          */
954         //TODO: Should make LocationManager.FUSED_PROVIDER system API in S.
955         private static final String FUSED_PROVIDER = "fused";
956 
957         /**
958          * The interval in which location requests will be sent.
959          * see more: <code>LocationRequest#setInterval</code>
960          */
961         private static final long LOCATION_REQUEST_INTERVAL_MILLIS = 1000;
962 
963         private final LocationManager mLocationManager;
964         private final List<LocationUpdateCallback> mCallbacks;
965         private final HandlerHelper mHandlerHelper;
966         private final Context mContext;
967         private final LocationListener mLocationListener;
968 
969         private boolean mLocationUpdateInProgress;
970         private final Runnable mLocationUnavailable;
971         private final String mDebugTag;
972 
LocationRequester(Context context, LocationManager locationManager, HandlerHelper handlerHelper, String debugTag)973         LocationRequester(Context context, LocationManager locationManager,
974                 HandlerHelper handlerHelper, String debugTag) {
975             mLocationManager = locationManager;
976             mCallbacks = new ArrayList<>();
977             mContext = context;
978             mHandlerHelper = handlerHelper;
979             mLocationUpdateInProgress = false;
980             mLocationUnavailable = this::onLocationUnavailable;
981 
982             // Location request did not cancel itself when using this::onLocationListener
983             mLocationListener = this::onLocationUpdate;
984             mDebugTag = debugTag;
985         }
986 
987         /**
988          * Requests a stream of updates for {@code maximumWaitTimeSec} seconds.
989          * @param callback the callback used to communicate back to the caller
990          * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
991          *                           updated within the maximum wait time,
992          *                           {@code callback#onLocationUnavailable()} will be called.
993          */
requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)994         void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
995                 int maximumWaitTimeSec) {
996             mHandlerHelper.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeSec));
997         }
998 
onLocationUpdate(@onNull Location location)999         private void onLocationUpdate(@NonNull Location location) {
1000             if (location == null) {
1001                 /* onLocationUpdate should neverreceive a null location, but, covering all of our
1002                    bases here. */
1003                 Log.wtf(mDebugTag, "Location is never supposed to be null");
1004                 return;
1005             }
1006 
1007             LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
1008             float accuracy = location.getAccuracy();
1009             if (DBG) {
1010                 Log.d(mDebugTag, "onLocationUpdate: received location update");
1011             }
1012 
1013             boolean canCancel = true;
1014             for (LocationUpdateCallback callback : mCallbacks) {
1015                 callback.onLocationUpdate(latLng, accuracy);
1016                 canCancel = canCancel && callback.areAllMessagesHandled();
1017             }
1018             if (canCancel) {
1019                 Log.d(mDebugTag, "call cancel because all messages are handled.");
1020                 cancel();
1021             }
1022         }
1023 
onLocationUnavailable()1024         private void onLocationUnavailable() {
1025             Log.d(mDebugTag, "onLocationUnavailable: called");
1026             locationRequesterCycleComplete();
1027         }
1028 
1029         /* This should only be called if all of the messages are handled. */
cancel()1030         public void cancel() {
1031             if (mLocationUpdateInProgress) {
1032                 Log.d(mDebugTag, "cancel: location update in progress");
1033                 mHandlerHelper.removeCallbacks(mLocationUnavailable);
1034                 locationRequesterCycleComplete();
1035             } else {
1036                 Log.d(mDebugTag, "cancel: location update NOT in progress");
1037             }
1038         }
1039 
locationRequesterCycleComplete()1040         private void locationRequesterCycleComplete() {
1041             try {
1042                 for (LocationUpdateCallback callback : mCallbacks) {
1043                     callback.onLocationUnavailable();
1044                 }
1045             } finally {
1046                 mLocationManager.removeUpdates(mLocationListener);
1047                 // Reset the state of location requester for the next request
1048                 mCallbacks.clear();
1049                 mLocationUpdateInProgress = false;
1050             }
1051         }
1052 
requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)1053         private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
1054                 int maximumWaitTimeS) {
1055             if (DBG) Log.d(mDebugTag, "requestLocationUpdate");
1056             if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) {
1057                 if (DBG) {
1058                     Log.e(mDebugTag,
1059                             "Can't request location update because of no location permission");
1060                 }
1061                 callback.onLocationUnavailable();
1062                 return;
1063             }
1064 
1065             if (!mLocationUpdateInProgress) {
1066                 try {
1067                     // If the user does not turn on location, immediately report location
1068                     // unavailable.
1069                     if (!mLocationManager.isLocationEnabled()) {
1070                         Log.d(mDebugTag, "Location is turned off.");
1071                         callback.onLocationUnavailable();
1072                         return;
1073                     }
1074 
1075                     /* We will continue to send updates until the location timeout is reached. The
1076                     location timeout case is handled through onLocationUnavailable. */
1077                     LocationRequest request = LocationRequest.create()
1078                             .setProvider(FUSED_PROVIDER)
1079                             .setQuality(LocationRequest.ACCURACY_FINE)
1080                             .setInterval(LOCATION_REQUEST_INTERVAL_MILLIS);
1081                     if (DBG) {
1082                         Log.d(mDebugTag, "Location request=" + request);
1083                     }
1084                     mLocationManager.requestLocationUpdates(request,
1085                             new HandlerExecutor(mHandlerHelper.getHandler()),
1086                             mLocationListener);
1087 
1088                     // TODO: Remove the following workaround in S. We need to enforce the timeout
1089                     // before location manager adds the support for timeout value which is less
1090                     // than 30 seconds. After that we can rely on location manager's timeout
1091                     // mechanism.
1092                     mHandlerHelper.postDelayed(mLocationUnavailable,
1093                             TimeUnit.SECONDS.toMillis(maximumWaitTimeS));
1094                 } catch (IllegalArgumentException e) {
1095                     Log.e(mDebugTag, "Cannot get current location. e=" + e);
1096                     callback.onLocationUnavailable();
1097                     return;
1098                 }
1099                 mLocationUpdateInProgress = true;
1100             }
1101             mCallbacks.add(callback);
1102         }
1103 
hasPermission(String permission)1104         private boolean hasPermission(String permission) {
1105             // TODO: remove the check. This will always return true because cell broadcast service
1106             // is running under the UID Process.NETWORK_STACK_UID, which is below 10000. It will be
1107             // automatically granted with all runtime permissions.
1108             return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
1109                     == PackageManager.PERMISSION_GRANTED;
1110         }
1111     }
1112 
1113     /**
1114      * Provides message identifiers that are helpful when logging messages.
1115      *
1116      * @param message the message to log
1117      * @return a helpful message
1118      */
getMessageString(SmsCbMessage message)1119     protected static String getMessageString(SmsCbMessage message) {
1120         return "msg=("
1121                 + message.getServiceCategory() + ","
1122                 + message.getSerialNumber() + ")";
1123     }
1124 
1125 
1126     /**
1127      * Wraps the {@code Handler} in order to mock the methods.
1128      */
1129     @VisibleForTesting
1130     public static class HandlerHelper {
1131 
1132         private final Handler mHandler;
1133 
HandlerHelper(@onNull final Handler handler)1134         public HandlerHelper(@NonNull final Handler handler) {
1135             mHandler = handler;
1136         }
1137 
1138         /**
1139          * Posts {@code r} to {@code handler} with a delay of {@code delayMillis}
1140          *
1141          * @param r the runnable callback
1142          * @param delayMillis the number of milliseconds to delay
1143          */
postDelayed(Runnable r, long delayMillis)1144         public void postDelayed(Runnable r, long delayMillis) {
1145             mHandler.postDelayed(r, delayMillis);
1146         }
1147 
1148         /**
1149          * Posts {@code r} to the underlying handler
1150          *
1151          * @param r the runnable callback
1152          */
post(Runnable r)1153         public void post(Runnable r) {
1154             mHandler.post(r);
1155         }
1156 
1157         /**
1158          * Gets the underlying handler
1159          * @return the handler
1160          */
getHandler()1161         public Handler getHandler() {
1162             return mHandler;
1163         }
1164 
1165         /**
1166          * Remove any pending posts of Runnable r that are in the message queue.
1167          */
removeCallbacks(Runnable r)1168         public void removeCallbacks(Runnable r) {
1169             mHandler.removeCallbacks(r);
1170         }
1171     }
1172 }
1173