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.phone;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.RemoteException;
23 import android.telephony.NumberVerificationCallback;
24 import android.telephony.PhoneNumberRange;
25 import android.telephony.ServiceState;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.internal.telephony.Call;
30 import com.android.internal.telephony.INumberVerificationCallback;
31 import com.android.internal.telephony.Phone;
32 import com.android.internal.telephony.PhoneFactory;
33 
34 /**
35  * Singleton for managing the call based number verification requests.
36  */
37 public class NumberVerificationManager {
38     interface PhoneListSupplier {
getPhones()39         Phone[] getPhones();
40     }
41 
42     private static NumberVerificationManager sInstance;
43     private static String sAuthorizedPackageOverride;
44 
45     private PhoneNumberRange mCurrentRange;
46     private INumberVerificationCallback mCallback;
47     private final PhoneListSupplier mPhoneListSupplier;
48 
49     // We don't really care what thread this runs on, since it's only used for a non-blocking
50     // timeout.
51     private Handler mHandler;
52 
NumberVerificationManager(PhoneListSupplier phoneListSupplier)53     NumberVerificationManager(PhoneListSupplier phoneListSupplier) {
54         mPhoneListSupplier = phoneListSupplier;
55         mHandler = new Handler(Looper.getMainLooper());
56     }
57 
NumberVerificationManager()58     private NumberVerificationManager() {
59         this(PhoneFactory::getPhones);
60     }
61 
62     /**
63      * Check whether the incoming call matches one of the active filters. If so, call the callback
64      * that says that the number has been successfully verified.
65      * @param number A phone number
66      * @return true if the number matches, false otherwise
67      */
checkIncomingCall(String number)68     public synchronized boolean checkIncomingCall(String number) {
69         if (mCurrentRange == null || mCallback == null) {
70             return false;
71         }
72 
73         if (mCurrentRange.matches(number)) {
74             mCurrentRange = null;
75             try {
76                 mCallback.onCallReceived(number);
77                 return true;
78             } catch (RemoteException e) {
79                 Log.w(NumberVerificationManager.class.getSimpleName(),
80                         "Remote exception calling verification complete callback");
81                 // Intercept the call even if there was a remote exception -- it's still going to be
82                 // a strange call from a robot number
83                 return true;
84             } finally {
85                 mCallback = null;
86             }
87         }
88         return false;
89     }
90 
requestVerification(PhoneNumberRange numberRange, INumberVerificationCallback callback, long timeoutMillis)91     synchronized void requestVerification(PhoneNumberRange numberRange,
92             INumberVerificationCallback callback, long timeoutMillis) {
93         if (!checkNumberVerificationFeasibility(callback)) {
94             return;
95         }
96 
97         mCallback = callback;
98         mCurrentRange = numberRange;
99 
100         mHandler.postDelayed(() -> {
101             synchronized (NumberVerificationManager.this) {
102                 // Check whether the verification finished already -- if so, don't call anything.
103                 if (mCallback != null && mCurrentRange != null) {
104                     try {
105                         mCallback.onVerificationFailed(NumberVerificationCallback.REASON_TIMED_OUT);
106                     } catch (RemoteException e) {
107                         Log.w(NumberVerificationManager.class.getSimpleName(),
108                                 "Remote exception calling verification error callback");
109                     }
110                     mCallback = null;
111                     mCurrentRange = null;
112                 }
113             }
114         }, timeoutMillis);
115     }
116 
checkNumberVerificationFeasibility(INumberVerificationCallback callback)117     private boolean checkNumberVerificationFeasibility(INumberVerificationCallback callback) {
118         int reason = -1;
119         try {
120             if (mCurrentRange != null || mCallback != null) {
121                 reason = NumberVerificationCallback.REASON_CONCURRENT_REQUESTS;
122                 return false;
123             }
124             boolean doesAnyPhoneHaveRoomForIncomingCall = false;
125             boolean isAnyPhoneVoiceRegistered = false;
126             for (Phone phone : mPhoneListSupplier.getPhones()) {
127                 // abort if any phone is in an emergency call or ecbm
128                 if (phone.isInEmergencyCall()) {
129                     reason = NumberVerificationCallback.REASON_IN_EMERGENCY_CALL;
130                     return false;
131                 }
132                 if (phone.isInEcm()) {
133                     reason = NumberVerificationCallback.REASON_IN_ECBM;
134                     return false;
135                 }
136 
137                 // make sure at least one phone is registered for voice
138                 if (phone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
139                     isAnyPhoneVoiceRegistered = true;
140                 }
141                 // make sure at least one phone has room for an incoming call.
142                 if (phone.getRingingCall().getState() == Call.State.IDLE
143                         && (phone.getForegroundCall().getState() == Call.State.IDLE
144                         || phone.getBackgroundCall().getState() == Call.State.IDLE)) {
145                     doesAnyPhoneHaveRoomForIncomingCall = true;
146                 }
147             }
148             if (!isAnyPhoneVoiceRegistered) {
149                 reason = NumberVerificationCallback.REASON_NETWORK_NOT_AVAILABLE;
150                 return false;
151             }
152             if (!doesAnyPhoneHaveRoomForIncomingCall) {
153                 reason = NumberVerificationCallback.REASON_TOO_MANY_CALLS;
154                 return false;
155             }
156         } finally {
157             if (reason >= 0) {
158                 try {
159                     callback.onVerificationFailed(reason);
160                 } catch (RemoteException e) {
161                     Log.w(NumberVerificationManager.class.getSimpleName(),
162                             "Remote exception calling verification error callback");
163                 }
164             }
165         }
166         return true;
167     }
168 
169     /**
170      * Get the singleton instance of NumberVerificationManager.
171      * @return
172      */
getInstance()173     public static NumberVerificationManager getInstance() {
174         if (sInstance == null) {
175             sInstance = new NumberVerificationManager();
176         }
177         return sInstance;
178     }
179 
getAuthorizedPackage(Context context)180     static String getAuthorizedPackage(Context context) {
181         return !TextUtils.isEmpty(sAuthorizedPackageOverride) ? sAuthorizedPackageOverride :
182                 context.getResources().getString(R.string.platform_number_verification_package);
183     }
184 
185     /**
186      * Used by shell commands to override the authorized package name for number verification.
187      * @param pkgName
188      */
overrideAuthorizedPackage(String pkgName)189     static void overrideAuthorizedPackage(String pkgName) {
190         sAuthorizedPackageOverride = pkgName;
191     }
192 }
193