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