• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi;
18 
19 import 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