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 import static com.android.server.wifi.util.WorkSourceHelper.PRIORITY_INTERNAL; 25 26 import android.annotation.IntDef; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.res.Resources; 32 import android.net.NetworkInfo; 33 import android.net.wifi.WifiContext; 34 import android.net.wifi.p2p.WifiP2pManager; 35 import android.os.Message; 36 import android.os.Process; 37 import android.os.WorkSource; 38 import android.text.TextUtils; 39 import android.util.ArraySet; 40 import android.util.LocalLog; 41 import android.util.Log; 42 import android.util.Pair; 43 44 import androidx.annotation.NonNull; 45 46 import com.android.internal.util.State; 47 import com.android.internal.util.StateMachine; 48 import com.android.server.wifi.util.WaitingState; 49 import com.android.server.wifi.util.WorkSourceHelper; 50 import com.android.wifi.resources.R; 51 52 import java.io.FileDescriptor; 53 import java.io.PrintWriter; 54 import java.lang.annotation.Retention; 55 import java.lang.annotation.RetentionPolicy; 56 import java.util.Collections; 57 import java.util.HashSet; 58 import java.util.List; 59 import java.util.Set; 60 import java.util.function.Consumer; 61 62 /** 63 * Displays dialogs asking the user to approve or reject interface priority decisions. 64 */ 65 public class InterfaceConflictManager { 66 private static final String TAG = "InterfaceConflictManager"; 67 private boolean mVerboseLoggingEnabled = false; 68 69 private final WifiInjector mWifiInjector; 70 private final WifiContext mContext; 71 private final FrameworkFacade mFrameworkFacade; 72 private final HalDeviceManager mHdm; 73 private final WifiThreadRunner mThreadRunner; 74 private final WifiDialogManager mWifiDialogManager; 75 private final LocalLog mLocalLog; 76 77 private boolean mUserApprovalNeeded = false; 78 private Set<String> mUserApprovalExemptedPackages = new ArraySet<>(); 79 private boolean mUserApprovalNotRequireForDisconnectedP2p = false; 80 private boolean mUserApprovalNeededOverride = false; 81 private boolean mUserApprovalNeededOverrideValue = false; 82 83 private Object mLock = new Object(); 84 private boolean mUserApprovalPending = false; 85 private String mUserApprovalPendingTag = null; 86 private boolean mUserJustApproved = false; 87 private boolean mIsP2pConnected = false; 88 89 private WaitingState mCurrentWaitingState; 90 private State mCurrentTargetState; 91 private WifiDialogManager.DialogHandle mCurrentDialogHandle; 92 93 private static final String MESSAGE_BUNDLE_KEY_PENDING_USER = "pending_user_decision"; 94 InterfaceConflictManager(@onNull WifiInjector wifiInjector, WifiContext wifiContext, FrameworkFacade frameworkFacade, HalDeviceManager hdm, WifiThreadRunner threadRunner, WifiDialogManager wifiDialogManager, LocalLog localLog)95 public InterfaceConflictManager(@NonNull WifiInjector wifiInjector, WifiContext wifiContext, 96 FrameworkFacade frameworkFacade, HalDeviceManager hdm, WifiThreadRunner threadRunner, 97 WifiDialogManager wifiDialogManager, LocalLog localLog) { 98 mWifiInjector = wifiInjector; 99 mContext = wifiContext; 100 mFrameworkFacade = frameworkFacade; 101 mHdm = hdm; 102 mThreadRunner = threadRunner; 103 mWifiDialogManager = wifiDialogManager; 104 mLocalLog = localLog; 105 106 // Monitor P2P connection for auto-approval 107 IntentFilter intentFilter = new IntentFilter(); 108 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 109 mContext.registerReceiver(new BroadcastReceiver() { 110 @Override 111 public void onReceive(Context context, Intent intent) { 112 String action = intent.getAction(); 113 if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { 114 NetworkInfo mNetworkInfo = (NetworkInfo) intent.getParcelableExtra( 115 WifiP2pManager.EXTRA_NETWORK_INFO); 116 if (mNetworkInfo.getDetailedState() == NetworkInfo.DetailedState.CONNECTED) { 117 mIsP2pConnected = true; 118 } else { 119 mIsP2pConnected = false; 120 } 121 } 122 123 } 124 }, intentFilter); 125 } 126 127 /** 128 * Enable verbose logging. 129 */ enableVerboseLogging(boolean verboseEnabled)130 public void enableVerboseLogging(boolean verboseEnabled) { 131 mVerboseLoggingEnabled = verboseEnabled; 132 } 133 134 /** 135 * Returns whether user approval is needed to delete an existing interface for a new one. 136 * User approval is controlled by: 137 * - A global overlay `config_wifiUserApprovalRequiredForD2dInterfacePriority` 138 * - An exemption list overlay `config_wifiExcludedFromUserApprovalForD2dInterfacePriority` 139 * which is a list of packages which are *exempted* from user approval 140 * - A shell command which can be used to override 141 * 142 * @param requestedCreateType Requested interface type 143 * @param newRequestorWsHelper WorkSourceHelper of the new interface 144 * @param existingCreateType Existing interface type 145 * @param existingRequestorWsHelper WorkSourceHelper of the existing interface 146 * @return true if the new interface needs user approval to delete the existing one. 147 */ needsUserApprovalToDelete( int requestedCreateType, @NonNull WorkSourceHelper newRequestorWsHelper, int existingCreateType, @NonNull WorkSourceHelper existingRequestorWsHelper)148 public boolean needsUserApprovalToDelete( 149 int requestedCreateType, @NonNull WorkSourceHelper newRequestorWsHelper, 150 int existingCreateType, @NonNull WorkSourceHelper existingRequestorWsHelper) { 151 if (!isUserApprovalEnabled()) { 152 return false; 153 } 154 155 // Check if every package in the WorkSource are exempt from user approval. 156 if (!mUserApprovalExemptedPackages.isEmpty()) { 157 boolean exemptFromUserApproval = true; 158 WorkSource requestorWs = newRequestorWsHelper.getWorkSource(); 159 for (int i = 0; i < requestorWs.size(); i++) { 160 if (!mUserApprovalExemptedPackages.contains(requestorWs.getPackageName(i))) { 161 exemptFromUserApproval = false; 162 break; 163 } 164 } 165 if (exemptFromUserApproval) { 166 return false; 167 } 168 } 169 // Check if priority level can get user approval. 170 if (newRequestorWsHelper.getRequestorWsPriority() <= WorkSourceHelper.PRIORITY_BG 171 || existingRequestorWsHelper.getRequestorWsPriority() 172 == PRIORITY_INTERNAL) { 173 return false; 174 } 175 // Check if the conflicting interface types can get user approval. 176 if (requestedCreateType == HDM_CREATE_IFACE_AP 177 || requestedCreateType == HDM_CREATE_IFACE_AP_BRIDGE) { 178 if (existingCreateType == HDM_CREATE_IFACE_P2P 179 || existingCreateType == HDM_CREATE_IFACE_NAN) { 180 return true; 181 } 182 } else if (requestedCreateType == HDM_CREATE_IFACE_P2P) { 183 if (existingCreateType == HDM_CREATE_IFACE_AP 184 || existingCreateType == HDM_CREATE_IFACE_AP_BRIDGE 185 || existingCreateType == HDM_CREATE_IFACE_NAN) { 186 return true; 187 } 188 } else if (requestedCreateType == HDM_CREATE_IFACE_NAN) { 189 if (existingCreateType == HDM_CREATE_IFACE_AP 190 || existingCreateType == HDM_CREATE_IFACE_AP_BRIDGE 191 || existingCreateType == HDM_CREATE_IFACE_P2P) { 192 return true; 193 } 194 } 195 return false; 196 } 197 198 /** 199 * Override (potentially) the user approval needed device configuration. Intended for debugging 200 * via the shell command. 201 * 202 * @param override Enable overriding the default. 203 * @param overrideValue The actual override value (i.e. disable or enable). 204 */ setUserApprovalNeededOverride(boolean override, boolean overrideValue)205 public void setUserApprovalNeededOverride(boolean override, boolean overrideValue) { 206 localLog("setUserApprovalNeededOverride: override=" + override + ", overrideValue=" 207 + overrideValue); 208 mUserApprovalNeededOverride = override; 209 mUserApprovalNeededOverrideValue = overrideValue; 210 } 211 isUserApprovalEnabled()212 private boolean isUserApprovalEnabled() { 213 if (mUserApprovalNeededOverride) { 214 return mUserApprovalNeededOverrideValue; 215 } 216 return mUserApprovalNeeded; 217 } 218 219 /** 220 * Return values for {@link #manageInterfaceConflictForStateMachine} 221 */ 222 223 // Caller should continue and execute command: no need for user approval, or user approval 224 // already granted, or command bound to fail so just fail through the normal path 225 public static final int ICM_EXECUTE_COMMAND = 0; 226 227 // Caller should skip executing the command for now (do not defer it - already done!). The user 228 // was asked for permission and the command will be executed again when we get a response. 229 public static final int ICM_SKIP_COMMAND_WAIT_FOR_USER = 1; 230 231 // Caller should abort the command and execute whatever failure code is necessary - this 232 // command was rejected by the user or we cannot ask the user since there's a pending user 233 // request. 234 public static final int ICM_ABORT_COMMAND = 2; 235 236 @Retention(RetentionPolicy.SOURCE) 237 @IntDef(prefix = {"ICM_"}, value = { 238 ICM_EXECUTE_COMMAND, 239 ICM_SKIP_COMMAND_WAIT_FOR_USER, 240 ICM_ABORT_COMMAND 241 }) 242 @interface IcmResult {} 243 244 /** 245 * Manages interface conflicts for a State Machine based caller. Possible scenarios: 246 * - New request: 247 * - ok to proceed inline (i.e. caller can just proceed normally - no conflict) 248 * [nop] 249 * - need to request user approval (there's conflict, caller need to wait for user response) 250 * [msg get tagged + deferred, transition to waiting state] 251 * - Previously executed command (i.e. already asked the user) 252 * - user rejected request 253 * [discard request, execute any necessary error callbacks] 254 * - user approved request 255 * [~nop (i.e. proceed)] 256 * - Busy asking approval for another request: 257 * - If from another caller: reject 258 * - If from the same caller: defer the caller (possibly will be approved when gets to ask 259 * again). 260 * 261 * Synchronization: 262 * - Multiple threads accessing this method will be blocked until the processing of the other 263 * thread is done. The "processing" is simply the decision making - i.e. not the waiting for 264 * user response. 265 * - If a user response is pending then subsequent requests are auto-rejected if they require 266 * user approval. Note that this will result in race condition if this approval changes 267 * the conditions for the user approval request: e.g. it may increase the impact of a user 268 * approval (w/o telling the user) or it may be rejected even if approved by the user (if 269 * the newly allocated interface now has higher priority). 270 * 271 * @param tag Tag of the caller for logging 272 * @param msg The command which needs to be evaluated or executed for user approval 273 * @param stateMachine The source state machine 274 * @param waitingState The {@link WaitingState} added to the above state machine 275 * @param targetState The target state to transition to on user response 276 * @param createIfaceType The interface which needs to be created 277 * @param requestorWs The requestor WorkSource 278 * 279 * @param bypassDialog 280 * @return ICM_EXECUTE_COMMAND caller should execute the command, 281 * ICM_SKIP_COMMAND_WAIT_FOR_USER caller should skip the command (for now), 282 * ICM_ABORT_COMMAND caller should abort this command and execute whatever failure code is 283 * necessary. 284 */ manageInterfaceConflictForStateMachine(String tag, Message msg, StateMachine stateMachine, WaitingState waitingState, State targetState, @HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs, boolean bypassDialog)285 public @IcmResult int manageInterfaceConflictForStateMachine(String tag, Message msg, 286 StateMachine stateMachine, WaitingState waitingState, State targetState, 287 @HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs, 288 boolean bypassDialog) { 289 synchronized (mLock) { 290 // Check if we're waiting for user approval for a different caller. 291 if (mUserApprovalPending && !TextUtils.equals(tag, mUserApprovalPendingTag)) { 292 Log.w(TAG, tag + ": rejected since there's a pending user approval for " 293 + mUserApprovalPendingTag); 294 return ICM_ABORT_COMMAND; // caller should not proceed with operation 295 } 296 297 // is this a command which was waiting for a user decision? 298 boolean isReexecutedCommand = msg.getData().getBoolean( 299 MESSAGE_BUNDLE_KEY_PENDING_USER, false); 300 // is this a command that was issued while we were already waiting for a user decision? 301 boolean wasInWaitingState = WaitingState.wasMessageInWaitingState(msg); 302 if (isReexecutedCommand || (wasInWaitingState && !mUserJustApproved)) { 303 mUserApprovalPending = false; 304 mUserApprovalPendingTag = null; 305 306 localLog(tag + ": Executing a command with user approval result: " 307 + mUserJustApproved + ", isReexecutedCommand: " + isReexecutedCommand 308 + ", wasInWaitingState: " + wasInWaitingState); 309 return mUserJustApproved ? ICM_EXECUTE_COMMAND : ICM_ABORT_COMMAND; 310 } 311 312 // Check if we're already waiting for user approval for this caller. 313 if (mUserApprovalPending) { 314 Log.w(TAG, tag 315 + ": trying for another potentially waiting operation - but should be" 316 + " in a waiting state!?"); 317 stateMachine.deferMessage(msg); 318 return ICM_SKIP_COMMAND_WAIT_FOR_USER; // same effect 319 } 320 321 // Execute the command if the dialogs aren't enabled. 322 if (!isUserApprovalEnabled()) return ICM_EXECUTE_COMMAND; 323 324 // Auto-approve dialog if bypass is specified. 325 if (bypassDialog) return ICM_EXECUTE_COMMAND; 326 327 // Check if we need to show the dialog. 328 List<Pair<Integer, WorkSource>> impact = mHdm.reportImpactToCreateIface(createIfaceType, 329 false, requestorWs); 330 localLog(tag + ": Asking user about creating the interface, impact=" + impact); 331 if (impact == null || impact.isEmpty()) { 332 localLog(tag 333 + ": Either can't create interface or can w/o sid-effects - proceeding"); 334 return ICM_EXECUTE_COMMAND; 335 } 336 337 // Auto-approve dialog if we need to delete a disconnected P2P. 338 if (mUserApprovalNotRequireForDisconnectedP2p && !mIsP2pConnected 339 && impact.size() == 1 && impact.get(0).first == HDM_CREATE_IFACE_P2P) { 340 localLog(TAG 341 + ": existing interface is p2p and it is not connected - proceeding"); 342 return ICM_EXECUTE_COMMAND; 343 } 344 345 // Auto-approve dialog if we need to delete a opportunistic Aware. 346 if (impact.size() == 1 && impact.get(0).first == HDM_CREATE_IFACE_NAN 347 && impact.get(0).second.equals(new WorkSource(Process.WIFI_UID))) { 348 localLog(TAG + ": existing interface is NAN and it is opportunistic - proceeding"); 349 return ICM_EXECUTE_COMMAND; 350 } 351 352 boolean shouldShowDialogToDelete = false; 353 for (Pair<Integer, WorkSource> ifaceToDelete : impact) { 354 if (needsUserApprovalToDelete( 355 createIfaceType, mWifiInjector.makeWsHelper(requestorWs), 356 ifaceToDelete.first, mWifiInjector.makeWsHelper(ifaceToDelete.second))) { 357 shouldShowDialogToDelete = true; 358 break; 359 } 360 } 361 // None of the interfaces to delete require us to show a dialog. 362 if (!shouldShowDialogToDelete) { 363 return ICM_EXECUTE_COMMAND; 364 } 365 366 // defer message to have it executed again automatically when switching 367 // states - want to do it now so that it will be at the top of the queue 368 // when we switch back. Will need to skip it if the user rejected it! 369 msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_PENDING_USER, true); 370 stateMachine.deferMessage(msg); 371 stateMachine.transitionTo(waitingState); 372 373 mUserApprovalPending = true; 374 mUserApprovalPendingTag = tag; 375 mCurrentWaitingState = waitingState; 376 mCurrentTargetState = targetState; 377 mUserJustApproved = false; 378 mCurrentDialogHandle = createUserApprovalDialog(createIfaceType, requestorWs, impact, 379 (result) -> { 380 localLog(tag + ": User response to creating " + getInterfaceName( 381 createIfaceType) + ": " + result); 382 mUserJustApproved = result; 383 mCurrentWaitingState = null; 384 mCurrentTargetState = null; 385 mCurrentDialogHandle = null; 386 waitingState.sendTransitionStateCommand(targetState); 387 }); 388 mCurrentDialogHandle.launchDialog(); 389 390 return ICM_SKIP_COMMAND_WAIT_FOR_USER; 391 } 392 } 393 394 /** 395 * Trigger a dialog which requests user approval to resolve an interface priority confict. 396 * 397 * @param createIfaceType The interface to be created. 398 * @param requestorWs The WorkSource of the requesting application. 399 * @param impact The impact of creating this interface (a list of interfaces to be deleted and 400 * their corresponding impacted WorkSources). 401 * @param handleResult A Consumer to execute with results. 402 */ createUserApprovalDialog( @alDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs, List<Pair<Integer, WorkSource>> impact, Consumer<Boolean> handleResult)403 private WifiDialogManager.DialogHandle createUserApprovalDialog( 404 @HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, 405 WorkSource requestorWs, 406 List<Pair<Integer, WorkSource>> impact, 407 Consumer<Boolean> handleResult) { 408 localLog("displayUserApprovalDialog: createIfaceType=" + createIfaceType 409 + ", requestorWs=" + requestorWs + ", impact=" + impact); 410 411 CharSequence requestorAppName = mFrameworkFacade.getAppName(mContext, 412 requestorWs.getPackageName(0), requestorWs.getUid(0)); 413 String requestedInterface = getInterfaceName(createIfaceType); 414 Set<String> impactedInterfacesSet = new HashSet<>(); 415 Set<String> impactedPackagesSet = new HashSet<>(); 416 for (Pair<Integer, WorkSource> detail : impact) { 417 impactedInterfacesSet.add(getInterfaceName(detail.first)); 418 for (int j = 0; j < detail.second.size(); ++j) { 419 impactedPackagesSet.add( 420 mFrameworkFacade.getAppName(mContext, detail.second.getPackageName(j), 421 detail.second.getUid(j)).toString()); 422 } 423 } 424 String impactedPackages = TextUtils.join(", ", impactedPackagesSet); 425 String impactedInterfaces = TextUtils.join(", ", impactedInterfacesSet); 426 427 Resources res = mContext.getResources(); 428 return mWifiDialogManager.createSimpleDialog( 429 res.getString(R.string.wifi_interface_priority_title, 430 requestorAppName, requestedInterface, impactedPackages, impactedInterfaces), 431 impactedPackagesSet.size() == 1 ? res.getString( 432 R.string.wifi_interface_priority_message, requestorAppName, 433 requestedInterface, impactedPackages, impactedInterfaces) 434 : res.getString(R.string.wifi_interface_priority_message_plural, 435 requestorAppName, requestedInterface, impactedPackages, 436 impactedInterfaces), 437 res.getString(R.string.wifi_interface_priority_approve), 438 res.getString(R.string.wifi_interface_priority_reject), 439 null, 440 new WifiDialogManager.SimpleDialogCallback() { 441 @Override 442 public void onPositiveButtonClicked() { 443 localLog("User approved request for " + getInterfaceName( 444 createIfaceType)); 445 handleResult.accept(true); 446 } 447 448 @Override 449 public void onNegativeButtonClicked() { 450 localLog("User rejected request for " + getInterfaceName( 451 createIfaceType)); 452 handleResult.accept(false); 453 } 454 455 @Override 456 public void onNeutralButtonClicked() { 457 onNegativeButtonClicked(); 458 } 459 460 @Override 461 public void onCancelled() { 462 onNegativeButtonClicked(); 463 } 464 }, mThreadRunner); 465 } 466 467 private String getInterfaceName(@HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType) { 468 Resources res = mContext.getResources(); 469 switch (createIfaceType) { 470 case HDM_CREATE_IFACE_STA: 471 return res.getString(R.string.wifi_interface_priority_interface_name_sta); 472 case HDM_CREATE_IFACE_AP: 473 return res.getString(R.string.wifi_interface_priority_interface_name_ap); 474 case HDM_CREATE_IFACE_AP_BRIDGE: 475 return res.getString( 476 R.string.wifi_interface_priority_interface_name_ap_bridge); 477 case HDM_CREATE_IFACE_P2P: 478 return res.getString(R.string.wifi_interface_priority_interface_name_p2p); 479 case HDM_CREATE_IFACE_NAN: 480 return res.getString(R.string.wifi_interface_priority_interface_name_nan); 481 } 482 return "Unknown"; 483 } 484 485 /** 486 * Reset the current state of InterfaceConflictManager, dismiss any open dialogs, and transition 487 * any waiting StateMachines back to their target state. 488 */ 489 public void reset() { 490 synchronized (mLock) { 491 if (mCurrentWaitingState != null && mCurrentTargetState != null) { 492 mCurrentWaitingState.sendTransitionStateCommand(mCurrentTargetState); 493 } 494 mCurrentWaitingState = null; 495 mCurrentTargetState = null; 496 if (mCurrentDialogHandle != null) { 497 mCurrentDialogHandle.dismissDialog(); 498 } 499 mUserApprovalPending = false; 500 mUserApprovalPendingTag = null; 501 mUserJustApproved = false; 502 } 503 } 504 505 /** 506 * Initialization after boot completes to get boot-dependent resources. 507 */ 508 public void handleBootCompleted() { 509 Resources res = mContext.getResources(); 510 mUserApprovalNeeded = res.getBoolean( 511 R.bool.config_wifiUserApprovalRequiredForD2dInterfacePriority); 512 String[] packageList = res.getStringArray( 513 R.array.config_wifiExcludedFromUserApprovalForD2dInterfacePriority); 514 mUserApprovalExemptedPackages = 515 (packageList == null || packageList.length == 0) ? Collections.emptySet() 516 : new ArraySet<>(packageList); 517 mUserApprovalNotRequireForDisconnectedP2p = res.getBoolean( 518 R.bool.config_wifiUserApprovalNotRequireForDisconnectedP2p); 519 } 520 521 // A helper to log debugging information in the local log buffer, which can 522 // be retrieved in bugreport. It is also used to print the log in the console. 523 private void localLog(String log) { 524 mLocalLog.log(log); 525 if (mVerboseLoggingEnabled) { 526 Log.d(TAG, log, null); 527 } 528 } 529 530 /** 531 * Dump the internal state of the class. 532 */ 533 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 534 pw.println("dump of " + TAG + ":"); 535 pw.println(" mUserApprovalNeeded=" + mUserApprovalNeeded); 536 pw.println(" mUserApprovalNeededOverride=" + mUserApprovalNeededOverride); 537 pw.println(" mUserApprovalNeededOverrideValue=" + mUserApprovalNeededOverrideValue); 538 pw.println(" mUserApprovalPending=" + mUserApprovalPending); 539 pw.println(" mUserApprovalPendingTag=" + mUserApprovalPendingTag); 540 pw.println(" mUserJustApproved=" + mUserJustApproved); 541 pw.println(" mUserApprovalNotRequireForDisconnectedP2p=" 542 + mUserApprovalNotRequireForDisconnectedP2p); 543 mLocalLog.dump(pw); 544 } 545 } 546