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 com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP; 20 import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP_BRIDGE; 21 import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_NAN; 22 import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_P2P; 23 import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_STA; 24 25 import android.annotation.IntDef; 26 import android.content.res.Resources; 27 import android.net.wifi.WifiContext; 28 import android.os.Message; 29 import android.os.WorkSource; 30 import android.text.TextUtils; 31 import android.util.ArraySet; 32 import android.util.Log; 33 import android.util.Pair; 34 35 import com.android.internal.util.State; 36 import com.android.internal.util.StateMachine; 37 import com.android.server.wifi.util.WaitingState; 38 import com.android.wifi.resources.R; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.Collections; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Set; 48 import java.util.function.Consumer; 49 50 /** 51 * Displays dialogs asking the user to approve or reject interface priority decisions. 52 */ 53 public class InterfaceConflictManager { 54 private static final String TAG = "InterfaceConflictManager"; 55 private boolean mVerboseLoggingEnabled = false; 56 57 private final WifiContext mContext; 58 private final FrameworkFacade mFrameworkFacade; 59 private final HalDeviceManager mHdm; 60 private final WifiThreadRunner mThreadRunner; 61 private final WifiDialogManager mWifiDialogManager; 62 63 private final Resources mResources; 64 private final boolean mUserApprovalNeeded; 65 private final Set<String> mUserApprovalExemptedPackages; 66 private boolean mUserApprovalNeededOverride = false; 67 private boolean mUserApprovalNeededOverrideValue = false; 68 69 private Object mLock = new Object(); 70 private boolean mUserApprovalPending = false; 71 private String mUserApprovalPendingTag = null; 72 private boolean mUserJustApproved = false; 73 74 private static final String MESSAGE_BUNDLE_KEY_PENDING_USER = "pending_user_decision"; 75 InterfaceConflictManager(WifiContext wifiContext, FrameworkFacade frameworkFacade, HalDeviceManager hdm, WifiThreadRunner threadRunner, WifiDialogManager wifiDialogManager)76 public InterfaceConflictManager(WifiContext wifiContext, FrameworkFacade frameworkFacade, 77 HalDeviceManager hdm, WifiThreadRunner threadRunner, 78 WifiDialogManager wifiDialogManager) { 79 mContext = wifiContext; 80 mFrameworkFacade = frameworkFacade; 81 mHdm = hdm; 82 mThreadRunner = threadRunner; 83 mWifiDialogManager = wifiDialogManager; 84 85 mResources = mContext.getResources(); 86 mUserApprovalNeeded = mResources.getBoolean( 87 R.bool.config_wifiUserApprovalRequiredForD2dInterfacePriority); 88 String[] packageList = mResources.getStringArray( 89 R.array.config_wifiExcludedFromUserApprovalForD2dInterfacePriority); 90 mUserApprovalExemptedPackages = 91 (packageList == null || packageList.length == 0) ? Collections.emptySet() 92 : new ArraySet<>(packageList); 93 } 94 95 /** 96 * Enable verbose logging. 97 */ enableVerboseLogging(boolean verboseEnabled)98 public void enableVerboseLogging(boolean verboseEnabled) { 99 mVerboseLoggingEnabled = verboseEnabled; 100 } 101 102 /** 103 * Returns an indication as to whether user approval is needed for this specific request. User 104 * approval is controlled by: 105 * - A global overlay `config_wifiUserApprovalRequiredForD2dInterfacePriority` 106 * - An exemption list overlay `config_wifiExcludedFromUserApprovalForD2dInterfacePriority` 107 * which is a list of packages which are *exempted* from user approval 108 * - A shell command which can be used to override 109 * 110 * @param requestorWs The WorkSource of the requestor - used to determine whether it is exempted 111 * from user approval. All requesting packages must be exempted for the 112 * dialog to NOT be displayed. 113 */ isUserApprovalNeeded(WorkSource requestorWs)114 private boolean isUserApprovalNeeded(WorkSource requestorWs) { 115 if (mUserApprovalNeededOverride) return mUserApprovalNeededOverrideValue; 116 if (!mUserApprovalNeeded || mUserApprovalExemptedPackages.isEmpty()) { 117 return mUserApprovalNeeded; 118 } 119 120 for (int i = 0; i < requestorWs.size(); ++i) { 121 if (!mUserApprovalExemptedPackages.contains(requestorWs.getPackageName(i))) { 122 return true; 123 } 124 } 125 126 return false; // all packages of the requestor are excluded 127 } 128 129 /** 130 * Override (potentially) the user approval needed device configuration. Intended for debugging 131 * via the shell command. 132 * 133 * @param override Enable overriding the default. 134 * @param overrideValue The actual override value (i.e. disable or enable). 135 */ setUserApprovalNeededOverride(boolean override, boolean overrideValue)136 public void setUserApprovalNeededOverride(boolean override, boolean overrideValue) { 137 if (mVerboseLoggingEnabled) { 138 Log.d(TAG, "setUserApprovalNeededOverride: override=" + override + ", overrideValue=" 139 + overrideValue); 140 } 141 mUserApprovalNeededOverride = override; 142 mUserApprovalNeededOverrideValue = overrideValue; 143 } 144 145 /** 146 * Return values for {@link #manageInterfaceConflictForStateMachine} 147 */ 148 149 // Caller should continue and execute command: no need for user approval, or user approval 150 // already granted, or command bound to fail so just fail through the normal path 151 public static final int ICM_EXECUTE_COMMAND = 0; 152 153 // Caller should skip executing the command for now (do not defer it - already done!). The user 154 // was asked for permission and the command will be executed again when we get a response. 155 public static final int ICM_SKIP_COMMAND_WAIT_FOR_USER = 1; 156 157 // Caller should abort the command and execute whatever failure code is necessary - this 158 // command was rejected by the user or we cannot ask the user since there's a pending user 159 // request. 160 public static final int ICM_ABORT_COMMAND = 2; 161 162 @Retention(RetentionPolicy.SOURCE) 163 @IntDef(prefix = {"ICM_"}, value = { 164 ICM_EXECUTE_COMMAND, 165 ICM_SKIP_COMMAND_WAIT_FOR_USER, 166 ICM_ABORT_COMMAND 167 }) 168 @interface IcmResult {} 169 170 /** 171 * Manages interface conflicts for a State Machine based caller. Possible scenarios: 172 * - New request: 173 * - ok to proceed inline (i.e. caller can just proceed normally - no conflict) 174 * [nop] 175 * - need to request user approval (there's conflict, caller need to wait for user response) 176 * [msg get tagged + deferred, transition to waiting state] 177 * - Previously executed command (i.e. already asked the user) 178 * - user rejected request 179 * [discard request, execute any necessary error callbacks] 180 * - user approved request 181 * [~nop (i.e. proceed)] 182 * - Busy asking approval for another request: 183 * - If from another caller: reject 184 * - If from the same caller: defer the caller (possibly will be approved when gets to ask 185 * again). 186 * 187 * Synchronization: 188 * - Multiple threads accessing this method will be blocked until the processing of the other 189 * thread is done. The "processing" is simply the decision making - i.e. not the waiting for 190 * user response. 191 * - If a user response is pending then subsequent requests are auto-rejected if they require 192 * user approval. Note that this will result in race condition if this approval changes 193 * the conditions for the user approval request: e.g. it may increase the impact of a user 194 * approval (w/o telling the user) or it may be rejected even if approved by the user (if 195 * the newly allocated interface now has higher priority). 196 * 197 * @param tag Tag of the caller for logging 198 * @param msg The command which needs to be evaluated or executed for user approval 199 * @param stateMachine The source state machine 200 * @param waitingState The {@link WaitingState} added to the above state machine 201 * @param targetState The target state to transition to on user response 202 * @param createIfaceType The interface which needs to be created 203 * @param requestorWs The requestor WorkSource 204 * 205 * @return ICM_EXECUTE_COMMAND caller should execute the command, 206 * ICM_SKIP_COMMAND_WAIT_FOR_USER caller should skip the command (for now), 207 * ICM_ABORT_COMMAND caller should abort this command and execute whatever failure code is 208 * necessary. 209 */ manageInterfaceConflictForStateMachine(String tag, Message msg, StateMachine stateMachine, WaitingState waitingState, State targetState, @HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs)210 public @IcmResult int manageInterfaceConflictForStateMachine(String tag, Message msg, 211 StateMachine stateMachine, WaitingState waitingState, State targetState, 212 @HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs) { 213 synchronized (mLock) { 214 if (mUserApprovalPending && !TextUtils.equals(tag, mUserApprovalPendingTag)) { 215 Log.w(TAG, tag + ": rejected since there's a pending user approval for " 216 + mUserApprovalPendingTag); 217 return ICM_ABORT_COMMAND; // caller should not proceed with operation 218 } 219 220 // is this a command which was waiting for a user decision? 221 boolean isReexecutedCommand = msg.getData().getBoolean( 222 MESSAGE_BUNDLE_KEY_PENDING_USER, false); 223 if (isReexecutedCommand) { 224 mUserApprovalPending = false; 225 mUserApprovalPendingTag = null; 226 227 if (mVerboseLoggingEnabled) { 228 Log.d(TAG, tag + ": Re-executing a command with user approval result - " 229 + mUserJustApproved); 230 } 231 return mUserJustApproved ? ICM_EXECUTE_COMMAND : ICM_ABORT_COMMAND; 232 } 233 234 if (mUserApprovalPending) { 235 Log.w(TAG, tag 236 + ": trying for another potentially waiting operation - but should be" 237 + " in a waiting state!?"); 238 stateMachine.deferMessage(msg); 239 return ICM_SKIP_COMMAND_WAIT_FOR_USER; // same effect 240 } 241 242 if (!isUserApprovalNeeded(requestorWs)) return ICM_EXECUTE_COMMAND; 243 244 List<Pair<Integer, WorkSource>> impact = mHdm.reportImpactToCreateIface(createIfaceType, 245 false, requestorWs); 246 if (mVerboseLoggingEnabled) { 247 Log.d(TAG, tag + ": Asking user about creating the interface, impact=" + impact); 248 } 249 if (impact == null || impact.isEmpty()) { 250 Log.d(TAG, tag 251 + ": Either can't create interface or can w/o sid-effects - proceeding"); 252 return ICM_EXECUTE_COMMAND; 253 } 254 255 displayUserApprovalDialog(createIfaceType, requestorWs, impact, 256 (result) -> { 257 if (mVerboseLoggingEnabled) { 258 Log.d(TAG, tag + ": User response to creating " + getInterfaceName( 259 createIfaceType) + ": " + result); 260 } 261 mUserJustApproved = result; 262 waitingState.sendTransitionStateCommand(targetState); 263 }); 264 // defer message to have it executed again automatically when switching 265 // states - want to do it now so that it will be at the top of the queue 266 // when we switch back. Will need to skip it if the user rejected it! 267 msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_PENDING_USER, true); 268 stateMachine.deferMessage(msg); 269 stateMachine.transitionTo(waitingState); 270 271 mUserApprovalPending = true; 272 mUserApprovalPendingTag = tag; 273 274 return ICM_SKIP_COMMAND_WAIT_FOR_USER; 275 } 276 } 277 278 /** 279 * Trigger a dialog which requests user approval to resolve an interface priority confict. 280 * 281 * @param createIfaceType The interface to be created. 282 * @param requestorWs The WorkSource of the requesting application. 283 * @param impact The impact of creating this interface (a list of interfaces to be deleted and 284 * their corresponding impacted WorkSources). 285 * @param handleResult A Consumer to execute with results. 286 */ displayUserApprovalDialog( @alDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs, List<Pair<Integer, WorkSource>> impact, Consumer<Boolean> handleResult)287 private void displayUserApprovalDialog( 288 @HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, 289 WorkSource requestorWs, 290 List<Pair<Integer, WorkSource>> impact, 291 Consumer<Boolean> handleResult) { 292 if (mVerboseLoggingEnabled) { 293 Log.d(TAG, "displayUserApprovalDialog: createIfaceType=" + createIfaceType 294 + ", requestorWs=" + requestorWs + ", impact=" + impact); 295 } 296 297 CharSequence requestorAppName = mFrameworkFacade.getAppName(mContext, 298 requestorWs.getPackageName(0), requestorWs.getUid(0)); 299 String requestedInterface = getInterfaceName(createIfaceType); 300 Set<String> impactedPackagesSet = new HashSet<>(); 301 for (Pair<Integer, WorkSource> detail : impact) { 302 for (int j = 0; j < detail.second.size(); ++j) { 303 impactedPackagesSet.add( 304 mFrameworkFacade.getAppName(mContext, detail.second.getPackageName(j), 305 detail.second.getUid(j)).toString()); 306 } 307 } 308 String impactedPackages = TextUtils.join(", ", impactedPackagesSet); 309 310 mWifiDialogManager.createSimpleDialog( 311 mResources.getString(R.string.wifi_interface_priority_title, requestorAppName), 312 impactedPackagesSet.size() == 1 ? mResources.getString( 313 R.string.wifi_interface_priority_message, requestorAppName, 314 requestedInterface, impactedPackages) 315 : mResources.getString(R.string.wifi_interface_priority_message_plural, 316 requestorAppName, requestedInterface, impactedPackages), 317 mResources.getString(R.string.wifi_interface_priority_approve), 318 mResources.getString(R.string.wifi_interface_priority_reject), 319 null, 320 new WifiDialogManager.SimpleDialogCallback() { 321 @Override 322 public void onPositiveButtonClicked() { 323 if (mVerboseLoggingEnabled) { 324 Log.d(TAG, "User approved request for " + getInterfaceName( 325 createIfaceType)); 326 } 327 handleResult.accept(true); 328 } 329 330 @Override 331 public void onNegativeButtonClicked() { 332 if (mVerboseLoggingEnabled) { 333 Log.d(TAG, "User rejected request for " + getInterfaceName( 334 createIfaceType)); 335 } 336 handleResult.accept(false); 337 } 338 339 @Override 340 public void onNeutralButtonClicked() { 341 onNegativeButtonClicked(); 342 } 343 344 @Override 345 public void onCancelled() { 346 onNegativeButtonClicked(); 347 } 348 }, mThreadRunner).launchDialog(); 349 } 350 getInterfaceName(@alDeviceManager.HdmIfaceTypeForCreation int createIfaceType)351 private String getInterfaceName(@HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType) { 352 switch (createIfaceType) { 353 case HDM_CREATE_IFACE_STA: 354 return "STA"; 355 case HDM_CREATE_IFACE_AP: 356 return "AP"; 357 case HDM_CREATE_IFACE_AP_BRIDGE: 358 return "AP"; 359 case HDM_CREATE_IFACE_P2P: 360 return "Wi-Fi Direct"; 361 case HDM_CREATE_IFACE_NAN: 362 return "Wi-Fi Aware"; 363 } 364 return "Unknown"; 365 } 366 367 /** 368 * Dump the internal state of the class. 369 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)370 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 371 pw.println("dump of " + TAG + ":"); 372 pw.println(" mUserApprovalNeeded=" + mUserApprovalNeeded); 373 pw.println(" mUserApprovalNeededOverride=" + mUserApprovalNeededOverride); 374 pw.println(" mUserApprovalNeededOverrideValue=" + mUserApprovalNeededOverrideValue); 375 pw.println(" mUserApprovalPending=" + mUserApprovalPending); 376 pw.println(" mUserApprovalPendingTag=" + mUserApprovalPendingTag); 377 pw.println(" mUserJustApproved=" + mUserJustApproved); 378 } 379 } 380