diff options
author | 2022-01-10 10:22:25 -0800 | |
---|---|---|
committer | 2022-01-26 10:37:58 -0800 | |
commit | ca3c5d996523d363da82b2215c4e4b8abcc414ad (patch) | |
tree | f2100d887d983b1282b98ae102744fdf331ffa2d | |
parent | eaf49ae38e3d8f189f3aaa2f89688b499d5bbd19 (diff) |
Plumb reply to P2P Invitation Received dialog back to framework
Plumb the reply to the P2P Invitation Recieved dialog back to the
framework via @hide APIs.
Bug: 209032090
Test: atest WifiDialogManagerTest, Tap on dialog from
`adb shell cmd launch-dialog-p2p-invitation-received <ssid>` and verify
WifiDialogManager log for dialog response.
Change-Id: Id0792073fee71a0829f2178f47570de85d891a01
9 files changed, 275 insertions, 12 deletions
diff --git a/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java b/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java index dadebcc62f..409c08e2d0 100644 --- a/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java +++ b/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java @@ -327,28 +327,27 @@ public class WifiDialogActivity extends Activity { Log.v(TAG, "P2P Invitation Received Dialog id=" + dialogId + " accepted with pin=" + pin); } - // TODO: Plumb this response to framework. + getWifiManager().replyToP2pInvitationReceivedDialog(dialogId, true, pin); }) .setNegativeButton(getStringId("decline"), (dialogNegative, which) -> { if (mIsVerboseLoggingEnabled) { Log.v(TAG, "P2P Invitation Received dialog id=" + dialogId + " declined."); } - // TODO: Plumb this response to framework. + getWifiManager().replyToP2pInvitationReceivedDialog(dialogId, false, null); }) .setOnCancelListener((dialogCancel) -> { if (mIsVerboseLoggingEnabled) { Log.v(TAG, "P2P Invitation Received dialog id=" + dialogId + " cancelled."); } - // TODO: Plumb this response to framework. + getWifiManager().replyToP2pInvitationReceivedDialog(dialogId, false, null); }) .setOnDismissListener((dialogDismiss) -> { if (mIsVerboseLoggingEnabled) { Log.v(TAG, "P2P Invitation Received dialog id=" + dialogId + " dismissed."); } - // TODO: Plumb this response to framework. removeIntentAndPossiblyFinish(dialogId); }) .create(); diff --git a/framework/java/android/net/wifi/BaseWifiService.java b/framework/java/android/net/wifi/BaseWifiService.java index b5d3160f78..61941595dd 100644 --- a/framework/java/android/net/wifi/BaseWifiService.java +++ b/framework/java/android/net/wifi/BaseWifiService.java @@ -854,5 +854,10 @@ public class BaseWifiService extends IWifiManager.Stub { public void validateCurrentWifiMeetsAdminRequirements() { throw new UnsupportedOperationException(); } -} + @Override + public void replyToP2pInvitationReceivedDialog( + int dialogId, boolean accepted, @Nullable String optionalPin) { + throw new UnsupportedOperationException(); + } +} diff --git a/framework/java/android/net/wifi/IWifiManager.aidl b/framework/java/android/net/wifi/IWifiManager.aidl index 5b346caece..ccac485178 100644 --- a/framework/java/android/net/wifi/IWifiManager.aidl +++ b/framework/java/android/net/wifi/IWifiManager.aidl @@ -374,4 +374,6 @@ interface IWifiManager boolean setStaConcurrencyForMultiInternetMode(int mode); void validateCurrentWifiMeetsAdminRequirements(); + + void replyToP2pInvitationReceivedDialog(int dialogId, boolean accepted, String optionalPin); } diff --git a/framework/java/android/net/wifi/WifiManager.java b/framework/java/android/net/wifi/WifiManager.java index 36e3b79248..6b4fc9e362 100644 --- a/framework/java/android/net/wifi/WifiManager.java +++ b/framework/java/android/net/wifi/WifiManager.java @@ -9121,4 +9121,26 @@ public class WifiManager { * @hide */ public static final String EXTRA_P2P_DISPLAY_PIN = "android.net.wifi.extra.P2P_DISPLAY_PIN"; + + /** + * Method for WifiDialog to notify the framework of a reply to a P2P Invitation Received dialog. + * @param dialogId id of the replying dialog. + * @param accepted Whether the invitation was accepted. + * @param optionalPin PIN of the reply, or {@code null} if none was supplied. + * @hide + */ + public void replyToP2pInvitationReceivedDialog( + int dialogId, boolean accepted, @Nullable String optionalPin) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "notifyP2pInvitationResponse: " + + "dialogId=" + dialogId + + ", accepted=" + accepted + + ", pin=" + optionalPin); + } + try { + mService.replyToP2pInvitationReceivedDialog(dialogId, accepted, optionalPin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/service/java/com/android/server/wifi/WifiDialogManager.java b/service/java/com/android/server/wifi/WifiDialogManager.java index 5adf96709a..a00d47f0b5 100644 --- a/service/java/com/android/server/wifi/WifiDialogManager.java +++ b/service/java/com/android/server/wifi/WifiDialogManager.java @@ -21,12 +21,15 @@ import android.content.pm.PackageManager; import android.net.wifi.WifiContext; import android.net.wifi.WifiManager; import android.os.UserHandle; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Set; + /** * Class to manage launching dialogs via WifiDialog and returning the user reply. */ @@ -38,8 +41,11 @@ public class WifiDialogManager { private boolean mVerboseLoggingEnabled; private int mNextDialogId = 0; + private Set<Integer> mCurrentDialogIds = new ArraySet<>(); private @NonNull SparseArray<P2pInvitationReceivedDialogCallback> mP2pInvitationReceivedDialogCallbacks = new SparseArray<>(); + private @NonNull SparseArray<WifiThreadRunner> + mP2pInvitationReceivedDialogThreadRunners = new SparseArray<>(); @NonNull WifiContext mContext; @NonNull PackageManager mPackageManager; @@ -57,7 +63,7 @@ public class WifiDialogManager { } private int getNextDialogId() { - if (mNextDialogId < 0) { + if (mCurrentDialogIds.isEmpty() || mNextDialogId < 0) { mNextDialogId = 0; } return mNextDialogId++; @@ -84,19 +90,26 @@ public class WifiDialogManager { * @param deviceName Name of the device sending the invitation. * @param isPinRequested True if a PIN was requested and a PIN input UI should be shown. * @param displayPin Display PIN, or {@code null} if no PIN should be displayed - * @param callback Callback to receive the dialog response. Runs on the main Wi-Fi thread. + * @param callback Callback to receive the dialog response. + * @param threadRunner WifiThreadRunner to run the callback on. * @return id of the launched dialog, or {@code -1} if the dialog could not be created. */ public int launchP2pInvitationReceivedDialog( String deviceName, boolean isPinRequested, @Nullable String displayPin, - @NonNull P2pInvitationReceivedDialogCallback callback) { + @NonNull P2pInvitationReceivedDialogCallback callback, + @NonNull WifiThreadRunner threadRunner) { if (callback == null) { Log.e(TAG, "Cannot launch a P2P Invitation Received dialog with null callback!"); return -1; } + if (callback == null) { + Log.e(TAG, "Cannot launch a P2P Invitation Received dialog with null handler!"); + return -1; + } int dialogId = getNextDialogId(); + mCurrentDialogIds.add(dialogId); Intent intent = new Intent(); String wifiDialogApkPkgName = mContext.getWifiDialogApkPkgName(); if (wifiDialogApkPkgName == null) { @@ -113,6 +126,7 @@ public class WifiDialogManager { intent.putExtra(WifiManager.EXTRA_P2P_DISPLAY_PIN, displayPin); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mP2pInvitationReceivedDialogCallbacks.put(dialogId, callback); + mP2pInvitationReceivedDialogThreadRunners.put(dialogId, threadRunner); mContext.startActivityAsUser(intent, UserHandle.CURRENT); if (mVerboseLoggingEnabled) { Log.v(TAG, "Launching P2P Invitation Received dialog." @@ -124,4 +138,49 @@ public class WifiDialogManager { } return dialogId; } + + /** + * Returns the reply to a P2P Invitation Received dialog to the callback of matching dialogId. + * @param dialogId id of the replying dialog. + * @param accepted Whether the invitation was accepted. + * @param optionalPin PIN of the reply, or {@code null} if none was supplied. + * @hide + */ + public void replyToP2pInvitationReceivedDialog( + final int dialogId, final boolean accepted, final @Nullable String optionalPin) { + if (mVerboseLoggingEnabled) { + Log.i(TAG, "Response received for P2P Invitation Received dialog." + + " id=" + dialogId + + " accepted=" + accepted + + " pin=" + optionalPin); + } + mCurrentDialogIds.remove(dialogId); + final P2pInvitationReceivedDialogCallback callback = + mP2pInvitationReceivedDialogCallbacks.get(dialogId); + mP2pInvitationReceivedDialogCallbacks.remove(dialogId); + if (callback == null) { + if (mVerboseLoggingEnabled) { + Log.w(TAG, "No matching callback for P2P Invitation Received dialog" + + " id=" + dialogId); + } + return; + } + final WifiThreadRunner threadRunner = + mP2pInvitationReceivedDialogThreadRunners.get(dialogId); + mP2pInvitationReceivedDialogThreadRunners.remove(dialogId); + if (threadRunner == null) { + if (mVerboseLoggingEnabled) { + Log.w(TAG, "No matching handler for P2P Invitation Received dialog" + + " id=" + dialogId); + } + return; + } + threadRunner.post(() -> { + if (accepted) { + callback.onAccepted(optionalPin); + } else { + callback.onDeclined(); + } + }); + } } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 228ffc69ab..e010a8d989 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -92,6 +92,7 @@ import android.net.wifi.WifiAnnotations.WifiStandard; import android.net.wifi.WifiAvailableChannel; import android.net.wifi.WifiClient; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiContext; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.AddNetworkResult; @@ -204,7 +205,7 @@ public class WifiServiceImpl extends BaseWifiService { private final ActiveModeWarden mActiveModeWarden; private final ScanRequestProxy mScanRequestProxy; - private final Context mContext; + private final WifiContext mContext; private final FrameworkFacade mFacade; private final Clock mClock; @@ -306,6 +307,7 @@ public class WifiServiceImpl extends BaseWifiService { private final SimRequiredNotifier mSimRequiredNotifier; private final MakeBeforeBreakManager mMakeBeforeBreakManager; private final LastCallerInfoManager mLastCallerInfoManager; + private final @NonNull WifiDialogManager mWifiDialogManager; private boolean mWifiTetheringDisallowed; private boolean mIsBootComplete; @@ -427,7 +429,7 @@ public class WifiServiceImpl extends BaseWifiService { } - public WifiServiceImpl(Context context, WifiInjector wifiInjector) { + public WifiServiceImpl(WifiContext context, WifiInjector wifiInjector) { mContext = context; mWifiInjector = wifiInjector; mClock = wifiInjector.getClock(); @@ -475,6 +477,7 @@ public class WifiServiceImpl extends BaseWifiService { mWifiCarrierInfoManager = wifiInjector.getWifiCarrierInfoManager(); mMakeBeforeBreakManager = mWifiInjector.getMakeBeforeBreakManager(); mLastCallerInfoManager = mWifiInjector.getLastCallerInfoManager(); + mWifiDialogManager = mWifiInjector.getWifiDialogManager(); mBuildProperties = mWifiInjector.getBuildProperties(); mDefaultClientModeManager = mWifiInjector.getDefaultClientModeManager(); mCountryCodeTracker = new CountryCodeTracker(); @@ -6380,4 +6383,28 @@ public class WifiServiceImpl extends BaseWifiService { } }); } + + /** + * Method for WifiDialog to notify the framework of a reply to a P2P Invitation Received dialog. + * @param dialogId id of the replying dialog. + * @param accepted Whether the invitation was accepted. + * @param optionalPin PIN of the reply, or {@code null} if none was supplied. + */ + @Override + public void replyToP2pInvitationReceivedDialog( + int dialogId, boolean accepted, @Nullable String optionalPin) { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + mWifiPermissionsUtil.checkPackage(uid, mContext.getWifiDialogApkPkgName()); + if (isVerboseLoggingEnabled()) { + mLog.info("replyToP2pInvitationReceivedDialog uid=% pid=%" + + " dialogId=% accepted=% optionalPin=%") + .c(uid).c(pid).c(dialogId).c(accepted).c(optionalPin) + .flush(); + } + mWifiThreadRunner.post(() -> + mWifiDialogManager.replyToP2pInvitationReceivedDialog( + dialogId, accepted, optionalPin) + ); + } } diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java index fe0aae408a..4dba1b1f4a 100644 --- a/service/java/com/android/server/wifi/WifiShellCommand.java +++ b/service/java/com/android/server/wifi/WifiShellCommand.java @@ -1171,7 +1171,8 @@ public class WifiShellCommand extends BasicShellCommandHandler { deviceName, isPinRequested, displayPin, - callback); + callback, + mWifiThreadRunner); pw.println("Launched dialog. Waiting up to 15 seconds for user response."); pw.flush(); String msg = queue.poll(15, TimeUnit.SECONDS); diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java new file mode 100644 index 0000000000..3401de1171 --- /dev/null +++ b/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 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 org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.wifi.WifiContext; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link WifiDialogManager}. + */ +@SmallTest +public class WifiDialogManagerTest extends WifiBaseTest { + + @Mock WifiContext mWifiContext; + @Mock WifiThreadRunner mCallbackThreadRunner; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mWifiContext.getWifiDialogApkPkgName()).thenReturn("WifiDialogApkPkgName"); + when(mCallbackThreadRunner.post(any(Runnable.class))).then(i -> { + ((Runnable) i.getArguments()[0]).run(); + return true; + }); + } + + /** + * Verifies that launching a P2P Invitation Received dialog with a callback will result in the + * correct callback methods invoked when a response is received. + */ + @Test + public void testP2pInvitationReceivedDialog_launchAndResponse_notifiesCallback() { + WifiDialogManager wifiDialogManager = new WifiDialogManager(mWifiContext); + WifiDialogManager.P2pInvitationReceivedDialogCallback callback = + mock(WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + int dialogId; + + // Accept without PIN + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", false, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, true, null); + verify(callback, times(1)).onAccepted(null); + + // Callback should be removed from callback list, so a second notification should be ignored + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, true, "012345"); + verify(callback, times(0)).onAccepted("012345"); + + // Accept with PIN + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", true, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, true, "012345"); + verify(callback, times(1)).onAccepted("012345"); + + // Accept with PIN but PIN was not requested + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", false, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, true, "012345"); + verify(callback, times(2)).onAccepted("012345"); + + // Accept without PIN but PIN was requested + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", true, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, true, null); + verify(callback, times(2)).onAccepted(null); + + // Decline without PIN + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", false, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, false, null); + verify(callback, times(1)).onDeclined(); + + // Decline with PIN + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", true, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, false, "012345"); + verify(callback, times(2)).onDeclined(); + + // Decline with PIN but PIN was not requested + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", false, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, false, "012345"); + verify(callback, times(3)).onDeclined(); + + // Decline without PIN but PIN was requested + dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", true, null, callback, mCallbackThreadRunner); + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, false, null); + verify(callback, times(4)).onDeclined(); + } + + /** + * Verifies the right callback is notified for a response to a P2P Invitation Received dialog. + */ + @Test + public void testP2pInvitationReceivedDialog_multipleDialogs_responseMatchedToCorrectCallback() { + WifiDialogManager wifiDialogManager = new WifiDialogManager(mWifiContext); + + // Launch Dialog1 + WifiDialogManager.P2pInvitationReceivedDialogCallback callback1 = mock( + WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + int dialogId1 = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", false, null, callback1, mCallbackThreadRunner); + + // Launch Dialog2 + WifiDialogManager.P2pInvitationReceivedDialogCallback callback2 = mock( + WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + int dialogId2 = wifiDialogManager.launchP2pInvitationReceivedDialog( + "deviceName", false, null, callback2, mCallbackThreadRunner); + + // callback1 notified + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId1, true, null); + verify(callback1, times(1)).onAccepted(null); + verify(callback2, times(0)).onAccepted(null); + + // callback2 notified + wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId2, true, null); + verify(callback1, times(1)).onAccepted(null); + verify(callback2, times(1)).onAccepted(null); + } +} diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java index 86980d0032..e23559c19c 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java @@ -147,6 +147,7 @@ import android.net.wifi.SoftApInfo; import android.net.wifi.WifiClient; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WifiContext; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -313,7 +314,7 @@ public class WifiServiceImplTest extends WifiBaseTest { ArgumentCaptor.forClass(SoftApModeConfiguration.class); @Mock Bundle mBundle; - @Mock Context mContext; + @Mock WifiContext mContext; @Mock Context mContextAsUser; @Mock WifiInjector mWifiInjector; @Mock WifiCountryCode mWifiCountryCode; |