diff options
7 files changed, 381 insertions, 109 deletions
diff --git a/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java b/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java index d02ae0a979..788f8eb8f2 100644 --- a/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java +++ b/WifiDialog/src/com/android/wifi/dialog/WifiDialogActivity.java @@ -60,7 +60,7 @@ public class WifiDialogActivity extends Activity { private boolean mIsVerboseLoggingEnabled; private int mGravity = Gravity.NO_GRAVITY; - private @NonNull SparseArray<Intent> mIntentsPerId = new SparseArray<>(); + private @NonNull SparseArray<Intent> mLaunchIntentsPerId = new SparseArray<>(); private @NonNull SparseArray<Dialog> mActiveDialogsPerId = new SparseArray<>(); // Broadcast receiver for listening to ACTION_CLOSE_SYSTEM_DIALOGS @@ -154,7 +154,7 @@ public class WifiDialogActivity extends Activity { } continue; } - mIntentsPerId.put(dialogId, intent); + mLaunchIntentsPerId.put(dialogId, intent); } } @@ -167,9 +167,9 @@ public class WifiDialogActivity extends Activity { registerReceiver( mCloseSystemDialogsReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); ArraySet<Integer> invalidDialogIds = new ArraySet<>(); - for (int i = 0; i < mIntentsPerId.size(); i++) { - int dialogId = mIntentsPerId.keyAt(i); - if (!createAndShowDialogForIntent(dialogId, mIntentsPerId.get(dialogId))) { + for (int i = 0; i < mLaunchIntentsPerId.size(); i++) { + int dialogId = mLaunchIntentsPerId.keyAt(i); + if (!createAndShowDialogForIntent(dialogId, mLaunchIntentsPerId.get(dialogId))) { invalidDialogIds.add(dialogId); } } @@ -192,7 +192,12 @@ public class WifiDialogActivity extends Activity { } return; } - mIntentsPerId.put(dialogId, intent); + String action = intent.getAction(); + if (WifiManager.ACTION_CANCEL_DIALOG.equals(action)) { + removeIntentAndPossiblyFinish(dialogId); + return; + } + mLaunchIntentsPerId.put(dialogId, intent); if (!createAndShowDialogForIntent(dialogId, intent)) { removeIntentAndPossiblyFinish(dialogId); } @@ -214,8 +219,8 @@ public class WifiDialogActivity extends Activity { @Override protected void onSaveInstanceState(Bundle outState) { ArrayList<Intent> intentList = new ArrayList<>(); - for (int i = 0; i < mIntentsPerId.size(); i++) { - intentList.add(mIntentsPerId.valueAt(i)); + for (int i = 0; i < mLaunchIntentsPerId.size(); i++) { + intentList.add(mLaunchIntentsPerId.valueAt(i)); } outState.putParcelableArrayList(KEY_DIALOG_INTENTS, intentList); super.onSaveInstanceState(outState); @@ -226,7 +231,7 @@ public class WifiDialogActivity extends Activity { * there are no dialogs left to show. */ private void removeIntentAndPossiblyFinish(int dialogId) { - mIntentsPerId.remove(dialogId); + mLaunchIntentsPerId.remove(dialogId); Dialog dialog = mActiveDialogsPerId.get(dialogId); mActiveDialogsPerId.remove(dialogId); if (dialog != null && dialog.isShowing()) { @@ -235,7 +240,7 @@ public class WifiDialogActivity extends Activity { if (mIsVerboseLoggingEnabled) { Log.v(TAG, "Dialog id " + dialogId + " removed."); } - if (mIntentsPerId.size() == 0) { + if (mLaunchIntentsPerId.size() == 0) { if (mIsVerboseLoggingEnabled) { Log.v(TAG, "No dialogs left to show, finishing."); } @@ -257,6 +262,10 @@ public class WifiDialogActivity extends Activity { * Returns {@code true} if the dialog was successfully created, {@code false} otherwise. */ private @Nullable boolean createAndShowDialogForIntent(int dialogId, @NonNull Intent intent) { + String action = intent.getAction(); + if (!WifiManager.ACTION_LAUNCH_DIALOG.equals(action)) { + return false; + } Dialog dialog = null; int dialogType = intent.getIntExtra( WifiManager.EXTRA_DIALOG_TYPE, WifiManager.DIALOG_TYPE_UNKNOWN); diff --git a/framework/java/android/net/wifi/WifiManager.java b/framework/java/android/net/wifi/WifiManager.java index 65a6d2c7eb..5594efacc3 100644 --- a/framework/java/android/net/wifi/WifiManager.java +++ b/framework/java/android/net/wifi/WifiManager.java @@ -9074,6 +9074,22 @@ public class WifiManager { } /** + * Intent action to launch a dialog from the WifiDialog app. + * Must include EXTRA_DIALOG_ID, EXTRA_DIALOG_TYPE, and appropriate extras for the dialog type. + * @hide + */ + public static final String ACTION_LAUNCH_DIALOG = + "android.net.wifi.action.LAUNCH_DIALOG"; + + /** + * Intent action to cancel an existing dialog from the WifiDialog app. + * Must include EXTRA_DIALOG_ID. + * @hide + */ + public static final String ACTION_CANCEL_DIALOG = + "android.net.wifi.action.CANCEL_DIALOG"; + + /** * Unknown DialogType. * @hide */ diff --git a/service/ServiceWifiResources/res/values/config.xml b/service/ServiceWifiResources/res/values/config.xml index c5a897c3b5..669a9d978c 100644 --- a/service/ServiceWifiResources/res/values/config.xml +++ b/service/ServiceWifiResources/res/values/config.xml @@ -779,4 +779,7 @@ <!-- Indicate the gravity of Wifi dialogs. Must be an integer corresponding to a android.view.Gravity.GravityFlags constant. Default = Gravity.NO_GRAVITY (0) --> <integer translatable="false" name="config_wifiDialogGravity">0</integer> + <!-- Indicate the time in milliseconds to wait before auto-cancelling a P2P invitation received + dialog that the user has not responded to. A value of 0 indicates no timeout. --> + <integer translatable="false" name="config_p2pInvitationReceivedDialogTimeoutMs">0</integer> </resources> diff --git a/service/ServiceWifiResources/res/values/overlayable.xml b/service/ServiceWifiResources/res/values/overlayable.xml index 342bc88ccc..67d97e33a2 100644 --- a/service/ServiceWifiResources/res/values/overlayable.xml +++ b/service/ServiceWifiResources/res/values/overlayable.xml @@ -218,6 +218,7 @@ <item type="integer" name="config_wifiConfigurationRestoreNetworksBatchNum" /> <item type="bool" name="config_wifiSoftapOweTransitionSupported" /> <item type="integer" name="config_wifiDialogGravity" /> + <item type="integer" name="config_p2pInvitationReceivedDialogTimeoutMs"/> <!-- Params from config.xml that can be overlayed --> <!-- Params from strings.xml that can be overlayed --> diff --git a/service/java/com/android/server/wifi/WifiDialogManager.java b/service/java/com/android/server/wifi/WifiDialogManager.java index a00d47f0b5..d8b97f6a52 100644 --- a/service/java/com/android/server/wifi/WifiDialogManager.java +++ b/service/java/com/android/server/wifi/WifiDialogManager.java @@ -17,7 +17,6 @@ package com.android.server.wifi; import android.content.Intent; -import android.content.pm.PackageManager; import android.net.wifi.WifiContext; import android.net.wifi.WifiManager; import android.os.UserHandle; @@ -25,34 +24,39 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.AnyThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.wifi.resources.R; import java.util.Set; /** * Class to manage launching dialogs via WifiDialog and returning the user reply. + * This class is thread-safe via the use of a single WifiThreadRunner. */ public class WifiDialogManager { private static final String TAG = "WifiDialogManager"; - public static final String WIFI_DIALOG_ACTIVITY_CLASSNAME = + @VisibleForTesting static final String WIFI_DIALOG_ACTIVITY_CLASSNAME = "com.android.wifi.dialog.WifiDialogActivity"; 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<>(); + private final Set<Integer> mCurrentDialogIds = new ArraySet<>(); + private final @NonNull SparseArray<P2pInvitationReceivedDialogHandle> + mP2pInvitationReceivedDialogsHandles = new SparseArray<>(); - @NonNull WifiContext mContext; - @NonNull PackageManager mPackageManager; + private final @NonNull WifiContext mContext; + private final @NonNull WifiThreadRunner mWifiThreadRunner; - public WifiDialogManager(WifiContext context) { + public WifiDialogManager( + @NonNull WifiContext context, + @NonNull WifiThreadRunner wifiThreadRunner) { mContext = context; - mPackageManager = context.getPackageManager(); + mWifiThreadRunner = wifiThreadRunner; } /** @@ -70,6 +74,25 @@ public class WifiDialogManager { } /** + * Sends an Intent to cancel a launched dialog with the given ID. + */ + private void sendCancelIntentForDialogId(int dialogId) { + if (!mCurrentDialogIds.contains(dialogId)) { + Log.e(TAG, "Tried to cancel dialog but could not find id " + dialogId); + return; + } + String wifiDialogApkPkgName = mContext.getWifiDialogApkPkgName(); + if (wifiDialogApkPkgName == null) { + Log.e(TAG, "Tried to cancel dialog but could not find a WifiDialog apk package name!"); + return; + } + Intent intent = new Intent(WifiManager.ACTION_CANCEL_DIALOG); + intent.putExtra(WifiManager.EXTRA_DIALOG_ID, dialogId); + intent.setClassName(mContext.getWifiDialogApkPkgName(), WIFI_DIALOG_ACTIVITY_CLASSNAME); + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + + /** * Callback for receiving P2P Invitation Received dialog responses. */ public interface P2pInvitationReceivedDialogCallback { @@ -86,15 +109,74 @@ public class WifiDialogManager { } /** + * Handles launching and callback response for a single P2P Invitation Received dialog + */ + private class P2pInvitationReceivedDialogHandle { + private int mDialogId; + private Intent mIntent; + private @NonNull P2pInvitationReceivedDialogCallback mCallback; + private @NonNull WifiThreadRunner mCallbackThreadRunner; + private @Nullable Runnable mTimeoutRunnable; + + P2pInvitationReceivedDialogHandle( + final int dialogId, + final @NonNull String deviceName, + final boolean isPinRequested, + @Nullable String displayPin, + @NonNull P2pInvitationReceivedDialogCallback callback, + @NonNull WifiThreadRunner callbackThreadRunner) { + mDialogId = dialogId; + Intent intent = new Intent(WifiManager.ACTION_LAUNCH_DIALOG); + intent.putExtra(WifiManager.EXTRA_DIALOG_TYPE, + WifiManager.DIALOG_TYPE_P2P_INVITATION_RECEIVED); + intent.putExtra(WifiManager.EXTRA_DIALOG_ID, dialogId); + intent.putExtra(WifiManager.EXTRA_P2P_DEVICE_NAME, deviceName); + intent.putExtra(WifiManager.EXTRA_P2P_PIN_REQUESTED, isPinRequested); + intent.putExtra(WifiManager.EXTRA_P2P_DISPLAY_PIN, displayPin); + intent.setClassName(mContext.getWifiDialogApkPkgName(), WIFI_DIALOG_ACTIVITY_CLASSNAME); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mIntent = intent; + mCallback = callback; + mCallbackThreadRunner = callbackThreadRunner; + } + + void launchDialog() { + mContext.startActivityAsUser(mIntent, UserHandle.CURRENT); + int timeoutMs = mContext.getResources() + .getInteger(R.integer.config_p2pInvitationReceivedDialogTimeoutMs); + if (timeoutMs > 0) { + mTimeoutRunnable = () -> sendCancelIntentForDialogId(mDialogId); + mWifiThreadRunner.postDelayed(mTimeoutRunnable, timeoutMs); + } + } + + void onAccepted(@Nullable String optionalPin) { + mCallbackThreadRunner.post(() -> mCallback.onAccepted(optionalPin)); + if (mTimeoutRunnable != null) { + mWifiThreadRunner.removeCallbacks(mTimeoutRunnable); + mTimeoutRunnable = null; + } + } + + void onDeclined() { + mCallbackThreadRunner.post(() -> mCallback.onDeclined()); + if (mTimeoutRunnable != null) { + mWifiThreadRunner.removeCallbacks(mTimeoutRunnable); + mTimeoutRunnable = null; + } + } + } + + /** * Launches a P2P Invitation Received dialog. * @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. * @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( + @AnyThread + public void launchP2pInvitationReceivedDialog( String deviceName, boolean isPinRequested, @Nullable String displayPin, @@ -102,32 +184,38 @@ public class WifiDialogManager { @NonNull WifiThreadRunner threadRunner) { if (callback == null) { Log.e(TAG, "Cannot launch a P2P Invitation Received dialog with null callback!"); - return -1; + return; } - if (callback == null) { - Log.e(TAG, "Cannot launch a P2P Invitation Received dialog with null handler!"); - return -1; + if (threadRunner == null) { + Log.e(TAG, "Cannot launch a P2P Invitation Received dialog with null thread runner!"); + return; } + mWifiThreadRunner.post(() -> + launchP2pInvitationReceivedDialogInternal( + deviceName, + isPinRequested, + displayPin, + callback, + threadRunner)); + } + + private void launchP2pInvitationReceivedDialogInternal( + String deviceName, + boolean isPinRequested, + @Nullable String displayPin, + @NonNull P2pInvitationReceivedDialogCallback callback, + @NonNull WifiThreadRunner threadRunner) { int dialogId = getNextDialogId(); mCurrentDialogIds.add(dialogId); - Intent intent = new Intent(); - String wifiDialogApkPkgName = mContext.getWifiDialogApkPkgName(); - if (wifiDialogApkPkgName == null) { - Log.e(TAG, "Tried to launch P2P Invitation Received dialog but could not find a" - + " WifiDialog apk package name!"); - return -1; - } - intent.setClassName(wifiDialogApkPkgName, WIFI_DIALOG_ACTIVITY_CLASSNAME); - intent.putExtra(WifiManager.EXTRA_DIALOG_TYPE, - WifiManager.DIALOG_TYPE_P2P_INVITATION_RECEIVED); - intent.putExtra(WifiManager.EXTRA_DIALOG_ID, dialogId); - intent.putExtra(WifiManager.EXTRA_P2P_DEVICE_NAME, deviceName); - intent.putExtra(WifiManager.EXTRA_P2P_PIN_REQUESTED, isPinRequested); - 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); + P2pInvitationReceivedDialogHandle dialogHandle = new P2pInvitationReceivedDialogHandle( + dialogId, + deviceName, + isPinRequested, + displayPin, + callback, + threadRunner); + mP2pInvitationReceivedDialogsHandles.put(dialogId, dialogHandle); + dialogHandle.launchDialog(); if (mVerboseLoggingEnabled) { Log.v(TAG, "Launching P2P Invitation Received dialog." + " id=" + dialogId @@ -136,7 +224,6 @@ public class WifiDialogManager { + " displayPin=" + displayPin + " callback=" + callback); } - return dialogId; } /** @@ -146,8 +233,21 @@ public class WifiDialogManager { * @param optionalPin PIN of the reply, or {@code null} if none was supplied. * @hide */ + @AnyThread public void replyToP2pInvitationReceivedDialog( - final int dialogId, final boolean accepted, final @Nullable String optionalPin) { + int dialogId, + boolean accepted, + @Nullable String optionalPin) { + mWifiThreadRunner.post(() -> replyToP2pInvitationReceivedDialogInternal( + dialogId, + accepted, + optionalPin)); + } + + private void replyToP2pInvitationReceivedDialogInternal( + int dialogId, + boolean accepted, + @Nullable String optionalPin) { if (mVerboseLoggingEnabled) { Log.i(TAG, "Response received for P2P Invitation Received dialog." + " id=" + dialogId @@ -155,32 +255,20 @@ public class WifiDialogManager { + " pin=" + optionalPin); } mCurrentDialogIds.remove(dialogId); - final P2pInvitationReceivedDialogCallback callback = - mP2pInvitationReceivedDialogCallbacks.get(dialogId); - mP2pInvitationReceivedDialogCallbacks.remove(dialogId); - if (callback == null) { + P2pInvitationReceivedDialogHandle dialogHandle = + mP2pInvitationReceivedDialogsHandles.get(dialogId); + mP2pInvitationReceivedDialogsHandles.remove(dialogId); + if (dialogHandle == null) { if (mVerboseLoggingEnabled) { - Log.w(TAG, "No matching callback for P2P Invitation Received dialog" + Log.w(TAG, "No matching dialog handle 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; + if (accepted) { + dialogHandle.onAccepted(optionalPin); + } else { + dialogHandle.onDeclined(); } - threadRunner.post(() -> { - if (accepted) { - callback.onAccepted(optionalPin); - } else { - callback.onDeclined(); - } - }); } } diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 21be3be9e0..c208a1f3b8 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -531,7 +531,7 @@ public class WifiInjector { mSimRequiredNotifier = new SimRequiredNotifier(mContext, mFrameworkFacade, mWifiNotificationManager); mLastCallerInfoManager = new LastCallerInfoManager(); - mWifiDialogManager = new WifiDialogManager(mContext); + mWifiDialogManager = new WifiDialogManager(mContext, mWifiThreadRunner); } /** diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java index 3401de1171..75ae632ed4 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/WifiDialogManagerTest.java @@ -16,19 +16,34 @@ package com.android.server.wifi; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; +import android.content.Intent; +import android.content.res.Resources; import android.net.wifi.WifiContext; +import android.net.wifi.WifiManager; +import android.os.UserHandle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; +import com.android.wifi.resources.R; + import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -37,18 +52,93 @@ import org.mockito.MockitoAnnotations; */ @SmallTest public class WifiDialogManagerTest extends WifiBaseTest { + private static final int TIMEOUT_MILLIS = 30_000; + private static final String WIFI_DIALOG_APK_PKG_NAME = "WifiDialogApkPkgName"; @Mock WifiContext mWifiContext; - @Mock WifiThreadRunner mCallbackThreadRunner; + @Mock Resources mResources; + @Mock WifiThreadRunner mWifiDialogManagerThreadRunner; + WifiDialogManager mWifiDialogManager; @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; - }); + when(mWifiContext.getWifiDialogApkPkgName()).thenReturn(WIFI_DIALOG_APK_PKG_NAME); + when(mWifiContext.getResources()).thenReturn(mResources); + mWifiDialogManager = new WifiDialogManager(mWifiContext, mWifiDialogManagerThreadRunner); + } + + /** + * Helper method to call launchP2pInvitationReceivedDialog synchronously. + * @return the launched dialog ID. + */ + private int launchP2pInvitationReceivedDialogSynchronous( + String deviceName, + boolean isPinRequested, + @Nullable String displayPin, + @NonNull WifiDialogManager.P2pInvitationReceivedDialogCallback callback, + @NonNull WifiThreadRunner callbackThreadRunner, + int timeoutMs) { + when(mResources.getInteger(R.integer.config_p2pInvitationReceivedDialogTimeoutMs)) + .thenReturn(timeoutMs); + mWifiDialogManager.launchP2pInvitationReceivedDialog( + deviceName, isPinRequested, displayPin, callback, callbackThreadRunner); + + // Synchronously run the posted launchP2pInvitationReceivedDialogInternal runnable. + ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWifiDialogManagerThreadRunner, atLeastOnce()) + .post(runnableArgumentCaptor.capture()); + runnableArgumentCaptor.getValue().run(); + + // Verify the launch Intent + ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mWifiContext, atLeastOnce()) + .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT)); + Intent launchIntent = intentArgumentCaptor.getValue(); + assertThat(launchIntent.getAction()).isEqualTo(WifiManager.ACTION_LAUNCH_DIALOG); + ComponentName component = launchIntent.getComponent(); + assertThat(component.getPackageName()).isEqualTo(WIFI_DIALOG_APK_PKG_NAME); + assertThat(component.getClassName()) + .isEqualTo(WifiDialogManager.WIFI_DIALOG_ACTIVITY_CLASSNAME); + assertThat(launchIntent.hasExtra(WifiManager.EXTRA_DIALOG_ID)).isTrue(); + int dialogId = launchIntent.getIntExtra(WifiManager.EXTRA_DIALOG_ID, -1); + assertThat(dialogId).isNotEqualTo(-1); + assertThat(launchIntent.hasExtra(WifiManager.EXTRA_DIALOG_TYPE)).isTrue(); + assertThat(launchIntent.getIntExtra(WifiManager.EXTRA_DIALOG_TYPE, + WifiManager.DIALOG_TYPE_UNKNOWN)) + .isEqualTo(WifiManager.DIALOG_TYPE_P2P_INVITATION_RECEIVED); + assertThat(launchIntent.hasExtra(WifiManager.EXTRA_P2P_DEVICE_NAME)).isTrue(); + assertThat(launchIntent.getStringExtra(WifiManager.EXTRA_P2P_DEVICE_NAME)) + .isEqualTo(deviceName); + assertThat(launchIntent.hasExtra(WifiManager.EXTRA_P2P_PIN_REQUESTED)).isTrue(); + assertThat(launchIntent.getBooleanExtra(WifiManager.EXTRA_P2P_PIN_REQUESTED, false)) + .isEqualTo(isPinRequested); + assertThat(launchIntent.hasExtra(WifiManager.EXTRA_P2P_DISPLAY_PIN)).isTrue(); + assertThat(launchIntent.getStringExtra(WifiManager.EXTRA_P2P_DISPLAY_PIN)) + .isEqualTo(displayPin); + return dialogId; + } + + /** + * Helper method to call replyToP2pInvitationReceivedDialog synchronously. + */ + public void replyToP2pInvitationReceivedDialogSynchronous( + int dialogId, + boolean accepted, + @Nullable String optionalPin) { + mWifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, accepted, optionalPin); + + // Synchronously run the posted replyToP2pInvitationReceivedDialogInternal runnable. + ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWifiDialogManagerThreadRunner, atLeastOnce()) + .post(runnableArgumentCaptor.capture()); + runnableArgumentCaptor.getValue().run(); + } + + private void dispatchMockWifiThreadRunner(WifiThreadRunner wifiThreadRunner) { + ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(wifiThreadRunner, atLeastOnce()).post(runnableArgumentCaptor.capture()); + runnableArgumentCaptor.getValue().run(); } /** @@ -57,61 +147,69 @@ public class WifiDialogManagerTest extends WifiBaseTest { */ @Test public void testP2pInvitationReceivedDialog_launchAndResponse_notifiesCallback() { - WifiDialogManager wifiDialogManager = new WifiDialogManager(mWifiContext); WifiDialogManager.P2pInvitationReceivedDialogCallback callback = mock(WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + WifiThreadRunner callbackThreadRunner = mock(WifiThreadRunner.class); int dialogId; // Accept without PIN - dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( - "deviceName", false, null, callback, mCallbackThreadRunner); - wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, true, null); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, true, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); 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"); + replyToP2pInvitationReceivedDialogSynchronous(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"); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", true, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, true, "012345"); + dispatchMockWifiThreadRunner(callbackThreadRunner); 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"); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, true, "012345"); + dispatchMockWifiThreadRunner(callbackThreadRunner); 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); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", true, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, true, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); verify(callback, times(2)).onAccepted(null); // Decline without PIN - dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( - "deviceName", false, null, callback, mCallbackThreadRunner); - wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, false, null); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, false, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); verify(callback, times(1)).onDeclined(); // Decline with PIN - dialogId = wifiDialogManager.launchP2pInvitationReceivedDialog( - "deviceName", true, null, callback, mCallbackThreadRunner); - wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId, false, "012345"); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", true, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, false, "012345"); + dispatchMockWifiThreadRunner(callbackThreadRunner); 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"); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, false, "012345"); + dispatchMockWifiThreadRunner(callbackThreadRunner); 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); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", true, null, callback, callbackThreadRunner, 0); + replyToP2pInvitationReceivedDialogSynchronous(dialogId, false, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); verify(callback, times(4)).onDeclined(); } @@ -120,28 +218,85 @@ public class WifiDialogManagerTest extends WifiBaseTest { */ @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); + WifiThreadRunner callbackThreadRunner = mock(WifiThreadRunner.class); + int dialogId1 = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback1, callbackThreadRunner, 0); // Launch Dialog2 WifiDialogManager.P2pInvitationReceivedDialogCallback callback2 = mock( WifiDialogManager.P2pInvitationReceivedDialogCallback.class); - int dialogId2 = wifiDialogManager.launchP2pInvitationReceivedDialog( - "deviceName", false, null, callback2, mCallbackThreadRunner); + int dialogId2 = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback2, callbackThreadRunner, 0); // callback1 notified - wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId1, true, null); + replyToP2pInvitationReceivedDialogSynchronous(dialogId1, true, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); verify(callback1, times(1)).onAccepted(null); verify(callback2, times(0)).onAccepted(null); // callback2 notified - wifiDialogManager.replyToP2pInvitationReceivedDialog(dialogId2, true, null); + replyToP2pInvitationReceivedDialogSynchronous(dialogId2, true, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); verify(callback1, times(1)).onAccepted(null); verify(callback2, times(1)).onAccepted(null); } + + /** + * Verifies that a P2P Invitation Received dialog is cancelled after the specified timeout + */ + @Test + public void testP2pInvitationReceivedDialog_timeout_cancelsDialog() { + // Launch Dialog without timeout. + WifiDialogManager.P2pInvitationReceivedDialogCallback callback = mock( + WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + WifiThreadRunner callbackThreadRunner = mock(WifiThreadRunner.class); + launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, 0); + + // Verify cancel runnable wasn't posted. + verify(mWifiDialogManagerThreadRunner, never()).postDelayed(any(Runnable.class), anyInt()); + + // Launch Dialog with timeout + callback = mock(WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + int dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, TIMEOUT_MILLIS); + + // Verify the timeout runnable was posted and run it. + ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWifiDialogManagerThreadRunner, times(1)) + .postDelayed(runnableArgumentCaptor.capture(), eq((long) TIMEOUT_MILLIS)); + runnableArgumentCaptor.getValue().run(); + + // Verify that a cancel Intent was sent. + ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mWifiContext, atLeastOnce()) + .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT)); + Intent cancelIntent = intentArgumentCaptor.getValue(); + assertThat(cancelIntent.getAction()).isEqualTo(WifiManager.ACTION_CANCEL_DIALOG); + ComponentName component = cancelIntent.getComponent(); + assertThat(component.getPackageName()).isEqualTo(WIFI_DIALOG_APK_PKG_NAME); + assertThat(component.getClassName()) + .isEqualTo(WifiDialogManager.WIFI_DIALOG_ACTIVITY_CLASSNAME); + assertThat(cancelIntent.hasExtra(WifiManager.EXTRA_DIALOG_ID)).isTrue(); + assertThat(cancelIntent.getIntExtra(WifiManager.EXTRA_DIALOG_ID, -1)).isEqualTo(dialogId); + + // Launch Dialog without timeout + callback = mock(WifiDialogManager.P2pInvitationReceivedDialogCallback.class); + dialogId = launchP2pInvitationReceivedDialogSynchronous( + "deviceName", false, null, callback, callbackThreadRunner, TIMEOUT_MILLIS); + + // Reply before the timeout is over + replyToP2pInvitationReceivedDialogSynchronous(dialogId, true, null); + dispatchMockWifiThreadRunner(callbackThreadRunner); + + // Verify callback was replied to, and the cancel runnable was posted but then removed. + verify(callback).onAccepted(null); + verify(callback, never()).onDeclined(); + verify(mWifiDialogManagerThreadRunner, times(2)) + .postDelayed(runnableArgumentCaptor.capture(), eq((long) TIMEOUT_MILLIS)); + verify(mWifiDialogManagerThreadRunner).removeCallbacks(runnableArgumentCaptor.getValue()); + } } |