/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.telephony; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.Parcelable; import android.os.RemoteException; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.telephony.ITelephony; import com.android.telephony.Rlog; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; /** * Manages the radio access network scan requests and callbacks. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) public final class TelephonyScanManager { private static final String TAG = "TelephonyScanManager"; /** @hide */ public static final String SCAN_RESULT_KEY = "scanResult"; /** @hide */ public static final int CALLBACK_SCAN_RESULTS = 1; /** @hide */ public static final int CALLBACK_SCAN_ERROR = 2; /** @hide */ public static final int CALLBACK_SCAN_COMPLETE = 3; /** @hide */ public static final int CALLBACK_RESTRICTED_SCAN_RESULTS = 4; /** @hide */ public static final int CALLBACK_TELEPHONY_DIED = 5; /** @hide */ public static final int INVALID_SCAN_ID = -1; /** * The caller of * {@link * TelephonyManager#requestNetworkScan(NetworkScanRequest, Executor, NetworkScanCallback)} * should implement and provide this callback so that the scan results or errors can be * returned. */ public static abstract class NetworkScanCallback { /** Returns the scan results to the user, this callback will be called multiple times. */ public void onResults(List results) {} /** * Informs the user that the scan has stopped. * * This callback will be called when the scan is finished or cancelled by the user. * The related NetworkScanRequest will be deleted after this callback. */ public void onComplete() {} /** * Informs the user that there is some error about the scan. * * This callback will be called whenever there is any error about the scan, and the scan * will be terminated. onComplete() will NOT be called. * * @param error Error code when the scan is failed, as defined in {@link NetworkScan}. */ public void onError(@NetworkScan.ScanErrorCode int error) {} } private static class NetworkScanInfo { private final NetworkScanRequest mRequest; private final Executor mExecutor; private final NetworkScanCallback mCallback; NetworkScanInfo( NetworkScanRequest request, Executor executor, NetworkScanCallback callback) { mRequest = request; mExecutor = executor; mCallback = callback; } } private final Looper mLooper; private final Handler mHandler; private final Messenger mMessenger; private final SparseArray mScanInfo = new SparseArray(); private final Binder.DeathRecipient mDeathRecipient; public TelephonyScanManager() { HandlerThread thread = new HandlerThread(TAG); thread.start(); mLooper = thread.getLooper(); mHandler = new Handler(mLooper) { @Override public void handleMessage(Message message) { checkNotNull(message, "message cannot be null"); if (message.what == CALLBACK_TELEPHONY_DIED) { // If there are no objects in mScanInfo then binder death will simply return. synchronized (mScanInfo) { for (int i = 0; i < mScanInfo.size(); i++) { NetworkScanInfo nsi = mScanInfo.valueAt(i); // At this point we go into panic mode and ignore errors that would // normally stop the show in order to try and clean up as gracefully // as possible. if (nsi == null) continue; // shouldn't be possible Executor e = nsi.mExecutor; NetworkScanCallback cb = nsi.mCallback; if (e == null || cb == null) continue; try { e.execute( () -> cb.onError(NetworkScan.ERROR_MODEM_UNAVAILABLE)); } catch (java.util.concurrent.RejectedExecutionException ignore) { // ignore so that we can continue } } mScanInfo.clear(); } return; } NetworkScanInfo nsi; synchronized (mScanInfo) { nsi = mScanInfo.get(message.arg2); } if (nsi == null) { Rlog.e(TAG, "Unexpceted message " + message.what + " as there is no NetworkScanInfo with id " + message.arg2); return; } final NetworkScanCallback callback = nsi.mCallback; final Executor executor = nsi.mExecutor; switch (message.what) { case CALLBACK_RESTRICTED_SCAN_RESULTS: case CALLBACK_SCAN_RESULTS: try { final Bundle b = message.getData(); final Parcelable[] parcelables = b.getParcelableArray(SCAN_RESULT_KEY); CellInfo[] ci = new CellInfo[parcelables.length]; for (int i = 0; i < parcelables.length; i++) { ci[i] = (CellInfo) parcelables[i]; } executor.execute(() -> { Rlog.d(TAG, "onResults: " + Arrays.toString(ci)); callback.onResults(Arrays.asList(ci)); }); } catch (Exception e) { Rlog.e(TAG, "Exception in networkscan callback onResults", e); } break; case CALLBACK_SCAN_ERROR: try { final int errorCode = message.arg1; executor.execute(() -> { Rlog.d(TAG, "onError: " + errorCode); callback.onError(errorCode); }); synchronized (mScanInfo) { mScanInfo.remove(message.arg2); } } catch (Exception e) { Rlog.e(TAG, "Exception in networkscan callback onError", e); } break; case CALLBACK_SCAN_COMPLETE: try { executor.execute(() -> { Rlog.d(TAG, "onComplete"); callback.onComplete(); }); synchronized (mScanInfo) { mScanInfo.remove(message.arg2); } } catch (Exception e) { Rlog.e(TAG, "Exception in networkscan callback onComplete", e); } break; default: Rlog.e(TAG, "Unhandled message " + Integer.toHexString(message.what)); break; } } }; mMessenger = new Messenger(mHandler); mDeathRecipient = new Binder.DeathRecipient() { @Override public void binderDied() { mHandler.obtainMessage(CALLBACK_TELEPHONY_DIED).sendToTarget(); } }; } /** * Request a network scan. * * This method is asynchronous, so the network scan results will be returned by callback. * The returned NetworkScan will contain a callback method which can be used to stop the scan. * *

* Requires Permission: * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * Or the calling app has carrier privileges. @see #hasCarrierPrivileges * @param renounceFineLocationAccess Set this to true if the caller would not like to receive * location related information which will be sent if the caller already possess * {@link android.Manifest.permission.ACCESS_FINE_LOCATION} and do not renounce the permission * @param request Contains all the RAT with bands/channels that need to be scanned. * @param callback Returns network scan results or errors. * @param callingPackage The package name of the caller * @param callingFeatureId The feature id inside of the calling package * @return A NetworkScan obj which contains a callback which can stop the scan. * @hide */ public NetworkScan requestNetworkScan(int subId, boolean renounceFineLocationAccess, NetworkScanRequest request, Executor executor, NetworkScanCallback callback, String callingPackage, @Nullable String callingFeatureId) { try { Objects.requireNonNull(request, "Request was null"); Objects.requireNonNull(callback, "Callback was null"); Objects.requireNonNull(executor, "Executor was null"); final ITelephony telephony = getITelephony(); if (telephony == null) return null; // The lock must be taken before calling requestNetworkScan because the resulting // scanId can be invoked asynchronously on another thread at any time after // requestNetworkScan invoked, leaving a critical section between that call and adding // the record to the ScanInfo cache. synchronized (mScanInfo) { int scanId = telephony.requestNetworkScan( subId, renounceFineLocationAccess, request, mMessenger, new Binder(), callingPackage, callingFeatureId); if (scanId == INVALID_SCAN_ID) { Rlog.e(TAG, "Failed to initiate network scan"); return null; } // We link to death whenever a scan is started to ensure that we are linked // at the point that phone process death might matter. // We never unlink because: // - Duplicate links to death with the same callback do not result in // extraneous callbacks (the tracking de-dupes). // - Receiving binderDeath() when no scans are active is a no-op. telephony.asBinder().linkToDeath(mDeathRecipient, 0); saveScanInfo(scanId, request, executor, callback); return new NetworkScan(scanId, subId); } } catch (RemoteException ex) { Rlog.e(TAG, "requestNetworkScan RemoteException", ex); } catch (NullPointerException ex) { Rlog.e(TAG, "requestNetworkScan NPE", ex); } return null; } @GuardedBy("mScanInfo") private void saveScanInfo( int id, NetworkScanRequest request, Executor executor, NetworkScanCallback callback) { mScanInfo.put(id, new NetworkScanInfo(request, executor, callback)); } private ITelephony getITelephony() { return ITelephony.Stub.asInterface( TelephonyFrameworkInitializer .getTelephonyServiceManager() .getTelephonyServiceRegisterer() .get()); } }