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