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