1 /* 2 * Copyright (C) 2022 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.server.wifi; 18 19 import static android.net.wifi.WifiManager.PnoScanResultsCallback.REGISTER_PNO_CALLBACK_RESOURCE_BUSY; 20 import static android.net.wifi.WifiManager.PnoScanResultsCallback.REMOVE_PNO_CALLBACK_RESULTS_DELIVERED; 21 import static android.net.wifi.WifiManager.PnoScanResultsCallback.REMOVE_PNO_CALLBACK_UNREGISTERED; 22 23 import android.annotation.NonNull; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.net.wifi.IPnoScanResultsCallback; 27 import android.net.wifi.ScanResult; 28 import android.net.wifi.WifiManager; 29 import android.net.wifi.WifiSsid; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.util.ArraySet; 35 import android.util.Log; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.NoSuchElementException; 44 import java.util.Set; 45 import java.util.stream.Collectors; 46 47 /** 48 * Manages PNO scan requests from apps. 49 * This class is not thread safe and is expected to be run on a single thread. 50 */ 51 public class ExternalPnoScanRequestManager implements IBinder.DeathRecipient { 52 private static final String TAG = "ExternalPnoScanRequestManager"; 53 private ExternalPnoScanRequest mCurrentRequest; 54 private final Handler mHandler; 55 private int mCurrentRequestOnPnoNetworkFoundCount = 0; 56 private Context mContext; 57 private boolean mVerboseLoggingEnabled = false; 58 59 /** 60 * Creates a ExternalPnoScanRequestManager. 61 * @param handler to run binder death callback. 62 * @param context of the wifi service. 63 */ ExternalPnoScanRequestManager(Handler handler, Context context)64 public ExternalPnoScanRequestManager(Handler handler, Context context) { 65 mHandler = handler; 66 mContext = context; 67 } 68 69 /** 70 * Returns a copy of the current SSIDs being requested for PNO scan. 71 */ getExternalPnoScanSsids()72 public Set<String> getExternalPnoScanSsids() { 73 return mCurrentRequest == null ? Collections.EMPTY_SET 74 : new ArraySet<>(mCurrentRequest.mSsidStrings); 75 } 76 77 /** 78 * Returns a copy of the current frequencies being requested for PNO scan. 79 */ getExternalPnoScanFrequencies()80 public Set<Integer> getExternalPnoScanFrequencies() { 81 return mCurrentRequest == null ? Collections.EMPTY_SET 82 : new ArraySet<>(mCurrentRequest.mFrequencies); 83 } 84 85 /** 86 * Enables verbose logging. 87 */ enableVerboseLogging(boolean enabled)88 public void enableVerboseLogging(boolean enabled) { 89 mVerboseLoggingEnabled = enabled; 90 } 91 92 /** 93 * Sets the request. This will fail if there's already a request set. 94 */ setRequest(int uid, @NonNull String packageName, @NonNull IBinder binder, @NonNull IPnoScanResultsCallback callback, @NonNull List<WifiSsid> ssids, @NonNull int[] frequencies)95 public boolean setRequest(int uid, @NonNull String packageName, @NonNull IBinder binder, 96 @NonNull IPnoScanResultsCallback callback, 97 @NonNull List<WifiSsid> ssids, @NonNull int[] frequencies) { 98 if (mCurrentRequest != null && uid != mCurrentRequest.mUid) { 99 try { 100 callback.onRegisterFailed(REGISTER_PNO_CALLBACK_RESOURCE_BUSY); 101 } catch (RemoteException e) { 102 Log.e(TAG, "RemoteException failed to trigger onRegisterFailed for callback=" 103 + callback); 104 } 105 return false; 106 } 107 ExternalPnoScanRequest request = new ExternalPnoScanRequestManager.ExternalPnoScanRequest( 108 uid, packageName, binder, callback, ssids, frequencies); 109 try { 110 request.mBinder.linkToDeath(this, 0); 111 } catch (RemoteException e) { 112 Log.e(TAG, "mBinder.linkToDeath failed: " + e.getMessage()); 113 return false; 114 } 115 try { 116 request.mCallback.onRegisterSuccess(); 117 } catch (RemoteException e) { 118 Log.e(TAG, "Failed to register request due to remote exception:" + e.getMessage()); 119 return false; 120 } 121 removeCurrentRequest(); 122 if (mVerboseLoggingEnabled) { 123 Log.i(TAG, "Successfully set external PNO scan request:" + request); 124 } 125 mCurrentRequest = request; 126 return true; 127 } 128 removeCurrentRequest()129 private void removeCurrentRequest() { 130 if (mCurrentRequest != null) { 131 try { 132 mCurrentRequest.mBinder.unlinkToDeath(this, 0); 133 if (mVerboseLoggingEnabled) { 134 Log.i(TAG, "mBinder.unlinkToDeath on request:" + mCurrentRequest); 135 } 136 } catch (NoSuchElementException e) { 137 Log.e(TAG, "Encountered remote exception in unlinkToDeath=" + e.getMessage()); 138 } 139 } 140 mCurrentRequest = null; 141 mCurrentRequestOnPnoNetworkFoundCount = 0; 142 } 143 144 /** 145 * Removes the requests. Will fail if the remover's uid and packageName does not match with the 146 * creator's uid and packageName. 147 */ removeRequest(int uid)148 public boolean removeRequest(int uid) { 149 if (mCurrentRequest == null || uid != mCurrentRequest.mUid) { 150 return false; 151 } 152 153 try { 154 mCurrentRequest.mCallback.onRemoved(REMOVE_PNO_CALLBACK_UNREGISTERED); 155 } catch (RemoteException e) { 156 Log.e(TAG, "Encountered remote exception in onRemoved=" + e.getMessage()); 157 } 158 removeCurrentRequest(); 159 return true; 160 } 161 162 /** 163 * Triggered when networks are found. Any results matching the external request will be 164 * sent to the callback. 165 * @param scanDetails the list of scanDetails received by the framework 166 */ onScanResultsAvailable(List<ScanDetail> scanDetails)167 public void onScanResultsAvailable(List<ScanDetail> scanDetails) { 168 if (mCurrentRequest == null) { 169 return; 170 } 171 mCurrentRequestOnPnoNetworkFoundCount++; 172 List<ScanResult> requestedResults = new ArrayList<>(); 173 for (ScanDetail scanDetail : scanDetails) { 174 if (mCurrentRequest.mSsidStrings.contains( 175 scanDetail.getScanResult().getWifiSsid().toString())) { 176 requestedResults.add(scanDetail.getScanResult()); 177 } 178 } 179 if (requestedResults.isEmpty()) { 180 return; 181 } 182 183 // requested PNO SSIDs found. Send results and then remove request. 184 if (mVerboseLoggingEnabled) { 185 Log.i(TAG, "On network found for request:" + mCurrentRequest); 186 } 187 sendScanResultAvailableBroadcastToPackage(mCurrentRequest.mPackageName); 188 try { 189 mCurrentRequest.mCallback.onScanResultsAvailable(requestedResults); 190 mCurrentRequest.mCallback.onRemoved(REMOVE_PNO_CALLBACK_RESULTS_DELIVERED); 191 } catch (RemoteException e) { 192 Log.e(TAG, "Failed to send PNO results via callback due to remote exception=" 193 + e.getMessage()); 194 } 195 removeCurrentRequest(); 196 } 197 sendScanResultAvailableBroadcastToPackage(String packageName)198 private void sendScanResultAvailableBroadcastToPackage(String packageName) { 199 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 200 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); 201 intent.setPackage(packageName); 202 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 203 if (mVerboseLoggingEnabled) { 204 Log.i(TAG, "Successfully sent out targeted broadcast for:" + mCurrentRequest); 205 } 206 } 207 208 /** 209 * Tracks a request for PNO scan made by an app. 210 */ 211 public static class ExternalPnoScanRequest { 212 private int mUid; 213 private String mPackageName; 214 private Set<String> mSsidStrings; 215 private Set<Integer> mFrequencies; 216 private IPnoScanResultsCallback mCallback; 217 private IBinder mBinder; 218 219 /** 220 * @param uid identifies the caller 221 * @param binder obtained from the caller 222 * @param callback used to send results back to the caller 223 * @param ssids requested SSIDs for PNO scan 224 */ ExternalPnoScanRequest(int uid, String packageName, IBinder binder, IPnoScanResultsCallback callback, List<WifiSsid> ssids, int[] frequencies)225 public ExternalPnoScanRequest(int uid, String packageName, IBinder binder, 226 IPnoScanResultsCallback callback, List<WifiSsid> ssids, int[] frequencies) { 227 mUid = uid; 228 mPackageName = packageName; 229 mBinder = binder; 230 mCallback = callback; 231 mSsidStrings = new ArraySet<>(); 232 for (WifiSsid wifiSsid : ssids) { 233 mSsidStrings.add(wifiSsid.toString()); 234 } 235 mFrequencies = Arrays.stream(frequencies).boxed().collect(Collectors.toSet()); 236 } 237 238 @Override toString()239 public String toString() { 240 StringBuilder sbuf = new StringBuilder(); 241 sbuf.append("uid=").append(mUid) 242 .append(", packageName=").append(mPackageName) 243 .append(", binder=").append(mBinder) 244 .append(", callback=").append(mCallback) 245 .append(", mSsidStrings="); 246 for (String s : mSsidStrings) { 247 sbuf.append(s).append(", "); 248 } 249 sbuf.append(" frequencies="); 250 for (int f : mFrequencies) { 251 sbuf.append(f).append(", "); 252 } 253 return sbuf.toString(); 254 } 255 } 256 257 /** 258 * Binder has died. Perform cleanup. 259 */ 260 @Override binderDied()261 public void binderDied() { 262 // Log binder died, but keep the request since result will still be delivered with directed 263 // broadcast 264 Log.w(TAG, "Binder died."); 265 } 266 267 /** 268 * Dump the local logs. 269 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)270 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 271 pw.println("Dump of ExternalPnoScanRequestManager"); 272 pw.println("ExternalPnoScanRequestManager - Log Begin ----"); 273 if (mCurrentRequest != null) { 274 pw.println("Current external PNO scan request:"); 275 pw.println(mCurrentRequest.toString()); 276 } else { 277 pw.println("No external PNO scan request set."); 278 } 279 pw.println("mCurrentRequestOnPnoNetworkFoundCount: " 280 + mCurrentRequestOnPnoNetworkFoundCount); 281 pw.println("ExternalPnoScanRequestManager - Log End ----"); 282 } 283 } 284