1 /*
2  * Copyright (C) 2018 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.dialer.phonelookup.emergency;
18 
19 import android.content.Context;
20 import com.android.dialer.DialerPhoneNumber;
21 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
22 import com.android.dialer.inject.ApplicationContext;
23 import com.android.dialer.phonelookup.PhoneLookup;
24 import com.android.dialer.phonelookup.PhoneLookupInfo;
25 import com.android.dialer.phonelookup.PhoneLookupInfo.EmergencyInfo;
26 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
27 import com.google.common.collect.ImmutableMap;
28 import com.google.common.collect.ImmutableSet;
29 import com.google.common.util.concurrent.Futures;
30 import com.google.common.util.concurrent.ListenableFuture;
31 import com.google.common.util.concurrent.ListeningExecutorService;
32 import javax.inject.Inject;
33 
34 /**
35  * PhoneLookup implementation for checking if a number is an emergency number.
36  *
37  * <p>The check has to be done in a PhoneLookup as it involves detecting the user's location and
38  * obtaining SIM info, which are expensive operations. Doing it in the main thread will make the UI
39  * super janky.
40  */
41 public class EmergencyPhoneLookup implements PhoneLookup<EmergencyInfo> {
42 
43   private final Context appContext;
44   private final ListeningExecutorService backgroundExecutorService;
45 
46   @Inject
EmergencyPhoneLookup( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutorService)47   EmergencyPhoneLookup(
48       @ApplicationContext Context appContext,
49       @BackgroundExecutor ListeningExecutorService backgroundExecutorService) {
50     this.appContext = appContext;
51     this.backgroundExecutorService = backgroundExecutorService;
52   }
53 
54   @Override
lookup(DialerPhoneNumber dialerPhoneNumber)55   public ListenableFuture<EmergencyInfo> lookup(DialerPhoneNumber dialerPhoneNumber) {
56     return backgroundExecutorService.submit(
57         () ->
58             EmergencyInfo.newBuilder()
59                 .setIsEmergencyNumber(
60                     PhoneNumberHelper.isLocalEmergencyNumber(
61                         appContext, dialerPhoneNumber.getNormalizedNumber()))
62                 .build());
63   }
64 
65   @Override
isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers)66   public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
67     return Futures.immediateFuture(false);
68   }
69 
70   @Override
getMostRecentInfo( ImmutableMap<DialerPhoneNumber, EmergencyInfo> existingInfoMap)71   public ListenableFuture<ImmutableMap<DialerPhoneNumber, EmergencyInfo>> getMostRecentInfo(
72       ImmutableMap<DialerPhoneNumber, EmergencyInfo> existingInfoMap) {
73     // We can update EmergencyInfo for all numbers in the provided map, but the negative impact on
74     // performance is intolerable as checking a single number involves detecting the user's location
75     // and obtaining SIM info, which will take more than 100ms (see
76     // android.telephony.PhoneNumberUtils#isLocalEmergencyNumber(Context, int, String) for details).
77     //
78     // As emergency numbers won't change in a country, the only case we will miss is that
79     //   (1) a number is an emergency number in country A but not in country B,
80     //   (2) a user has an emergency call entry when they are in country A, and
81     //   (3) they travel from A to B,
82     // which is a rare event.
83     //
84     // We can update the implementation if telecom supports batch check in the future.
85     return Futures.immediateFuture(existingInfoMap);
86   }
87 
88   @Override
setSubMessage(PhoneLookupInfo.Builder destination, EmergencyInfo subMessage)89   public void setSubMessage(PhoneLookupInfo.Builder destination, EmergencyInfo subMessage) {
90     destination.setEmergencyInfo(subMessage);
91   }
92 
93   @Override
getSubMessage(PhoneLookupInfo phoneLookupInfo)94   public EmergencyInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) {
95     return phoneLookupInfo.getEmergencyInfo();
96   }
97 
98   @Override
onSuccessfulBulkUpdate()99   public ListenableFuture<Void> onSuccessfulBulkUpdate() {
100     return Futures.immediateFuture(null);
101   }
102 
103   @Override
registerContentObservers()104   public void registerContentObservers() {
105     // No content observer to register.
106   }
107 
108   @Override
unregisterContentObservers()109   public void unregisterContentObservers() {
110     // Nothing to be done as no content observer is registered.
111   }
112 
113   @Override
clearData()114   public ListenableFuture<Void> clearData() {
115     return Futures.immediateFuture(null);
116   }
117 
118   @Override
getLoggingName()119   public String getLoggingName() {
120     return "EmergencyPhoneLookup";
121   }
122 }
123