• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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