• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 package com.android.dialer.interactions;
17 
18 import android.Manifest;
19 import android.annotation.SuppressLint;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.FragmentManager;
25 import android.content.Context;
26 import android.content.CursorLoader;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.Loader;
30 import android.content.Loader.OnLoadCompleteListener;
31 import android.content.pm.PackageManager;
32 import android.database.Cursor;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Parcel;
36 import android.os.Parcelable;
37 import android.provider.ContactsContract.CommonDataKinds.Phone;
38 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
39 import android.provider.ContactsContract.Contacts;
40 import android.provider.ContactsContract.Data;
41 import android.provider.ContactsContract.RawContacts;
42 import android.support.annotation.IntDef;
43 import android.support.annotation.VisibleForTesting;
44 import android.support.v4.app.ActivityCompat;
45 import android.support.v4.content.ContextCompat;
46 import android.view.LayoutInflater;
47 import android.view.View;
48 import android.view.ViewGroup;
49 import android.widget.ArrayAdapter;
50 import android.widget.CheckBox;
51 import android.widget.ListAdapter;
52 import android.widget.TextView;
53 import com.android.contacts.common.Collapser;
54 import com.android.contacts.common.Collapser.Collapsible;
55 import com.android.contacts.common.MoreContactUtils;
56 import com.android.contacts.common.util.ContactDisplayUtils;
57 import com.android.dialer.callintent.CallInitiationType;
58 import com.android.dialer.callintent.CallIntentBuilder;
59 import com.android.dialer.callintent.CallIntentParser;
60 import com.android.dialer.callintent.CallSpecificAppData;
61 import com.android.dialer.common.Assert;
62 import com.android.dialer.common.LogUtil;
63 import com.android.dialer.logging.InteractionEvent;
64 import com.android.dialer.logging.Logger;
65 import com.android.dialer.util.DialerUtils;
66 import com.android.dialer.util.TransactionSafeActivity;
67 import java.lang.annotation.Retention;
68 import java.lang.annotation.RetentionPolicy;
69 import java.util.ArrayList;
70 import java.util.List;
71 
72 /**
73  * Initiates phone calls or a text message. If there are multiple candidates, this class shows a
74  * dialog to pick one. Creating one of these interactions should be done through the static factory
75  * methods.
76  *
77  * <p>Note that this class initiates not only usual *phone* calls but also *SIP* calls.
78  *
79  * <p>TODO: clean up code and documents since it is quite confusing to use "phone numbers" or "phone
80  * calls" here while they can be SIP addresses or SIP calls (See also issue 5039627).
81  */
82 public class PhoneNumberInteraction implements OnLoadCompleteListener<Cursor> {
83 
84   private static final String TAG = PhoneNumberInteraction.class.getSimpleName();
85   /** The identifier for a permissions request if one is generated. */
86   public static final int REQUEST_READ_CONTACTS = 1;
87   public static final int REQUEST_CALL_PHONE = 2;
88 
89   @VisibleForTesting
90   public static final String[] PHONE_NUMBER_PROJECTION =
91       new String[] {
92         Phone._ID,
93         Phone.NUMBER,
94         Phone.IS_SUPER_PRIMARY,
95         RawContacts.ACCOUNT_TYPE,
96         RawContacts.DATA_SET,
97         Phone.TYPE,
98         Phone.LABEL,
99         Phone.MIMETYPE,
100         Phone.CONTACT_ID,
101       };
102 
103   private static final String PHONE_NUMBER_SELECTION =
104       Data.MIMETYPE
105           + " IN ('"
106           + Phone.CONTENT_ITEM_TYPE
107           + "', "
108           + "'"
109           + SipAddress.CONTENT_ITEM_TYPE
110           + "') AND "
111           + Data.DATA1
112           + " NOT NULL";
113   private static final int UNKNOWN_CONTACT_ID = -1;
114   private final Context mContext;
115   private final int mInteractionType;
116   private final CallSpecificAppData mCallSpecificAppData;
117   private long mContactId = UNKNOWN_CONTACT_ID;
118   private CursorLoader mLoader;
119   private boolean mIsVideoCall;
120 
121   /** Error codes for interactions. */
122   @Retention(RetentionPolicy.SOURCE)
123   @IntDef(
124     value = {
125       InteractionErrorCode.CONTACT_NOT_FOUND,
126       InteractionErrorCode.CONTACT_HAS_NO_NUMBER,
127       InteractionErrorCode.USER_LEAVING_ACTIVITY,
128       InteractionErrorCode.OTHER_ERROR
129     }
130   )
131   public @interface InteractionErrorCode {
132 
133     int CONTACT_NOT_FOUND = 1;
134     int CONTACT_HAS_NO_NUMBER = 2;
135     int OTHER_ERROR = 3;
136     int USER_LEAVING_ACTIVITY = 4;
137   }
138 
139   /**
140    * Activities which use this class must implement this. They will be notified if there was an
141    * error performing the interaction. For example, this callback will be invoked on the activity if
142    * the contact URI provided points to a deleted contact, or to a contact without a phone number.
143    */
144   public interface InteractionErrorListener {
145 
interactionError(@nteractionErrorCode int interactionErrorCode)146     void interactionError(@InteractionErrorCode int interactionErrorCode);
147   }
148 
149   /**
150    * Activities which use this class must implement this. They will be notified if the phone number
151    * disambiguation dialog is dismissed.
152    */
153   public interface DisambigDialogDismissedListener {
onDisambigDialogDismissed()154     void onDisambigDialogDismissed();
155   }
156 
PhoneNumberInteraction( Context context, int interactionType, boolean isVideoCall, CallSpecificAppData callSpecificAppData)157   private PhoneNumberInteraction(
158       Context context,
159       int interactionType,
160       boolean isVideoCall,
161       CallSpecificAppData callSpecificAppData) {
162     mContext = context;
163     mInteractionType = interactionType;
164     mCallSpecificAppData = callSpecificAppData;
165     mIsVideoCall = isVideoCall;
166 
167     Assert.checkArgument(context instanceof InteractionErrorListener);
168     Assert.checkArgument(context instanceof DisambigDialogDismissedListener);
169     Assert.checkArgument(context instanceof ActivityCompat.OnRequestPermissionsResultCallback);
170   }
171 
performAction( Context context, String phoneNumber, int interactionType, boolean isVideoCall, CallSpecificAppData callSpecificAppData)172   private static void performAction(
173       Context context,
174       String phoneNumber,
175       int interactionType,
176       boolean isVideoCall,
177       CallSpecificAppData callSpecificAppData) {
178     Intent intent;
179     switch (interactionType) {
180       case ContactDisplayUtils.INTERACTION_SMS:
181         intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("sms", phoneNumber, null));
182         break;
183       default:
184         intent =
185             new CallIntentBuilder(phoneNumber, callSpecificAppData)
186                 .setIsVideoCall(isVideoCall)
187                 .build();
188         break;
189     }
190     DialerUtils.startActivityWithErrorToast(context, intent);
191   }
192 
193   /**
194    * @param activity that is calling this interaction. This must be of type {@link
195    *     TransactionSafeActivity} because we need to check on the activity state after the phone
196    *     numbers have been queried for. The activity must implement {@link InteractionErrorListener}
197    *     and {@link DisambigDialogDismissedListener}.
198    * @param isVideoCall {@code true} if the call is a video call, {@code false} otherwise.
199    */
startInteractionForPhoneCall( TransactionSafeActivity activity, Uri uri, boolean isVideoCall, CallSpecificAppData callSpecificAppData)200   public static void startInteractionForPhoneCall(
201       TransactionSafeActivity activity,
202       Uri uri,
203       boolean isVideoCall,
204       CallSpecificAppData callSpecificAppData) {
205     new PhoneNumberInteraction(
206             activity, ContactDisplayUtils.INTERACTION_CALL, isVideoCall, callSpecificAppData)
207         .startInteraction(uri);
208   }
209 
performAction(String phoneNumber)210   private void performAction(String phoneNumber) {
211     PhoneNumberInteraction.performAction(
212         mContext, phoneNumber, mInteractionType, mIsVideoCall, mCallSpecificAppData);
213   }
214 
215   /**
216    * Initiates the interaction to result in either a phone call or sms message for a contact.
217    *
218    * @param uri Contact Uri
219    */
startInteraction(Uri uri)220   private void startInteraction(Uri uri) {
221     // It's possible for a shortcut to have been created, and then permissions revoked. To avoid a
222     // crash when the user tries to use such a shortcut, check for this condition and ask the user
223     // for the permission.
224     if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.CALL_PHONE)
225         != PackageManager.PERMISSION_GRANTED) {
226       LogUtil.i("PhoneNumberInteraction.startInteraction", "No phone permissions");
227       ActivityCompat.requestPermissions(
228           (Activity) mContext, new String[] {Manifest.permission.CALL_PHONE}, REQUEST_CALL_PHONE);
229       return;
230     }
231     if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_CONTACTS)
232         != PackageManager.PERMISSION_GRANTED) {
233       LogUtil.i("PhoneNumberInteraction.startInteraction", "No contact permissions");
234       ActivityCompat.requestPermissions(
235           (Activity) mContext,
236           new String[] {Manifest.permission.READ_CONTACTS},
237           REQUEST_READ_CONTACTS);
238       return;
239     }
240 
241     if (mLoader != null) {
242       mLoader.reset();
243     }
244     final Uri queryUri;
245     final String inputUriAsString = uri.toString();
246     if (inputUriAsString.startsWith(Contacts.CONTENT_URI.toString())) {
247       if (!inputUriAsString.endsWith(Contacts.Data.CONTENT_DIRECTORY)) {
248         queryUri = Uri.withAppendedPath(uri, Contacts.Data.CONTENT_DIRECTORY);
249       } else {
250         queryUri = uri;
251       }
252     } else if (inputUriAsString.startsWith(Data.CONTENT_URI.toString())) {
253       queryUri = uri;
254     } else {
255       throw new UnsupportedOperationException(
256           "Input Uri must be contact Uri or data Uri (input: \"" + uri + "\")");
257     }
258 
259     mLoader =
260         new CursorLoader(
261             mContext, queryUri, PHONE_NUMBER_PROJECTION, PHONE_NUMBER_SELECTION, null, null);
262     mLoader.registerListener(0, this);
263     mLoader.startLoading();
264   }
265 
266   @Override
onLoadComplete(Loader<Cursor> loader, Cursor cursor)267   public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
268     if (cursor == null) {
269       LogUtil.i("PhoneNumberInteraction.onLoadComplete", "null cursor");
270       interactionError(InteractionErrorCode.OTHER_ERROR);
271       return;
272     }
273     try {
274       ArrayList<PhoneItem> phoneList = new ArrayList<>();
275       String primaryPhone = null;
276       if (!isSafeToCommitTransactions()) {
277         LogUtil.i("PhoneNumberInteraction.onLoadComplete", "not safe to commit transaction");
278         interactionError(InteractionErrorCode.USER_LEAVING_ACTIVITY);
279         return;
280       }
281       if (cursor.moveToFirst()) {
282         int contactIdColumn = cursor.getColumnIndexOrThrow(Phone.CONTACT_ID);
283         int isSuperPrimaryColumn = cursor.getColumnIndexOrThrow(Phone.IS_SUPER_PRIMARY);
284         int phoneNumberColumn = cursor.getColumnIndexOrThrow(Phone.NUMBER);
285         int phoneIdColumn = cursor.getColumnIndexOrThrow(Phone._ID);
286         int accountTypeColumn = cursor.getColumnIndexOrThrow(RawContacts.ACCOUNT_TYPE);
287         int dataSetColumn = cursor.getColumnIndexOrThrow(RawContacts.DATA_SET);
288         int phoneTypeColumn = cursor.getColumnIndexOrThrow(Phone.TYPE);
289         int phoneLabelColumn = cursor.getColumnIndexOrThrow(Phone.LABEL);
290         int phoneMimeTpeColumn = cursor.getColumnIndexOrThrow(Phone.MIMETYPE);
291         do {
292           if (mContactId == UNKNOWN_CONTACT_ID) {
293             mContactId = cursor.getLong(contactIdColumn);
294           }
295 
296           if (cursor.getInt(isSuperPrimaryColumn) != 0) {
297             // Found super primary, call it.
298             primaryPhone = cursor.getString(phoneNumberColumn);
299           }
300 
301           PhoneItem item = new PhoneItem();
302           item.id = cursor.getLong(phoneIdColumn);
303           item.phoneNumber = cursor.getString(phoneNumberColumn);
304           item.accountType = cursor.getString(accountTypeColumn);
305           item.dataSet = cursor.getString(dataSetColumn);
306           item.type = cursor.getInt(phoneTypeColumn);
307           item.label = cursor.getString(phoneLabelColumn);
308           item.mimeType = cursor.getString(phoneMimeTpeColumn);
309 
310           phoneList.add(item);
311         } while (cursor.moveToNext());
312       } else {
313         interactionError(InteractionErrorCode.CONTACT_NOT_FOUND);
314         return;
315       }
316 
317       if (primaryPhone != null) {
318         performAction(primaryPhone);
319         return;
320       }
321 
322       Collapser.collapseList(phoneList, mContext);
323       if (phoneList.size() == 0) {
324         interactionError(InteractionErrorCode.CONTACT_HAS_NO_NUMBER);
325       } else if (phoneList.size() == 1) {
326         PhoneItem item = phoneList.get(0);
327         performAction(item.phoneNumber);
328       } else {
329         // There are multiple candidates. Let the user choose one.
330         showDisambiguationDialog(phoneList);
331       }
332     } finally {
333       cursor.close();
334     }
335   }
336 
interactionError(@nteractionErrorCode int interactionErrorCode)337   private void interactionError(@InteractionErrorCode int interactionErrorCode) {
338     // mContext is really the activity -- see ctor docs.
339     ((InteractionErrorListener) mContext).interactionError(interactionErrorCode);
340   }
341 
isSafeToCommitTransactions()342   private boolean isSafeToCommitTransactions() {
343     return !(mContext instanceof TransactionSafeActivity)
344         || ((TransactionSafeActivity) mContext).isSafeToCommitTransactions();
345   }
346 
347   @VisibleForTesting
getLoader()348   /* package */ CursorLoader getLoader() {
349     return mLoader;
350   }
351 
showDisambiguationDialog(ArrayList<PhoneItem> phoneList)352   private void showDisambiguationDialog(ArrayList<PhoneItem> phoneList) {
353     final Activity activity = (Activity) mContext;
354     if (activity.isDestroyed()) {
355       // Check whether the activity is still running
356       LogUtil.i("PhoneNumberInteraction.showDisambiguationDialog", "activity destroyed");
357       return;
358     }
359     try {
360       PhoneDisambiguationDialogFragment.show(
361           activity.getFragmentManager(),
362           phoneList,
363           mInteractionType,
364           mIsVideoCall,
365           mCallSpecificAppData);
366     } catch (IllegalStateException e) {
367       // ignore to be safe. Shouldn't happen because we checked the
368       // activity wasn't destroyed, but to be safe.
369       LogUtil.e("PhoneNumberInteraction.showDisambiguationDialog", "caught exception", e);
370     }
371   }
372 
373   /** A model object for capturing a phone number for a given contact. */
374   @VisibleForTesting
375   /* package */ static class PhoneItem implements Parcelable, Collapsible<PhoneItem> {
376 
377     public static final Parcelable.Creator<PhoneItem> CREATOR =
378         new Parcelable.Creator<PhoneItem>() {
379           @Override
380           public PhoneItem createFromParcel(Parcel in) {
381             return new PhoneItem(in);
382           }
383 
384           @Override
385           public PhoneItem[] newArray(int size) {
386             return new PhoneItem[size];
387           }
388         };
389     long id;
390     String phoneNumber;
391     String accountType;
392     String dataSet;
393     long type;
394     String label;
395     /** {@link Phone#CONTENT_ITEM_TYPE} or {@link SipAddress#CONTENT_ITEM_TYPE}. */
396     String mimeType;
397 
PhoneItem()398     private PhoneItem() {}
399 
PhoneItem(Parcel in)400     private PhoneItem(Parcel in) {
401       this.id = in.readLong();
402       this.phoneNumber = in.readString();
403       this.accountType = in.readString();
404       this.dataSet = in.readString();
405       this.type = in.readLong();
406       this.label = in.readString();
407       this.mimeType = in.readString();
408     }
409 
410     @Override
writeToParcel(Parcel dest, int flags)411     public void writeToParcel(Parcel dest, int flags) {
412       dest.writeLong(id);
413       dest.writeString(phoneNumber);
414       dest.writeString(accountType);
415       dest.writeString(dataSet);
416       dest.writeLong(type);
417       dest.writeString(label);
418       dest.writeString(mimeType);
419     }
420 
421     @Override
describeContents()422     public int describeContents() {
423       return 0;
424     }
425 
426     @Override
collapseWith(PhoneItem phoneItem)427     public void collapseWith(PhoneItem phoneItem) {
428       // Just keep the number and id we already have.
429     }
430 
431     @Override
shouldCollapseWith(PhoneItem phoneItem, Context context)432     public boolean shouldCollapseWith(PhoneItem phoneItem, Context context) {
433       return MoreContactUtils.shouldCollapse(
434           Phone.CONTENT_ITEM_TYPE, phoneNumber, Phone.CONTENT_ITEM_TYPE, phoneItem.phoneNumber);
435     }
436 
437     @Override
toString()438     public String toString() {
439       return phoneNumber;
440     }
441   }
442 
443   /** A list adapter that populates the list of contact's phone numbers. */
444   private static class PhoneItemAdapter extends ArrayAdapter<PhoneItem> {
445 
446     private final int mInteractionType;
447 
PhoneItemAdapter(Context context, List<PhoneItem> list, int interactionType)448     PhoneItemAdapter(Context context, List<PhoneItem> list, int interactionType) {
449       super(context, R.layout.phone_disambig_item, android.R.id.text2, list);
450       mInteractionType = interactionType;
451     }
452 
453     @Override
getView(int position, View convertView, ViewGroup parent)454     public View getView(int position, View convertView, ViewGroup parent) {
455       final View view = super.getView(position, convertView, parent);
456 
457       final PhoneItem item = getItem(position);
458       Assert.isNotNull(item, "Null item at position: %d", position);
459       final TextView typeView = (TextView) view.findViewById(android.R.id.text1);
460       CharSequence value =
461           ContactDisplayUtils.getLabelForCallOrSms(
462               (int) item.type, item.label, mInteractionType, getContext());
463 
464       typeView.setText(value);
465       return view;
466     }
467   }
468 
469   /**
470    * {@link DialogFragment} used for displaying a dialog with a list of phone numbers of which one
471    * will be chosen to make a call or initiate an sms message.
472    *
473    * <p>It is recommended to use {@link #startInteractionForPhoneCall(TransactionSafeActivity, Uri,
474    * boolean, CallSpecificAppData)} instead of directly using this class, as those methods handle
475    * one or multiple data cases appropriately.
476    *
477    * <p>This fragment may only be attached to activities which implement {@link
478    * DisambigDialogDismissedListener}.
479    */
480   @SuppressWarnings("WeakerAccess") // Made public to let the system reach this class
481   public static class PhoneDisambiguationDialogFragment extends DialogFragment
482       implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
483 
484     private static final String ARG_PHONE_LIST = "phoneList";
485     private static final String ARG_INTERACTION_TYPE = "interactionType";
486     private static final String ARG_IS_VIDEO_CALL = "is_video_call";
487 
488     private int mInteractionType;
489     private ListAdapter mPhonesAdapter;
490     private List<PhoneItem> mPhoneList;
491     private CallSpecificAppData mCallSpecificAppData;
492     private boolean mIsVideoCall;
493 
PhoneDisambiguationDialogFragment()494     public PhoneDisambiguationDialogFragment() {
495       super();
496     }
497 
show( FragmentManager fragmentManager, ArrayList<PhoneItem> phoneList, int interactionType, boolean isVideoCall, CallSpecificAppData callSpecificAppData)498     public static void show(
499         FragmentManager fragmentManager,
500         ArrayList<PhoneItem> phoneList,
501         int interactionType,
502         boolean isVideoCall,
503         CallSpecificAppData callSpecificAppData) {
504       PhoneDisambiguationDialogFragment fragment = new PhoneDisambiguationDialogFragment();
505       Bundle bundle = new Bundle();
506       bundle.putParcelableArrayList(ARG_PHONE_LIST, phoneList);
507       bundle.putInt(ARG_INTERACTION_TYPE, interactionType);
508       bundle.putBoolean(ARG_IS_VIDEO_CALL, isVideoCall);
509       CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData);
510       fragment.setArguments(bundle);
511       fragment.show(fragmentManager, TAG);
512     }
513 
514     @Override
onCreateDialog(Bundle savedInstanceState)515     public Dialog onCreateDialog(Bundle savedInstanceState) {
516       final Activity activity = getActivity();
517       Assert.checkState(activity instanceof DisambigDialogDismissedListener);
518 
519       mPhoneList = getArguments().getParcelableArrayList(ARG_PHONE_LIST);
520       mInteractionType = getArguments().getInt(ARG_INTERACTION_TYPE);
521       mIsVideoCall = getArguments().getBoolean(ARG_IS_VIDEO_CALL);
522       mCallSpecificAppData = CallIntentParser.getCallSpecificAppData(getArguments());
523 
524       mPhonesAdapter = new PhoneItemAdapter(activity, mPhoneList, mInteractionType);
525       final LayoutInflater inflater = activity.getLayoutInflater();
526       @SuppressLint("InflateParams") // Allowed since dialog view is not available yet
527       final View setPrimaryView = inflater.inflate(R.layout.set_primary_checkbox, null);
528       return new AlertDialog.Builder(activity)
529           .setAdapter(mPhonesAdapter, this)
530           .setTitle(
531               mInteractionType == ContactDisplayUtils.INTERACTION_SMS
532                   ? R.string.sms_disambig_title
533                   : R.string.call_disambig_title)
534           .setView(setPrimaryView)
535           .create();
536     }
537 
538     @Override
onClick(DialogInterface dialog, int which)539     public void onClick(DialogInterface dialog, int which) {
540       final Activity activity = getActivity();
541       if (activity == null) {
542         return;
543       }
544       final AlertDialog alertDialog = (AlertDialog) dialog;
545       if (mPhoneList.size() > which && which >= 0) {
546         final PhoneItem phoneItem = mPhoneList.get(which);
547         final CheckBox checkBox = (CheckBox) alertDialog.findViewById(R.id.setPrimary);
548         if (checkBox.isChecked()) {
549           if (mCallSpecificAppData.getCallInitiationType() == CallInitiationType.Type.SPEED_DIAL) {
550             Logger.get(getContext())
551                 .logInteraction(
552                     InteractionEvent.Type.SPEED_DIAL_SET_DEFAULT_NUMBER_FOR_AMBIGUOUS_CONTACT);
553           }
554 
555           // Request to mark the data as primary in the background.
556           final Intent serviceIntent =
557               ContactUpdateService.createSetSuperPrimaryIntent(activity, phoneItem.id);
558           activity.startService(serviceIntent);
559         }
560 
561         PhoneNumberInteraction.performAction(
562             activity, phoneItem.phoneNumber, mInteractionType, mIsVideoCall, mCallSpecificAppData);
563       } else {
564         dialog.dismiss();
565       }
566     }
567 
568     @Override
onDismiss(DialogInterface dialogInterface)569     public void onDismiss(DialogInterface dialogInterface) {
570       super.onDismiss(dialogInterface);
571       Activity activity = getActivity();
572       if (activity != null) {
573         ((DisambigDialogDismissedListener) activity).onDisambigDialogDismissed();
574       }
575     }
576   }
577 }
578