380 lines
17 KiB
Java
380 lines
17 KiB
Java
|
/*
|
||
|
* Copyright (C) 2022 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package com.android.server.wifi;
|
||
|
|
||
|
import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP;
|
||
|
import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP_BRIDGE;
|
||
|
import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_NAN;
|
||
|
import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_P2P;
|
||
|
import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_STA;
|
||
|
|
||
|
import android.annotation.IntDef;
|
||
|
import android.content.res.Resources;
|
||
|
import android.net.wifi.WifiContext;
|
||
|
import android.os.Message;
|
||
|
import android.os.WorkSource;
|
||
|
import android.text.TextUtils;
|
||
|
import android.util.ArraySet;
|
||
|
import android.util.Log;
|
||
|
import android.util.Pair;
|
||
|
|
||
|
import com.android.internal.util.State;
|
||
|
import com.android.internal.util.StateMachine;
|
||
|
import com.android.server.wifi.util.WaitingState;
|
||
|
import com.android.wifi.resources.R;
|
||
|
|
||
|
import java.io.FileDescriptor;
|
||
|
import java.io.PrintWriter;
|
||
|
import java.lang.annotation.Retention;
|
||
|
import java.lang.annotation.RetentionPolicy;
|
||
|
import java.util.Collections;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.List;
|
||
|
import java.util.Set;
|
||
|
import java.util.function.Consumer;
|
||
|
|
||
|
/**
|
||
|
* Displays dialogs asking the user to approve or reject interface priority decisions.
|
||
|
*/
|
||
|
public class InterfaceConflictManager {
|
||
|
private static final String TAG = "InterfaceConflictManager";
|
||
|
private boolean mVerboseLoggingEnabled = false;
|
||
|
|
||
|
private final WifiContext mContext;
|
||
|
private final FrameworkFacade mFrameworkFacade;
|
||
|
private final HalDeviceManager mHdm;
|
||
|
private final WifiThreadRunner mThreadRunner;
|
||
|
private final WifiDialogManager mWifiDialogManager;
|
||
|
|
||
|
private final Resources mResources;
|
||
|
private final boolean mUserApprovalNeeded;
|
||
|
private final Set<String> mUserApprovalExemptedPackages;
|
||
|
private boolean mUserApprovalNeededOverride = false;
|
||
|
private boolean mUserApprovalNeededOverrideValue = false;
|
||
|
|
||
|
private Object mLock = new Object();
|
||
|
private boolean mUserApprovalPending = false;
|
||
|
private String mUserApprovalPendingTag = null;
|
||
|
private boolean mUserJustApproved = false;
|
||
|
|
||
|
private static final String MESSAGE_BUNDLE_KEY_PENDING_USER = "pending_user_decision";
|
||
|
|
||
|
public InterfaceConflictManager(WifiContext wifiContext, FrameworkFacade frameworkFacade,
|
||
|
HalDeviceManager hdm, WifiThreadRunner threadRunner,
|
||
|
WifiDialogManager wifiDialogManager) {
|
||
|
mContext = wifiContext;
|
||
|
mFrameworkFacade = frameworkFacade;
|
||
|
mHdm = hdm;
|
||
|
mThreadRunner = threadRunner;
|
||
|
mWifiDialogManager = wifiDialogManager;
|
||
|
|
||
|
mResources = mContext.getResources();
|
||
|
mUserApprovalNeeded = mResources.getBoolean(
|
||
|
R.bool.config_wifiUserApprovalRequiredForD2dInterfacePriority);
|
||
|
String[] packageList = mResources.getStringArray(
|
||
|
R.array.config_wifiExcludedFromUserApprovalForD2dInterfacePriority);
|
||
|
mUserApprovalExemptedPackages =
|
||
|
(packageList == null || packageList.length == 0) ? Collections.emptySet()
|
||
|
: new ArraySet<>(packageList);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable verbose logging.
|
||
|
*/
|
||
|
public void enableVerboseLogging(boolean verboseEnabled) {
|
||
|
mVerboseLoggingEnabled = verboseEnabled;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an indication as to whether user approval is needed for this specific request. User
|
||
|
* approval is controlled by:
|
||
|
* - A global overlay `config_wifiUserApprovalRequiredForD2dInterfacePriority`
|
||
|
* - An exemption list overlay `config_wifiExcludedFromUserApprovalForD2dInterfacePriority`
|
||
|
* which is a list of packages which are *exempted* from user approval
|
||
|
* - A shell command which can be used to override
|
||
|
*
|
||
|
* @param requestorWs The WorkSource of the requestor - used to determine whether it is exempted
|
||
|
* from user approval. All requesting packages must be exempted for the
|
||
|
* dialog to NOT be displayed.
|
||
|
*/
|
||
|
private boolean isUserApprovalNeeded(WorkSource requestorWs) {
|
||
|
if (mUserApprovalNeededOverride) return mUserApprovalNeededOverrideValue;
|
||
|
if (!mUserApprovalNeeded || mUserApprovalExemptedPackages.isEmpty()) {
|
||
|
return mUserApprovalNeeded;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < requestorWs.size(); ++i) {
|
||
|
if (!mUserApprovalExemptedPackages.contains(requestorWs.getPackageName(i))) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false; // all packages of the requestor are excluded
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Override (potentially) the user approval needed device configuration. Intended for debugging
|
||
|
* via the shell command.
|
||
|
*
|
||
|
* @param override Enable overriding the default.
|
||
|
* @param overrideValue The actual override value (i.e. disable or enable).
|
||
|
*/
|
||
|
public void setUserApprovalNeededOverride(boolean override, boolean overrideValue) {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "setUserApprovalNeededOverride: override=" + override + ", overrideValue="
|
||
|
+ overrideValue);
|
||
|
}
|
||
|
mUserApprovalNeededOverride = override;
|
||
|
mUserApprovalNeededOverrideValue = overrideValue;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return values for {@link #manageInterfaceConflictForStateMachine}
|
||
|
*/
|
||
|
|
||
|
// Caller should continue and execute command: no need for user approval, or user approval
|
||
|
// already granted, or command bound to fail so just fail through the normal path
|
||
|
public static final int ICM_EXECUTE_COMMAND = 0;
|
||
|
|
||
|
// Caller should skip executing the command for now (do not defer it - already done!). The user
|
||
|
// was asked for permission and the command will be executed again when we get a response.
|
||
|
public static final int ICM_SKIP_COMMAND_WAIT_FOR_USER = 1;
|
||
|
|
||
|
// Caller should abort the command and execute whatever failure code is necessary - this
|
||
|
// command was rejected by the user or we cannot ask the user since there's a pending user
|
||
|
// request.
|
||
|
public static final int ICM_ABORT_COMMAND = 2;
|
||
|
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
@IntDef(prefix = {"ICM_"}, value = {
|
||
|
ICM_EXECUTE_COMMAND,
|
||
|
ICM_SKIP_COMMAND_WAIT_FOR_USER,
|
||
|
ICM_ABORT_COMMAND
|
||
|
})
|
||
|
@interface IcmResult {}
|
||
|
|
||
|
/**
|
||
|
* Manages interface conflicts for a State Machine based caller. Possible scenarios:
|
||
|
* - New request:
|
||
|
* - ok to proceed inline (i.e. caller can just proceed normally - no conflict)
|
||
|
* [nop]
|
||
|
* - need to request user approval (there's conflict, caller need to wait for user response)
|
||
|
* [msg get tagged + deferred, transition to waiting state]
|
||
|
* - Previously executed command (i.e. already asked the user)
|
||
|
* - user rejected request
|
||
|
* [discard request, execute any necessary error callbacks]
|
||
|
* - user approved request
|
||
|
* [~nop (i.e. proceed)]
|
||
|
* - Busy asking approval for another request:
|
||
|
* - If from another caller: reject
|
||
|
* - If from the same caller: defer the caller (possibly will be approved when gets to ask
|
||
|
* again).
|
||
|
*
|
||
|
* Synchronization:
|
||
|
* - Multiple threads accessing this method will be blocked until the processing of the other
|
||
|
* thread is done. The "processing" is simply the decision making - i.e. not the waiting for
|
||
|
* user response.
|
||
|
* - If a user response is pending then subsequent requests are auto-rejected if they require
|
||
|
* user approval. Note that this will result in race condition if this approval changes
|
||
|
* the conditions for the user approval request: e.g. it may increase the impact of a user
|
||
|
* approval (w/o telling the user) or it may be rejected even if approved by the user (if
|
||
|
* the newly allocated interface now has higher priority).
|
||
|
*
|
||
|
* @param tag Tag of the caller for logging
|
||
|
* @param msg The command which needs to be evaluated or executed for user approval
|
||
|
* @param stateMachine The source state machine
|
||
|
* @param waitingState The {@link WaitingState} added to the above state machine
|
||
|
* @param targetState The target state to transition to on user response
|
||
|
* @param createIfaceType The interface which needs to be created
|
||
|
* @param requestorWs The requestor WorkSource
|
||
|
*
|
||
|
* @return ICM_EXECUTE_COMMAND caller should execute the command,
|
||
|
* ICM_SKIP_COMMAND_WAIT_FOR_USER caller should skip the command (for now),
|
||
|
* ICM_ABORT_COMMAND caller should abort this command and execute whatever failure code is
|
||
|
* necessary.
|
||
|
*/
|
||
|
public @IcmResult int manageInterfaceConflictForStateMachine(String tag, Message msg,
|
||
|
StateMachine stateMachine, WaitingState waitingState, State targetState,
|
||
|
@HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs) {
|
||
|
synchronized (mLock) {
|
||
|
if (mUserApprovalPending && !TextUtils.equals(tag, mUserApprovalPendingTag)) {
|
||
|
Log.w(TAG, tag + ": rejected since there's a pending user approval for "
|
||
|
+ mUserApprovalPendingTag);
|
||
|
return ICM_ABORT_COMMAND; // caller should not proceed with operation
|
||
|
}
|
||
|
|
||
|
// is this a command which was waiting for a user decision?
|
||
|
boolean isReexecutedCommand = msg.getData().getBoolean(
|
||
|
MESSAGE_BUNDLE_KEY_PENDING_USER, false);
|
||
|
if (isReexecutedCommand) {
|
||
|
mUserApprovalPending = false;
|
||
|
mUserApprovalPendingTag = null;
|
||
|
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, tag + ": Re-executing a command with user approval result - "
|
||
|
+ mUserJustApproved);
|
||
|
}
|
||
|
return mUserJustApproved ? ICM_EXECUTE_COMMAND : ICM_ABORT_COMMAND;
|
||
|
}
|
||
|
|
||
|
if (mUserApprovalPending) {
|
||
|
Log.w(TAG, tag
|
||
|
+ ": trying for another potentially waiting operation - but should be"
|
||
|
+ " in a waiting state!?");
|
||
|
stateMachine.deferMessage(msg);
|
||
|
return ICM_SKIP_COMMAND_WAIT_FOR_USER; // same effect
|
||
|
}
|
||
|
|
||
|
if (!isUserApprovalNeeded(requestorWs)) return ICM_EXECUTE_COMMAND;
|
||
|
|
||
|
List<Pair<Integer, WorkSource>> impact = mHdm.reportImpactToCreateIface(createIfaceType,
|
||
|
false, requestorWs);
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, tag + ": Asking user about creating the interface, impact=" + impact);
|
||
|
}
|
||
|
if (impact == null || impact.isEmpty()) {
|
||
|
Log.d(TAG, tag
|
||
|
+ ": Either can't create interface or can w/o sid-effects - proceeding");
|
||
|
return ICM_EXECUTE_COMMAND;
|
||
|
}
|
||
|
|
||
|
displayUserApprovalDialog(createIfaceType, requestorWs, impact,
|
||
|
(result) -> {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, tag + ": User response to creating " + getInterfaceName(
|
||
|
createIfaceType) + ": " + result);
|
||
|
}
|
||
|
mUserJustApproved = result;
|
||
|
waitingState.sendTransitionStateCommand(targetState);
|
||
|
});
|
||
|
// defer message to have it executed again automatically when switching
|
||
|
// states - want to do it now so that it will be at the top of the queue
|
||
|
// when we switch back. Will need to skip it if the user rejected it!
|
||
|
msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_PENDING_USER, true);
|
||
|
stateMachine.deferMessage(msg);
|
||
|
stateMachine.transitionTo(waitingState);
|
||
|
|
||
|
mUserApprovalPending = true;
|
||
|
mUserApprovalPendingTag = tag;
|
||
|
|
||
|
return ICM_SKIP_COMMAND_WAIT_FOR_USER;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Trigger a dialog which requests user approval to resolve an interface priority confict.
|
||
|
*
|
||
|
* @param createIfaceType The interface to be created.
|
||
|
* @param requestorWs The WorkSource of the requesting application.
|
||
|
* @param impact The impact of creating this interface (a list of interfaces to be deleted and
|
||
|
* their corresponding impacted WorkSources).
|
||
|
* @param handleResult A Consumer to execute with results.
|
||
|
*/
|
||
|
private void displayUserApprovalDialog(
|
||
|
@HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType,
|
||
|
WorkSource requestorWs,
|
||
|
List<Pair<Integer, WorkSource>> impact,
|
||
|
Consumer<Boolean> handleResult) {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "displayUserApprovalDialog: createIfaceType=" + createIfaceType
|
||
|
+ ", requestorWs=" + requestorWs + ", impact=" + impact);
|
||
|
}
|
||
|
|
||
|
CharSequence requestorAppName = mFrameworkFacade.getAppName(mContext,
|
||
|
requestorWs.getPackageName(0), requestorWs.getUid(0));
|
||
|
String requestedInterface = getInterfaceName(createIfaceType);
|
||
|
Set<String> impactedPackagesSet = new HashSet<>();
|
||
|
for (Pair<Integer, WorkSource> detail : impact) {
|
||
|
for (int j = 0; j < detail.second.size(); ++j) {
|
||
|
impactedPackagesSet.add(
|
||
|
mFrameworkFacade.getAppName(mContext, detail.second.getPackageName(j),
|
||
|
detail.second.getUid(j)).toString());
|
||
|
}
|
||
|
}
|
||
|
String impactedPackages = TextUtils.join(", ", impactedPackagesSet);
|
||
|
|
||
|
mWifiDialogManager.createSimpleDialog(
|
||
|
mResources.getString(R.string.wifi_interface_priority_title, requestorAppName),
|
||
|
impactedPackagesSet.size() == 1 ? mResources.getString(
|
||
|
R.string.wifi_interface_priority_message, requestorAppName,
|
||
|
requestedInterface, impactedPackages)
|
||
|
: mResources.getString(R.string.wifi_interface_priority_message_plural,
|
||
|
requestorAppName, requestedInterface, impactedPackages),
|
||
|
mResources.getString(R.string.wifi_interface_priority_approve),
|
||
|
mResources.getString(R.string.wifi_interface_priority_reject),
|
||
|
null,
|
||
|
new WifiDialogManager.SimpleDialogCallback() {
|
||
|
@Override
|
||
|
public void onPositiveButtonClicked() {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "User approved request for " + getInterfaceName(
|
||
|
createIfaceType));
|
||
|
}
|
||
|
handleResult.accept(true);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onNegativeButtonClicked() {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "User rejected request for " + getInterfaceName(
|
||
|
createIfaceType));
|
||
|
}
|
||
|
handleResult.accept(false);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onNeutralButtonClicked() {
|
||
|
onNegativeButtonClicked();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onCancelled() {
|
||
|
onNegativeButtonClicked();
|
||
|
}
|
||
|
}, mThreadRunner).launchDialog();
|
||
|
}
|
||
|
|
||
|
private String getInterfaceName(@HalDeviceManager.HdmIfaceTypeForCreation int createIfaceType) {
|
||
|
switch (createIfaceType) {
|
||
|
case HDM_CREATE_IFACE_STA:
|
||
|
return "STA";
|
||
|
case HDM_CREATE_IFACE_AP:
|
||
|
return "AP";
|
||
|
case HDM_CREATE_IFACE_AP_BRIDGE:
|
||
|
return "AP";
|
||
|
case HDM_CREATE_IFACE_P2P:
|
||
|
return "Wi-Fi Direct";
|
||
|
case HDM_CREATE_IFACE_NAN:
|
||
|
return "Wi-Fi Aware";
|
||
|
}
|
||
|
return "Unknown";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Dump the internal state of the class.
|
||
|
*/
|
||
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||
|
pw.println("dump of " + TAG + ":");
|
||
|
pw.println(" mUserApprovalNeeded=" + mUserApprovalNeeded);
|
||
|
pw.println(" mUserApprovalNeededOverride=" + mUserApprovalNeededOverride);
|
||
|
pw.println(" mUserApprovalNeededOverrideValue=" + mUserApprovalNeededOverrideValue);
|
||
|
pw.println(" mUserApprovalPending=" + mUserApprovalPending);
|
||
|
pw.println(" mUserApprovalPendingTag=" + mUserApprovalPendingTag);
|
||
|
pw.println(" mUserJustApproved=" + mUserJustApproved);
|
||
|
}
|
||
|
}
|