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 android.app.ActivityOptions; 20 import android.content.Intent; 21 import android.net.wifi.WifiContext; 22 import android.net.wifi.WifiManager; 23 import android.os.UserHandle; 24 import android.util.ArraySet; 25 import android.util.Log; 26 import android.util.SparseArray; 27 import android.view.Display; 28 29 import androidx.annotation.AnyThread; 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.modules.utils.build.SdkLevel; 35 36 import java.util.Set; 37 38 import javax.annotation.concurrent.ThreadSafe; 39 40 /** 41 * Class to manage launching dialogs via WifiDialog and returning the user reply. 42 * All methods run on the main Wi-Fi thread runner except those annotated with @AnyThread, which can 43 * run on any thread. 44 */ 45 public class WifiDialogManager { 46 private static final String TAG = "WifiDialogManager"; 47 @VisibleForTesting 48 static final String WIFI_DIALOG_ACTIVITY_CLASSNAME = 49 "com.android.wifi.dialog.WifiDialogActivity"; 50 51 private boolean mVerboseLoggingEnabled; 52 53 private int mNextDialogId = 0; 54 private final Set<Integer> mActiveDialogIds = new ArraySet<>(); 55 private final @NonNull SparseArray<DialogHandleInternal> mActiveDialogHandles = 56 new SparseArray<>(); 57 58 private final @NonNull WifiContext mContext; 59 private final @NonNull WifiThreadRunner mWifiThreadRunner; 60 61 /** 62 * Constructs a WifiDialogManager 63 * 64 * @param context Main Wi-Fi context. 65 * @param wifiThreadRunner Main Wi-Fi thread runner. 66 */ WifiDialogManager( @onNull WifiContext context, @NonNull WifiThreadRunner wifiThreadRunner)67 public WifiDialogManager( 68 @NonNull WifiContext context, 69 @NonNull WifiThreadRunner wifiThreadRunner) { 70 mContext = context; 71 mWifiThreadRunner = wifiThreadRunner; 72 } 73 74 /** 75 * Enables verbose logging. 76 */ enableVerboseLogging(boolean enabled)77 public void enableVerboseLogging(boolean enabled) { 78 mVerboseLoggingEnabled = enabled; 79 } 80 getNextDialogId()81 private int getNextDialogId() { 82 if (mActiveDialogIds.isEmpty() || mNextDialogId == WifiManager.INVALID_DIALOG_ID) { 83 mNextDialogId = 0; 84 } 85 return mNextDialogId++; 86 } 87 getBaseLaunchIntent(@ifiManager.DialogType int dialogType)88 private @Nullable Intent getBaseLaunchIntent(@WifiManager.DialogType int dialogType) { 89 Intent intent = new Intent(WifiManager.ACTION_LAUNCH_DIALOG) 90 .putExtra(WifiManager.EXTRA_DIALOG_TYPE, dialogType) 91 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 92 String wifiDialogApkPkgName = mContext.getWifiDialogApkPkgName(); 93 if (wifiDialogApkPkgName == null) { 94 Log.w(TAG, "Could not get WifiDialog APK package name!"); 95 return null; 96 } 97 intent.setClassName(wifiDialogApkPkgName, WIFI_DIALOG_ACTIVITY_CLASSNAME); 98 return intent; 99 } 100 getDismissIntent(int dialogId)101 private @Nullable Intent getDismissIntent(int dialogId) { 102 Intent intent = new Intent(WifiManager.ACTION_DISMISS_DIALOG); 103 intent.putExtra(WifiManager.EXTRA_DIALOG_ID, dialogId); 104 String wifiDialogApkPkgName = mContext.getWifiDialogApkPkgName(); 105 if (wifiDialogApkPkgName == null) { 106 Log.w(TAG, "Could not get WifiDialog APK package name!"); 107 return null; 108 } 109 intent.setClassName(wifiDialogApkPkgName, WIFI_DIALOG_ACTIVITY_CLASSNAME); 110 return intent; 111 } 112 113 /** 114 * Handle for launching and dismissing a dialog from any thread. 115 */ 116 @ThreadSafe 117 public class DialogHandle { 118 DialogHandleInternal mInternalHandle; DialogHandle(DialogHandleInternal internalHandle)119 private DialogHandle(DialogHandleInternal internalHandle) { 120 mInternalHandle = internalHandle; 121 } 122 123 /** 124 * Launches the dialog. 125 */ 126 @AnyThread launchDialog()127 public void launchDialog() { 128 mWifiThreadRunner.post(() -> mInternalHandle.launchDialog(0)); 129 } 130 131 /** 132 * Launches the dialog with a timeout before it is auto-cancelled. 133 * @param timeoutMs timeout in milliseconds before the dialog is auto-cancelled. A value <=0 134 * indicates no timeout. 135 */ 136 @AnyThread launchDialog(long timeoutMs)137 public void launchDialog(long timeoutMs) { 138 mWifiThreadRunner.post(() -> mInternalHandle.launchDialog(timeoutMs)); 139 140 } 141 142 /** 143 * Dismisses the dialog. Dialogs will automatically be dismissed once the user replies, but 144 * this method may be used to dismiss unanswered dialogs that are no longer needed. 145 */ 146 @AnyThread dismissDialog()147 public void dismissDialog() { 148 mWifiThreadRunner.post(() -> mInternalHandle.dismissDialog()); 149 } 150 } 151 152 /** 153 * Internal handle for launching and dismissing a dialog on the main Wi-Fi thread runner. 154 * @see {@link DialogHandle} 155 */ 156 private class DialogHandleInternal { 157 private int mDialogId = WifiManager.INVALID_DIALOG_ID; 158 private final @NonNull Intent mIntent; 159 private Runnable mTimeoutRunnable; 160 private final int mDisplayId; 161 DialogHandleInternal(@onNull Intent intent, int displayId)162 DialogHandleInternal(@NonNull Intent intent, int displayId) 163 throws IllegalArgumentException { 164 if (intent == null) { 165 throw new IllegalArgumentException("Intent cannot be null!"); 166 } 167 mDisplayId = displayId; 168 mIntent = intent; 169 } 170 171 /** 172 * @see {@link DialogHandle#launchDialog(long)} 173 */ launchDialog(long timeoutMs)174 void launchDialog(long timeoutMs) { 175 if (mDialogId != WifiManager.INVALID_DIALOG_ID) { 176 // Dialog is already active, ignore. 177 return; 178 } 179 registerDialog(); 180 mIntent.putExtra(WifiManager.EXTRA_DIALOG_ID, mDialogId); 181 boolean launched = false; 182 if (SdkLevel.isAtLeastT() && mDisplayId != Display.DEFAULT_DISPLAY) { 183 try { 184 mContext.startActivityAsUser(mIntent, 185 ActivityOptions.makeBasic().setLaunchDisplayId(mDisplayId).toBundle(), 186 UserHandle.CURRENT); 187 launched = true; 188 } catch (Exception e) { 189 Log.e(TAG, "Error startActivityAsUser - " + e); 190 } 191 } 192 if (!launched) { 193 mContext.startActivityAsUser(mIntent, UserHandle.CURRENT); 194 } 195 if (mVerboseLoggingEnabled) { 196 Log.v(TAG, "Launching dialog with id=" + mDialogId); 197 } 198 if (timeoutMs > 0) { 199 mTimeoutRunnable = () -> onTimeout(); 200 mWifiThreadRunner.postDelayed(mTimeoutRunnable, timeoutMs); 201 } 202 } 203 204 /** 205 * Callback to run when the dialog times out. 206 */ onTimeout()207 void onTimeout() { 208 dismissDialog(); 209 } 210 211 /** 212 * @see {@link DialogHandle#dismissDialog()} 213 */ dismissDialog()214 void dismissDialog() { 215 if (mDialogId == WifiManager.INVALID_DIALOG_ID) { 216 // Dialog is not active, ignore. 217 return; 218 } 219 Intent dismissIntent = getDismissIntent(mDialogId); 220 if (dismissIntent == null) { 221 Log.e(TAG, "Could not create intent for dismissing dialog with id: " 222 + mDialogId); 223 return; 224 } 225 mContext.startActivityAsUser(dismissIntent, UserHandle.CURRENT); 226 if (mVerboseLoggingEnabled) { 227 Log.v(TAG, "Dismissing dialog with id=" + mDialogId); 228 } 229 unregisterDialog(); 230 } 231 232 /** 233 * Assigns a dialog id to the dialog and registers it as an active dialog. 234 */ registerDialog()235 void registerDialog() { 236 if (mDialogId != WifiManager.INVALID_DIALOG_ID) { 237 // Already registered. 238 return; 239 } 240 mDialogId = getNextDialogId(); 241 mActiveDialogIds.add(mDialogId); 242 mActiveDialogHandles.put(mDialogId, this); 243 if (mVerboseLoggingEnabled) { 244 Log.v(TAG, "Registered dialog with id=" + mDialogId); 245 } 246 } 247 248 /** 249 * Unregisters the dialog as an active dialog and removes its dialog id. 250 * This should be called after a dialog is replied to or dismissed. 251 */ unregisterDialog()252 void unregisterDialog() { 253 if (mDialogId == WifiManager.INVALID_DIALOG_ID) { 254 // Already unregistered. 255 return; 256 } 257 if (mTimeoutRunnable != null) { 258 mWifiThreadRunner.removeCallbacks(mTimeoutRunnable); 259 } 260 mTimeoutRunnable = null; 261 mActiveDialogIds.remove(mDialogId); 262 mActiveDialogHandles.remove(mDialogId); 263 mDialogId = WifiManager.INVALID_DIALOG_ID; 264 if (mVerboseLoggingEnabled) { 265 Log.v(TAG, "Unregistered dialog with id=" + mDialogId); 266 } 267 } 268 } 269 270 private class SimpleDialogHandle extends DialogHandleInternal { 271 private @NonNull SimpleDialogCallback mCallback; 272 private @NonNull WifiThreadRunner mCallbackThreadRunner; 273 SimpleDialogHandle( final String title, final String message, final String messageUrl, final int messageUrlStart, final int messageUrlEnd, final String positiveButtonText, final String negativeButtonText, final String neutralButtonText, @NonNull SimpleDialogCallback callback, @NonNull WifiThreadRunner callbackThreadRunner)274 SimpleDialogHandle( 275 final String title, 276 final String message, 277 final String messageUrl, 278 final int messageUrlStart, 279 final int messageUrlEnd, 280 final String positiveButtonText, 281 final String negativeButtonText, 282 final String neutralButtonText, 283 @NonNull SimpleDialogCallback callback, 284 @NonNull WifiThreadRunner callbackThreadRunner) throws IllegalArgumentException { 285 super(getBaseLaunchIntent(WifiManager.DIALOG_TYPE_SIMPLE) 286 .putExtra(WifiManager.EXTRA_DIALOG_TITLE, title) 287 .putExtra(WifiManager.EXTRA_DIALOG_MESSAGE, message) 288 .putExtra(WifiManager.EXTRA_DIALOG_MESSAGE_URL, messageUrl) 289 .putExtra(WifiManager.EXTRA_DIALOG_MESSAGE_URL_START, messageUrlStart) 290 .putExtra(WifiManager.EXTRA_DIALOG_MESSAGE_URL_END, messageUrlEnd) 291 .putExtra(WifiManager.EXTRA_DIALOG_POSITIVE_BUTTON_TEXT, positiveButtonText) 292 .putExtra(WifiManager.EXTRA_DIALOG_NEGATIVE_BUTTON_TEXT, negativeButtonText) 293 .putExtra(WifiManager.EXTRA_DIALOG_NEUTRAL_BUTTON_TEXT, neutralButtonText), 294 Display.DEFAULT_DISPLAY); 295 if (messageUrl != null) { 296 if (message == null) { 297 throw new IllegalArgumentException("Cannot set span for null message!"); 298 } 299 if (messageUrlStart < 0) { 300 throw new IllegalArgumentException("Span start cannot be less than 0!"); 301 } 302 if (messageUrlEnd > message.length()) { 303 throw new IllegalArgumentException("Span end index " + messageUrlEnd 304 + " cannot be greater than message length " + message.length() + "!"); 305 } 306 } 307 if (callback == null) { 308 throw new IllegalArgumentException("Callback cannot be null!"); 309 } 310 if (callbackThreadRunner == null) { 311 throw new IllegalArgumentException("Callback thread runner cannot be null!"); 312 } 313 mCallback = callback; 314 mCallbackThreadRunner = callbackThreadRunner; 315 } 316 notifyOnPositiveButtonClicked()317 void notifyOnPositiveButtonClicked() { 318 mCallbackThreadRunner.post(() -> mCallback.onPositiveButtonClicked()); 319 unregisterDialog(); 320 } 321 notifyOnNegativeButtonClicked()322 void notifyOnNegativeButtonClicked() { 323 mCallbackThreadRunner.post(() -> mCallback.onNegativeButtonClicked()); 324 unregisterDialog(); 325 } 326 notifyOnNeutralButtonClicked()327 void notifyOnNeutralButtonClicked() { 328 mCallbackThreadRunner.post(() -> mCallback.onNeutralButtonClicked()); 329 unregisterDialog(); 330 } 331 notifyOnCancelled()332 void notifyOnCancelled() { 333 mCallbackThreadRunner.post(() -> mCallback.onCancelled()); 334 unregisterDialog(); 335 } 336 337 @Override onTimeout()338 void onTimeout() { 339 dismissDialog(); 340 notifyOnCancelled(); 341 } 342 } 343 344 /** 345 * Callback for receiving simple dialog responses. 346 */ 347 public interface SimpleDialogCallback { 348 /** 349 * The positive button was clicked. 350 */ onPositiveButtonClicked()351 void onPositiveButtonClicked(); 352 353 /** 354 * The negative button was clicked. 355 */ onNegativeButtonClicked()356 void onNegativeButtonClicked(); 357 358 /** 359 * The neutral button was clicked. 360 */ onNeutralButtonClicked()361 void onNeutralButtonClicked(); 362 363 /** 364 * The dialog was cancelled (back button or home button or timeout). 365 */ onCancelled()366 void onCancelled(); 367 } 368 369 /** 370 * Creates a simple dialog with optional title, message, and positive/negative/neutral buttons. 371 * 372 * @param title Title of the dialog. 373 * @param message Message of the dialog. 374 * @param positiveButtonText Text of the positive button or {@code null} for no button. 375 * @param negativeButtonText Text of the negative button or {@code null} for no button. 376 * @param neutralButtonText Text of the neutral button or {@code null} for no button. 377 * @param callback Callback to receive the dialog response. 378 * @param callbackThreadRunner WifiThreadRunner to run the callback on. 379 * @return DialogHandle Handle for the dialog, or {@code null} if no dialog could 380 * be created. 381 */ 382 @AnyThread 383 @Nullable createSimpleDialog( @ullable String title, @Nullable String message, @Nullable String positiveButtonText, @Nullable String negativeButtonText, @Nullable String neutralButtonText, @NonNull SimpleDialogCallback callback, @NonNull WifiThreadRunner callbackThreadRunner)384 public DialogHandle createSimpleDialog( 385 @Nullable String title, 386 @Nullable String message, 387 @Nullable String positiveButtonText, 388 @Nullable String negativeButtonText, 389 @Nullable String neutralButtonText, 390 @NonNull SimpleDialogCallback callback, 391 @NonNull WifiThreadRunner callbackThreadRunner) { 392 try { 393 return new DialogHandle( 394 new SimpleDialogHandle( 395 title, 396 message, 397 null /* messageUrl */, 398 0 /* messageUrlStart */, 399 0 /* messageUrlEnd */, 400 positiveButtonText, 401 negativeButtonText, 402 neutralButtonText, 403 callback, 404 callbackThreadRunner) 405 ); 406 } catch (IllegalArgumentException e) { 407 Log.e(TAG, "Could not create DialogHandle for simple dialog: " + e); 408 return null; 409 } 410 } 411 412 /** 413 * Creates a simple dialog with a URL embedded in the message. 414 * 415 * @param title Title of the dialog. 416 * @param message Message of the dialog. 417 * @param messageUrl URL to embed in the message. If non-null, then message must also 418 * be non-null. 419 * @param messageUrlStart Start index (inclusive) of the URL in the message. Must be 420 * non-negative. 421 * @param messageUrlEnd End index (exclusive) of the URL in the message. Must be less 422 * than the length of message. 423 * @param positiveButtonText Text of the positive button or {@code null} for no button. 424 * @param negativeButtonText Text of the negative button or {@code null} for no button. 425 * @param neutralButtonText Text of the neutral button or {@code null} for no button. 426 * @param callback Callback to receive the dialog response. 427 * @param callbackThreadRunner WifiThreadRunner to run the callback on. 428 * @return DialogHandle Handle for the dialog, or {@code null} if no dialog could 429 * be created. 430 */ 431 @AnyThread 432 @Nullable createSimpleDialogWithUrl( @ullable String title, @Nullable String message, @Nullable String messageUrl, int messageUrlStart, int messageUrlEnd, @Nullable String positiveButtonText, @Nullable String negativeButtonText, @Nullable String neutralButtonText, @NonNull SimpleDialogCallback callback, @NonNull WifiThreadRunner callbackThreadRunner)433 public DialogHandle createSimpleDialogWithUrl( 434 @Nullable String title, 435 @Nullable String message, 436 @Nullable String messageUrl, 437 int messageUrlStart, 438 int messageUrlEnd, 439 @Nullable String positiveButtonText, 440 @Nullable String negativeButtonText, 441 @Nullable String neutralButtonText, 442 @NonNull SimpleDialogCallback callback, 443 @NonNull WifiThreadRunner callbackThreadRunner) { 444 try { 445 return new DialogHandle( 446 new SimpleDialogHandle( 447 title, 448 message, 449 messageUrl, 450 messageUrlStart, 451 messageUrlEnd, 452 positiveButtonText, 453 negativeButtonText, 454 neutralButtonText, 455 callback, 456 callbackThreadRunner) 457 ); 458 } catch (IllegalArgumentException e) { 459 Log.e(TAG, "Could not create DialogHandle for simple dialog: " + e); 460 return null; 461 } 462 } 463 464 /** 465 * Returns the reply to a simple dialog to the callback of matching dialogId. 466 * @param dialogId id of the replying dialog. 467 * @param reply reply of the dialog. 468 */ replyToSimpleDialog(int dialogId, @WifiManager.DialogReply int reply)469 public void replyToSimpleDialog(int dialogId, @WifiManager.DialogReply int reply) { 470 if (mVerboseLoggingEnabled) { 471 Log.i(TAG, "Response received for simple dialog. id=" + dialogId + " reply=" + reply); 472 } 473 DialogHandleInternal internalHandle = mActiveDialogHandles.get(dialogId); 474 if (internalHandle == null) { 475 if (mVerboseLoggingEnabled) { 476 Log.w(TAG, "No matching dialog handle for simple dialog id=" + dialogId); 477 } 478 return; 479 } 480 if (!(internalHandle instanceof SimpleDialogHandle)) { 481 if (mVerboseLoggingEnabled) { 482 Log.w(TAG, "Dialog handle with id " + dialogId + " is not for a simple dialog."); 483 } 484 return; 485 } 486 switch (reply) { 487 case WifiManager.DIALOG_REPLY_POSITIVE: 488 ((SimpleDialogHandle) internalHandle).notifyOnPositiveButtonClicked(); 489 break; 490 case WifiManager.DIALOG_REPLY_NEGATIVE: 491 ((SimpleDialogHandle) internalHandle).notifyOnNegativeButtonClicked(); 492 break; 493 case WifiManager.DIALOG_REPLY_NEUTRAL: 494 ((SimpleDialogHandle) internalHandle).notifyOnNeutralButtonClicked(); 495 break; 496 case WifiManager.DIALOG_REPLY_CANCELLED: 497 ((SimpleDialogHandle) internalHandle).notifyOnCancelled(); 498 break; 499 default: 500 if (mVerboseLoggingEnabled) { 501 Log.w(TAG, "Received invalid reply=" + reply); 502 } 503 } 504 } 505 506 private class P2pInvitationReceivedDialogHandle extends DialogHandleInternal { 507 private @NonNull P2pInvitationReceivedDialogCallback mCallback; 508 private @NonNull WifiThreadRunner mCallbackThreadRunner; 509 P2pInvitationReceivedDialogHandle( final @NonNull String deviceName, final boolean isPinRequested, @Nullable String displayPin, int displayId, @NonNull P2pInvitationReceivedDialogCallback callback, @NonNull WifiThreadRunner callbackThreadRunner)510 P2pInvitationReceivedDialogHandle( 511 final @NonNull String deviceName, 512 final boolean isPinRequested, 513 @Nullable String displayPin, 514 int displayId, 515 @NonNull P2pInvitationReceivedDialogCallback callback, 516 @NonNull WifiThreadRunner callbackThreadRunner) throws IllegalArgumentException { 517 super(getBaseLaunchIntent(WifiManager.DIALOG_TYPE_P2P_INVITATION_RECEIVED) 518 .putExtra(WifiManager.EXTRA_P2P_DEVICE_NAME, deviceName) 519 .putExtra(WifiManager.EXTRA_P2P_PIN_REQUESTED, isPinRequested) 520 .putExtra(WifiManager.EXTRA_P2P_DISPLAY_PIN, displayPin), displayId); 521 if (deviceName == null) { 522 throw new IllegalArgumentException("Device name cannot be null!"); 523 } 524 if (callback == null) { 525 throw new IllegalArgumentException("Callback cannot be null!"); 526 } 527 if (callbackThreadRunner == null) { 528 throw new IllegalArgumentException("Callback thread runner cannot be null!"); 529 } 530 mCallback = callback; 531 mCallbackThreadRunner = callbackThreadRunner; 532 } 533 notifyOnAccepted(@ullable String optionalPin)534 void notifyOnAccepted(@Nullable String optionalPin) { 535 mCallbackThreadRunner.post(() -> mCallback.onAccepted(optionalPin)); 536 unregisterDialog(); 537 } 538 notifyOnDeclined()539 void notifyOnDeclined() { 540 mCallbackThreadRunner.post(() -> mCallback.onDeclined()); 541 unregisterDialog(); 542 } 543 544 @Override onTimeout()545 void onTimeout() { 546 dismissDialog(); 547 notifyOnDeclined(); 548 } 549 } 550 551 /** 552 * Callback for receiving P2P Invitation Received dialog responses. 553 */ 554 public interface P2pInvitationReceivedDialogCallback { 555 /** 556 * Invitation was accepted. 557 * 558 * @param optionalPin Optional PIN if a PIN was requested, or {@code null} otherwise. 559 */ onAccepted(@ullable String optionalPin)560 void onAccepted(@Nullable String optionalPin); 561 562 /** 563 * Invitation was declined or cancelled (back button or home button or timeout). 564 */ onDeclined()565 void onDeclined(); 566 } 567 568 /** 569 * Creates a P2P Invitation Received dialog. 570 * 571 * @param deviceName Name of the device sending the invitation. 572 * @param isPinRequested True if a PIN was requested and a PIN input UI should be shown. 573 * @param displayPin Display PIN, or {@code null} if no PIN should be displayed 574 * @param displayId The ID of the Display on which to place the dialog 575 * (Display.DEFAULT_DISPLAY 576 * refers to the default display) 577 * @param callback Callback to receive the dialog response. 578 * @param callbackThreadRunner WifiThreadRunner to run the callback on. 579 * @return DialogHandle Handle for the dialog, or {@code null} if no dialog could 580 * be created. 581 */ 582 @AnyThread createP2pInvitationReceivedDialog( @onNull String deviceName, boolean isPinRequested, @Nullable String displayPin, int displayId, @NonNull P2pInvitationReceivedDialogCallback callback, @NonNull WifiThreadRunner callbackThreadRunner)583 public DialogHandle createP2pInvitationReceivedDialog( 584 @NonNull String deviceName, 585 boolean isPinRequested, 586 @Nullable String displayPin, 587 int displayId, 588 @NonNull P2pInvitationReceivedDialogCallback callback, 589 @NonNull WifiThreadRunner callbackThreadRunner) { 590 try { 591 return new DialogHandle( 592 new P2pInvitationReceivedDialogHandle( 593 deviceName, 594 isPinRequested, 595 displayPin, 596 displayId, 597 callback, 598 callbackThreadRunner) 599 ); 600 } catch (IllegalArgumentException e) { 601 Log.e(TAG, "Could not create DialogHandle for P2P Invitation Received dialog: " + e); 602 return null; 603 } 604 } 605 606 /** 607 * Returns the reply to a P2P Invitation Received dialog to the callback of matching dialogId. 608 * Note: Must be invoked only from the main Wi-Fi thread. 609 * 610 * @param dialogId id of the replying dialog. 611 * @param accepted Whether the invitation was accepted. 612 * @param optionalPin PIN of the reply, or {@code null} if none was supplied. 613 */ replyToP2pInvitationReceivedDialog( int dialogId, boolean accepted, @Nullable String optionalPin)614 public void replyToP2pInvitationReceivedDialog( 615 int dialogId, 616 boolean accepted, 617 @Nullable String optionalPin) { 618 if (mVerboseLoggingEnabled) { 619 Log.i(TAG, "Response received for P2P Invitation Received dialog." 620 + " id=" + dialogId 621 + " accepted=" + accepted 622 + " pin=" + optionalPin); 623 } 624 DialogHandleInternal internalHandle = mActiveDialogHandles.get(dialogId); 625 if (internalHandle == null) { 626 if (mVerboseLoggingEnabled) { 627 Log.w(TAG, "No matching dialog handle for P2P Invitation Received dialog" 628 + " id=" + dialogId); 629 } 630 return; 631 } 632 if (!(internalHandle instanceof P2pInvitationReceivedDialogHandle)) { 633 if (mVerboseLoggingEnabled) { 634 Log.w(TAG, "Dialog handle with id " + dialogId 635 + " is not for a P2P Invitation Received dialog."); 636 } 637 return; 638 } 639 if (accepted) { 640 ((P2pInvitationReceivedDialogHandle) internalHandle).notifyOnAccepted(optionalPin); 641 } else { 642 ((P2pInvitationReceivedDialogHandle) internalHandle).notifyOnDeclined(); 643 } 644 } 645 646 private class P2pInvitationSentDialogHandle extends DialogHandleInternal { P2pInvitationSentDialogHandle( final @NonNull String deviceName, final @NonNull String displayPin, int displayId)647 P2pInvitationSentDialogHandle( 648 final @NonNull String deviceName, 649 final @NonNull String displayPin, 650 int displayId) throws IllegalArgumentException { 651 super(getBaseLaunchIntent(WifiManager.DIALOG_TYPE_P2P_INVITATION_SENT) 652 .putExtra(WifiManager.EXTRA_P2P_DEVICE_NAME, deviceName) 653 .putExtra(WifiManager.EXTRA_P2P_DISPLAY_PIN, displayPin), 654 displayId); 655 if (deviceName == null) { 656 throw new IllegalArgumentException("Device name cannot be null!"); 657 } 658 if (displayPin == null) { 659 throw new IllegalArgumentException("Display PIN cannot be null!"); 660 } 661 } 662 } 663 664 /** 665 * Creates a P2P Invitation Sent dialog. 666 * 667 * @param deviceName Name of the device the invitation was sent to. 668 * @param displayPin display PIN 669 * @param displayId display ID 670 * @return DialogHandle Handle for the dialog, or {@code null} if no dialog could 671 * be created. 672 */ 673 @AnyThread createP2pInvitationSentDialog( @onNull String deviceName, @Nullable String displayPin, int displayId)674 public DialogHandle createP2pInvitationSentDialog( 675 @NonNull String deviceName, 676 @Nullable String displayPin, 677 int displayId) { 678 try { 679 return new DialogHandle(new P2pInvitationSentDialogHandle(deviceName, displayPin, 680 displayId)); 681 } catch (IllegalArgumentException e) { 682 Log.e(TAG, "Could not create DialogHandle for P2P Invitation Sent dialog: " + e); 683 return null; 684 } 685 } 686 } 687