1 /*
2  * Copyright (C) 2017 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 android.telephony;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.annotation.Nullable;
22 import android.os.Binder;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.Messenger;
29 import android.os.Parcelable;
30 import android.os.RemoteException;
31 import android.util.SparseArray;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.internal.telephony.ITelephony;
35 import com.android.telephony.Rlog;
36 
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.concurrent.Executor;
41 
42 /**
43  * Manages the radio access network scan requests and callbacks.
44  */
45 public final class TelephonyScanManager {
46 
47     private static final String TAG = "TelephonyScanManager";
48 
49     /** @hide */
50     public static final String SCAN_RESULT_KEY = "scanResult";
51 
52     /** @hide */
53     public static final int CALLBACK_SCAN_RESULTS = 1;
54     /** @hide */
55     public static final int CALLBACK_SCAN_ERROR = 2;
56     /** @hide */
57     public static final int CALLBACK_SCAN_COMPLETE = 3;
58     /** @hide */
59     public static final int CALLBACK_RESTRICTED_SCAN_RESULTS = 4;
60     /** @hide */
61     public static final int CALLBACK_TELEPHONY_DIED = 5;
62 
63     /** @hide */
64     public static final int INVALID_SCAN_ID = -1;
65 
66     /**
67      * The caller of
68      * {@link
69      * TelephonyManager#requestNetworkScan(NetworkScanRequest, Executor, NetworkScanCallback)}
70      * should implement and provide this callback so that the scan results or errors can be
71      * returned.
72      */
73     public static abstract class NetworkScanCallback {
74         /** Returns the scan results to the user, this callback will be called multiple times. */
onResults(List<CellInfo> results)75         public void onResults(List<CellInfo> results) {}
76 
77         /**
78          * Informs the user that the scan has stopped.
79          *
80          * This callback will be called when the scan is finished or cancelled by the user.
81          * The related NetworkScanRequest will be deleted after this callback.
82          */
onComplete()83         public void onComplete() {}
84 
85         /**
86          * Informs the user that there is some error about the scan.
87          *
88          * This callback will be called whenever there is any error about the scan, and the scan
89          * will be terminated. onComplete() will NOT be called.
90          *
91          * @param error Error code when the scan is failed, as defined in {@link NetworkScan}.
92          */
onError(@etworkScan.ScanErrorCode int error)93         public void onError(@NetworkScan.ScanErrorCode int error) {}
94     }
95 
96     private static class NetworkScanInfo {
97         private final NetworkScanRequest mRequest;
98         private final Executor mExecutor;
99         private final NetworkScanCallback mCallback;
100 
NetworkScanInfo( NetworkScanRequest request, Executor executor, NetworkScanCallback callback)101         NetworkScanInfo(
102                 NetworkScanRequest request, Executor executor, NetworkScanCallback callback) {
103             mRequest = request;
104             mExecutor = executor;
105             mCallback = callback;
106         }
107     }
108 
109     private final Looper mLooper;
110     private final Handler mHandler;
111     private final Messenger mMessenger;
112     private final SparseArray<NetworkScanInfo> mScanInfo = new SparseArray<NetworkScanInfo>();
113     private final Binder.DeathRecipient mDeathRecipient;
114 
TelephonyScanManager()115     public TelephonyScanManager() {
116         HandlerThread thread = new HandlerThread(TAG);
117         thread.start();
118         mLooper = thread.getLooper();
119         mHandler = new Handler(mLooper) {
120             @Override
121             public void handleMessage(Message message) {
122                 checkNotNull(message, "message cannot be null");
123                 if (message.what == CALLBACK_TELEPHONY_DIED) {
124                     // If there are no objects in mScanInfo then binder death will simply return.
125                     synchronized (mScanInfo) {
126                         for (int i = 0; i < mScanInfo.size(); i++) {
127                             NetworkScanInfo nsi = mScanInfo.valueAt(i);
128                             // At this point we go into panic mode and ignore errors that would
129                             // normally stop the show in order to try and clean up as gracefully
130                             // as possible.
131                             if (nsi == null) continue; // shouldn't be possible
132                             Executor e = nsi.mExecutor;
133                             NetworkScanCallback cb = nsi.mCallback;
134                             if (e == null || cb == null) continue;
135                             try {
136                                 e.execute(
137                                         () -> cb.onError(NetworkScan.ERROR_MODEM_UNAVAILABLE));
138                             } catch (java.util.concurrent.RejectedExecutionException ignore) {
139                                 // ignore so that we can continue
140                             }
141                         }
142 
143                         mScanInfo.clear();
144                     }
145                     return;
146                 }
147 
148                 NetworkScanInfo nsi;
149                 synchronized (mScanInfo) {
150                     nsi = mScanInfo.get(message.arg2);
151                 }
152                 if (nsi == null) {
153                     throw new RuntimeException(
154                         "Failed to find NetworkScanInfo with id " + message.arg2);
155                 }
156 
157                 final NetworkScanCallback callback = nsi.mCallback;
158                 final Executor executor = nsi.mExecutor;
159 
160                 switch (message.what) {
161                     case CALLBACK_RESTRICTED_SCAN_RESULTS:
162                     case CALLBACK_SCAN_RESULTS:
163                         try {
164                             final Bundle b = message.getData();
165                             final Parcelable[] parcelables = b.getParcelableArray(SCAN_RESULT_KEY);
166                             CellInfo[] ci = new CellInfo[parcelables.length];
167                             for (int i = 0; i < parcelables.length; i++) {
168                                 ci[i] = (CellInfo) parcelables[i];
169                             }
170                             executor.execute(() -> {
171                                 Rlog.d(TAG, "onResults: " + ci.toString());
172                                 callback.onResults(Arrays.asList(ci));
173                             });
174                         } catch (Exception e) {
175                             Rlog.e(TAG, "Exception in networkscan callback onResults", e);
176                         }
177                         break;
178                     case CALLBACK_SCAN_ERROR:
179                         try {
180                             final int errorCode = message.arg1;
181                             executor.execute(() -> {
182                                 Rlog.d(TAG, "onError: " + errorCode);
183                                 callback.onError(errorCode);
184                             });
185                             synchronized (mScanInfo) {
186                                 mScanInfo.remove(message.arg2);
187                             }
188                         } catch (Exception e) {
189                             Rlog.e(TAG, "Exception in networkscan callback onError", e);
190                         }
191                         break;
192                     case CALLBACK_SCAN_COMPLETE:
193                         try {
194                             executor.execute(() -> {
195                                 Rlog.d(TAG, "onComplete");
196                                 callback.onComplete();
197                             });
198                             synchronized (mScanInfo) {
199                                 mScanInfo.remove(message.arg2);
200                             }
201                         } catch (Exception e) {
202                             Rlog.e(TAG, "Exception in networkscan callback onComplete", e);
203                         }
204                         break;
205                     default:
206                         Rlog.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
207                         break;
208                 }
209             }
210         };
211         mMessenger = new Messenger(mHandler);
212         mDeathRecipient = new Binder.DeathRecipient() {
213             @Override
214             public void binderDied() {
215                 mHandler.obtainMessage(CALLBACK_TELEPHONY_DIED).sendToTarget();
216             }
217         };
218     }
219 
220     /**
221      * Request a network scan.
222      *
223      * This method is asynchronous, so the network scan results will be returned by callback.
224      * The returned NetworkScan will contain a callback method which can be used to stop the scan.
225      *
226      * <p>
227      * Requires Permission:
228      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
229      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
230      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
231      *
232      * @param request Contains all the RAT with bands/channels that need to be scanned.
233      * @param callback Returns network scan results or errors.
234      * @param callingPackage The package name of the caller
235      * @param callingFeatureId The feature id inside of the calling package
236      * @return A NetworkScan obj which contains a callback which can stop the scan.
237      * @hide
238      */
requestNetworkScan(int subId, NetworkScanRequest request, Executor executor, NetworkScanCallback callback, String callingPackage, @Nullable String callingFeatureId)239     public NetworkScan requestNetworkScan(int subId,
240             NetworkScanRequest request, Executor executor, NetworkScanCallback callback,
241             String callingPackage, @Nullable String callingFeatureId) {
242         try {
243             Objects.requireNonNull(request, "Request was null");
244             Objects.requireNonNull(callback, "Callback was null");
245             Objects.requireNonNull(executor, "Executor was null");
246             final ITelephony telephony = getITelephony();
247             if (telephony == null) return null;
248 
249             // The lock must be taken before calling requestNetworkScan because the resulting
250             // scanId can be invoked asynchronously on another thread at any time after
251             // requestNetworkScan invoked, leaving a critical section between that call and adding
252             // the record to the ScanInfo cache.
253             synchronized (mScanInfo) {
254                 int scanId = telephony.requestNetworkScan(
255                         subId, request, mMessenger, new Binder(), callingPackage,
256                         callingFeatureId);
257                 if (scanId == INVALID_SCAN_ID) {
258                     Rlog.e(TAG, "Failed to initiate network scan");
259                     return null;
260                 }
261                 // We link to death whenever a scan is started to ensure that we are linked
262                 // at the point that phone process death might matter.
263                 // We never unlink because:
264                 // - Duplicate links to death with the same callback do not result in
265                 //   extraneous callbacks (the tracking de-dupes).
266                 // - Receiving binderDeath() when no scans are active is a no-op.
267                 telephony.asBinder().linkToDeath(mDeathRecipient, 0);
268                 saveScanInfo(scanId, request, executor, callback);
269                 return new NetworkScan(scanId, subId);
270             }
271         } catch (RemoteException ex) {
272             Rlog.e(TAG, "requestNetworkScan RemoteException", ex);
273         } catch (NullPointerException ex) {
274             Rlog.e(TAG, "requestNetworkScan NPE", ex);
275         }
276         return null;
277     }
278 
279     @GuardedBy("mScanInfo")
saveScanInfo( int id, NetworkScanRequest request, Executor executor, NetworkScanCallback callback)280     private void saveScanInfo(
281             int id, NetworkScanRequest request, Executor executor, NetworkScanCallback callback) {
282         mScanInfo.put(id, new NetworkScanInfo(request, executor, callback));
283     }
284 
getITelephony()285     private ITelephony getITelephony() {
286         return ITelephony.Stub.asInterface(
287             TelephonyFrameworkInitializer
288                     .getTelephonyServiceManager()
289                     .getTelephonyServiceRegisterer()
290                     .get());
291     }
292 }
293