• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.dialer.speeddial;
18 
19 import android.Manifest;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.database.ContentObserver;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.provider.ContactsContract.CommonDataKinds.Phone;
28 import android.provider.ContactsContract.Contacts;
29 import android.support.annotation.NonNull;
30 import android.support.annotation.Nullable;
31 import android.support.annotation.VisibleForTesting;
32 import android.support.v4.app.Fragment;
33 import android.support.v4.app.FragmentActivity;
34 import android.support.v4.app.FragmentManager;
35 import android.support.v7.app.AppCompatActivity;
36 import android.support.v7.widget.RecyclerView;
37 import android.support.v7.widget.helper.ItemTouchHelper;
38 import android.text.TextUtils;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import com.android.dialer.callintent.CallInitiationType;
43 import com.android.dialer.callintent.CallIntentBuilder;
44 import com.android.dialer.common.Assert;
45 import com.android.dialer.common.FragmentUtils;
46 import com.android.dialer.common.LogUtil;
47 import com.android.dialer.common.concurrent.DefaultFutureCallback;
48 import com.android.dialer.common.concurrent.DialerExecutorComponent;
49 import com.android.dialer.common.concurrent.SupportUiListener;
50 import com.android.dialer.common.concurrent.ThreadUtil;
51 import com.android.dialer.constants.ActivityRequestCodes;
52 import com.android.dialer.historyitemactions.DividerModule;
53 import com.android.dialer.historyitemactions.HistoryItemActionBottomSheet;
54 import com.android.dialer.historyitemactions.HistoryItemActionModule;
55 import com.android.dialer.historyitemactions.HistoryItemBottomSheetHeaderInfo;
56 import com.android.dialer.historyitemactions.IntentModule;
57 import com.android.dialer.logging.DialerImpression;
58 import com.android.dialer.logging.Logger;
59 import com.android.dialer.precall.PreCall;
60 import com.android.dialer.shortcuts.ShortcutRefresher;
61 import com.android.dialer.speeddial.ContextMenu.ContextMenuItemListener;
62 import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener;
63 import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener;
64 import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener;
65 import com.android.dialer.speeddial.database.SpeedDialEntry.Channel;
66 import com.android.dialer.speeddial.draghelper.SpeedDialItemTouchHelperCallback;
67 import com.android.dialer.speeddial.draghelper.SpeedDialLayoutManager;
68 import com.android.dialer.speeddial.loader.SpeedDialUiItem;
69 import com.android.dialer.speeddial.loader.UiItemLoaderComponent;
70 import com.android.dialer.util.IntentUtil;
71 import com.android.dialer.util.PermissionsUtil;
72 import com.android.dialer.widget.EmptyContentView;
73 import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
74 import com.google.common.collect.ImmutableList;
75 import com.google.common.util.concurrent.Futures;
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.List;
79 
80 /**
81  * Fragment for displaying:
82  *
83  * <ul>
84  *   <li>Favorite/Starred contacts
85  *   <li>Suggested contacts
86  * </ul>
87  *
88  * <p>Suggested contacts built from {@link android.provider.ContactsContract#STREQUENT_PHONE_ONLY}.
89  */
90 public class SpeedDialFragment extends Fragment {
91 
92   private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
93 
94   /**
95    * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS
96    * permission is granted via the UI in another fragment.
97    */
98   private final BroadcastReceiver readContactsPermissionGrantedReceiver =
99       new BroadcastReceiver() {
100         @Override
101         public void onReceive(Context context, Intent intent) {
102           loadContacts();
103         }
104       };
105 
106   /** Listen for changes to the strequents content observer. */
107   private final ContentObserver strequentsContentObserver =
108       new ContentObserver(ThreadUtil.getUiThreadHandler()) {
109         @Override
110         public void onChange(boolean selfChange) {
111           super.onChange(selfChange);
112           loadContacts();
113         }
114       };
115 
116   private final SpeedDialHeaderListener headerListener = new SpeedDialFragmentHeaderListener();
117   private final SpeedDialSuggestedListener suggestedListener = new SpeedDialSuggestedListener();
118 
119   private SpeedDialAdapter adapter;
120   private SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener;
121   private SpeedDialFavoritesListener favoritesListener;
122 
123   private EmptyContentView emptyContentView;
124 
125   /**
126    * We update the UI every time the fragment is resumed. This boolean suppresses that functionality
127    * once per onResume call.
128    */
129   private boolean updateSpeedDialItemsOnResume = true;
130 
newInstance()131   public static SpeedDialFragment newInstance() {
132     return new SpeedDialFragment();
133   }
134 
135   @Nullable
136   @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)137   public View onCreateView(
138       LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
139     LogUtil.enterBlock("SpeedDialFragment.onCreateView");
140     View rootLayout = inflater.inflate(R.layout.fragment_speed_dial, container, false);
141     emptyContentView = rootLayout.findViewById(R.id.speed_dial_empty_content_view);
142     emptyContentView.setImage(R.drawable.empty_speed_dial);
143 
144     speedDialLoaderListener =
145         DialerExecutorComponent.get(getContext())
146             .createUiListener(getChildFragmentManager(), "speed_dial_loader_listener");
147 
148     // Setup our RecyclerView
149     SpeedDialLayoutManager layoutManager =
150         new SpeedDialLayoutManager(getContext(), 3 /* spanCount */);
151     favoritesListener =
152         new SpeedDialFavoritesListener(
153             getActivity(),
154             getChildFragmentManager(),
155             layoutManager,
156             new UpdateSpeedDialAdapterListener(),
157             speedDialLoaderListener);
158     adapter =
159         new SpeedDialAdapter(
160             getContext(),
161             favoritesListener,
162             suggestedListener,
163             headerListener,
164             FragmentUtils.getParentUnsafe(this, HostInterface.class));
165     layoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup());
166     RecyclerView recyclerView = rootLayout.findViewById(R.id.speed_dial_recycler_view);
167     recyclerView.setLayoutManager(layoutManager);
168     recyclerView.setAdapter(adapter);
169 
170     // Setup drag and drop touch helper
171     ItemTouchHelper.Callback callback = new SpeedDialItemTouchHelperCallback(getContext(), adapter);
172     ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
173     touchHelper.attachToRecyclerView(recyclerView);
174     adapter.setItemTouchHelper(touchHelper);
175     return rootLayout;
176   }
177 
178   @Override
onResume()179   public void onResume() {
180     super.onResume();
181     loadContacts();
182   }
183 
184   @Override
onPause()185   public void onPause() {
186     super.onPause();
187     favoritesListener.hideMenu();
188     suggestedListener.onPause();
189     onHidden();
190   }
191 
192   @Override
onHiddenChanged(boolean hidden)193   public void onHiddenChanged(boolean hidden) {
194     super.onHiddenChanged(hidden);
195     if (hidden) {
196       onHidden();
197     } else {
198       loadContacts();
199     }
200   }
201 
onHidden()202   private void onHidden() {
203     if (!PermissionsUtil.hasContactsReadPermissions(getContext())) {
204       return;
205     }
206 
207     Futures.addCallback(
208         DialerExecutorComponent.get(getContext())
209             .backgroundExecutor()
210             .submit(
211                 () -> {
212                   UiItemLoaderComponent.get(getContext())
213                       .speedDialUiItemMutator()
214                       .updatePinnedPosition(adapter.getSpeedDialUiItems());
215                   return null;
216                 }),
217         new DefaultFutureCallback<>(),
218         DialerExecutorComponent.get(getContext()).backgroundExecutor());
219     ShortcutRefresher.refresh(
220         getContext(),
221         ShortcutRefresher.speedDialUiItemsToContactEntries(adapter.getSpeedDialUiItems()));
222   }
223 
224   @Override
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)225   public void onRequestPermissionsResult(
226       int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
227     if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE
228         && grantResults.length > 0
229         && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
230       PermissionsUtil.notifyPermissionGranted(getContext(), Manifest.permission.READ_CONTACTS);
231       loadContacts();
232     }
233   }
234 
loadContacts()235   private void loadContacts() {
236     if (!updateSpeedDialItemsOnResume) {
237       updateSpeedDialItemsOnResume = true;
238       return;
239     }
240 
241     if (showContactsPermissionEmptyContentView()) {
242       return;
243     }
244 
245     speedDialLoaderListener.listen(
246         getContext(),
247         UiItemLoaderComponent.get(getContext()).speedDialUiItemMutator().loadSpeedDialUiItems(),
248         this::onSpeedDialUiItemListLoaded,
249         throwable -> {
250           throw new RuntimeException(throwable);
251         });
252   }
253 
254   @Override
onActivityResult(int requestCode, int resultCode, Intent data)255   public void onActivityResult(int requestCode, int resultCode, Intent data) {
256     if (requestCode == ActivityRequestCodes.SPEED_DIAL_ADD_FAVORITE) {
257       if (resultCode == AppCompatActivity.RESULT_OK && data.getData() != null) {
258         Logger.get(getContext()).logImpression(DialerImpression.Type.FAVORITE_ADD_FAVORITE);
259         updateSpeedDialItemsOnResume = false;
260         speedDialLoaderListener.listen(
261             getContext(),
262             UiItemLoaderComponent.get(getContext())
263                 .speedDialUiItemMutator()
264                 .starContact(data.getData()),
265             this::onSpeedDialUiItemListLoaded,
266             throwable -> {
267               throw new RuntimeException(throwable);
268             });
269       }
270     }
271   }
272 
onSpeedDialUiItemListLoaded(ImmutableList<SpeedDialUiItem> speedDialUiItems)273   private void onSpeedDialUiItemListLoaded(ImmutableList<SpeedDialUiItem> speedDialUiItems) {
274     LogUtil.enterBlock("SpeedDialFragment.onSpeedDialUiItemListLoaded");
275     // TODO(calderwoodra): Use DiffUtil to properly update and animate the change
276     adapter.setSpeedDialUiItems(
277         UiItemLoaderComponent.get(getContext())
278             .speedDialUiItemMutator()
279             .insertDuoChannels(getContext(), speedDialUiItems));
280     adapter.notifyDataSetChanged();
281     maybeShowNoContactsEmptyContentView();
282 
283     if (getActivity() != null) {
284       FragmentUtils.getParentUnsafe(this, HostInterface.class)
285           .setHasFrequents(adapter.hasFrequents());
286     }
287   }
288 
289   /** Returns true if the empty content view was shown. */
showContactsPermissionEmptyContentView()290   private boolean showContactsPermissionEmptyContentView() {
291     if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
292       emptyContentView.setVisibility(View.GONE);
293       return false;
294     }
295 
296     emptyContentView.setVisibility(View.VISIBLE);
297     emptyContentView.setActionLabel(R.string.speed_dial_turn_on_contacts_permission);
298     emptyContentView.setDescription(R.string.speed_dial_contacts_permission_description);
299     emptyContentView.setActionClickedListener(
300         new SpeedDialContactPermissionEmptyViewListener(getContext(), this));
301     return true;
302   }
303 
maybeShowNoContactsEmptyContentView()304   private void maybeShowNoContactsEmptyContentView() {
305     if (adapter.getItemCount() != 0) {
306       emptyContentView.setVisibility(View.GONE);
307       return;
308     }
309 
310     emptyContentView.setVisibility(View.VISIBLE);
311     emptyContentView.setActionLabel(R.string.speed_dial_no_contacts_action_text);
312     emptyContentView.setDescription(R.string.speed_dial_no_contacts_description);
313     emptyContentView.setActionClickedListener(new SpeedDialNoContactsEmptyViewListener(this));
314   }
315 
316   @Override
onStart()317   public void onStart() {
318     super.onStart();
319     PermissionsUtil.registerPermissionReceiver(
320         getActivity(), readContactsPermissionGrantedReceiver, Manifest.permission.READ_CONTACTS);
321     if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
322       getContext()
323           .getContentResolver()
324           .registerContentObserver(Contacts.CONTENT_STREQUENT_URI, true, strequentsContentObserver);
325     }
326   }
327 
328   @Override
onStop()329   public void onStop() {
330     super.onStop();
331     PermissionsUtil.unregisterPermissionReceiver(
332         getContext(), readContactsPermissionGrantedReceiver);
333     getContext().getContentResolver().unregisterContentObserver(strequentsContentObserver);
334   }
335 
336   private class SpeedDialFragmentHeaderListener implements SpeedDialHeaderListener {
337 
338     @Override
onAddFavoriteClicked()339     public void onAddFavoriteClicked() {
340       Intent intent = new Intent(Intent.ACTION_PICK, Phone.CONTENT_URI);
341       startActivityForResult(intent, ActivityRequestCodes.SPEED_DIAL_ADD_FAVORITE);
342     }
343   }
344 
345   @VisibleForTesting
346   static final class SpeedDialFavoritesListener implements FavoriteContactsListener {
347 
348     private final FragmentActivity activity;
349     private final FragmentManager childFragmentManager;
350     private final SpeedDialLayoutManager layoutManager;
351     private final UpdateSpeedDialAdapterListener updateAdapterListener;
352     private final SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener;
353 
354     private final SpeedDialContextMenuItemListener speedDialContextMenuItemListener =
355         new SpeedDialContextMenuItemListener();
356 
357     private ContextMenu contextMenu;
358 
SpeedDialFavoritesListener( FragmentActivity activity, FragmentManager childFragmentManager, SpeedDialLayoutManager layoutManager, UpdateSpeedDialAdapterListener updateAdapterListener, SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener)359     SpeedDialFavoritesListener(
360         FragmentActivity activity,
361         FragmentManager childFragmentManager,
362         SpeedDialLayoutManager layoutManager,
363         UpdateSpeedDialAdapterListener updateAdapterListener,
364         SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener) {
365       this.activity = activity;
366       this.childFragmentManager = childFragmentManager;
367       this.layoutManager = layoutManager;
368       this.updateAdapterListener = updateAdapterListener;
369       this.speedDialLoaderListener = speedDialLoaderListener;
370     }
371 
372     @Override
onAmbiguousContactClicked(SpeedDialUiItem speedDialUiItem)373     public void onAmbiguousContactClicked(SpeedDialUiItem speedDialUiItem) {
374       // If there is only one channel, skip the menu and place a call directly
375       if (speedDialUiItem.channels().size() == 1) {
376         onClick(speedDialUiItem.channels().get(0));
377         return;
378       }
379 
380       Logger.get(activity).logImpression(DialerImpression.Type.FAVORITE_OPEN_DISAMBIG_DIALOG);
381       DisambigDialog.show(speedDialUiItem, childFragmentManager);
382     }
383 
384     @Override
onClick(Channel channel)385     public void onClick(Channel channel) {
386       if (channel.technology() == Channel.DUO) {
387         Logger.get(activity)
388             .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT);
389       }
390 
391       PreCall.start(
392           activity,
393           new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL)
394               .setAllowAssistedDial(true)
395               .setIsVideoCall(channel.isVideoTechnology())
396               .setIsDuoCall(channel.technology() == Channel.DUO));
397     }
398 
399     @Override
showContextMenu(View view, SpeedDialUiItem speedDialUiItem)400     public void showContextMenu(View view, SpeedDialUiItem speedDialUiItem) {
401       Logger.get(activity).logImpression(DialerImpression.Type.FAVORITE_OPEN_FAVORITE_MENU);
402       layoutManager.setScrollEnabled(false);
403       contextMenu =
404           ContextMenu.show(activity, view, speedDialContextMenuItemListener, speedDialUiItem);
405     }
406 
407     @Override
onTouchFinished(boolean closeContextMenu)408     public void onTouchFinished(boolean closeContextMenu) {
409       layoutManager.setScrollEnabled(true);
410 
411       if (closeContextMenu) {
412         contextMenu.hide();
413         contextMenu = null;
414       }
415     }
416 
417     @Override
onRequestRemove(SpeedDialUiItem speedDialUiItem)418     public void onRequestRemove(SpeedDialUiItem speedDialUiItem) {
419       Logger.get(activity)
420           .logImpression(DialerImpression.Type.FAVORITE_REMOVE_FAVORITE_BY_DRAG_AND_DROP);
421       speedDialContextMenuItemListener.removeFavoriteContact(speedDialUiItem);
422     }
423 
hideMenu()424     void hideMenu() {
425       if (contextMenu != null) {
426         contextMenu.hide();
427         contextMenu = null;
428       }
429     }
430 
getSpeedDialContextMenuItemListener()431     public SpeedDialContextMenuItemListener getSpeedDialContextMenuItemListener() {
432       return speedDialContextMenuItemListener;
433     }
434 
435     class SpeedDialContextMenuItemListener implements ContextMenuItemListener {
436 
437       @Override
placeCall(Channel channel)438       public void placeCall(Channel channel) {
439         if (channel.technology() == Channel.DUO) {
440           Logger.get(activity)
441               .logImpression(
442                   DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT);
443         }
444         PreCall.start(
445             activity,
446             new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL)
447                 .setAllowAssistedDial(true)
448                 .setIsVideoCall(channel.isVideoTechnology())
449                 .setIsDuoCall(channel.technology() == Channel.DUO));
450       }
451 
452       @Override
openSmsConversation(String number)453       public void openSmsConversation(String number) {
454         Logger.get(activity).logImpression(DialerImpression.Type.FAVORITE_SEND_MESSAGE);
455         activity.startActivity(IntentUtil.getSendSmsIntent(number));
456       }
457 
458       @Override
removeFavoriteContact(SpeedDialUiItem speedDialUiItem)459       public void removeFavoriteContact(SpeedDialUiItem speedDialUiItem) {
460         Logger.get(activity).logImpression(DialerImpression.Type.FAVORITE_REMOVE_FAVORITE);
461         speedDialLoaderListener.listen(
462             activity,
463             UiItemLoaderComponent.get(activity)
464                 .speedDialUiItemMutator()
465                 .removeSpeedDialUiItem(speedDialUiItem),
466             updateAdapterListener::updateAdapter,
467             throwable -> {
468               throw new RuntimeException(throwable);
469             });
470       }
471 
472       @Override
openContactInfo(SpeedDialUiItem speedDialUiItem)473       public void openContactInfo(SpeedDialUiItem speedDialUiItem) {
474         Logger.get(activity).logImpression(DialerImpression.Type.FAVORITE_OPEN_CONTACT_CARD);
475         activity.startActivity(
476             new Intent(
477                 Intent.ACTION_VIEW,
478                 Uri.withAppendedPath(
479                     Contacts.CONTENT_URI, String.valueOf(speedDialUiItem.contactId()))));
480       }
481     }
482   }
483 
484   private final class SpeedDialSuggestedListener implements SuggestedContactsListener {
485 
486     private HistoryItemActionBottomSheet bottomSheet;
487 
488     @Override
onOverFlowMenuClicked( SpeedDialUiItem speedDialUiItem, HistoryItemBottomSheetHeaderInfo headerInfo)489     public void onOverFlowMenuClicked(
490         SpeedDialUiItem speedDialUiItem, HistoryItemBottomSheetHeaderInfo headerInfo) {
491       List<HistoryItemActionModule> modules = new ArrayList<>();
492       Channel defaultChannel = speedDialUiItem.defaultChannel();
493 
494       // Add voice call module
495       Channel voiceChannel = speedDialUiItem.getDefaultVoiceChannel();
496       if (voiceChannel != null) {
497         modules.add(
498             IntentModule.newCallModule(
499                 getContext(),
500                 new CallIntentBuilder(voiceChannel.number(), CallInitiationType.Type.SPEED_DIAL)
501                     .setAllowAssistedDial(true)));
502       }
503 
504       // Add video if we can determine the correct channel
505       Channel videoChannel = speedDialUiItem.getDefaultVideoChannel();
506       if (videoChannel != null) {
507         modules.add(
508             IntentModule.newCallModule(
509                 getContext(),
510                 new CallIntentBuilder(videoChannel.number(), CallInitiationType.Type.SPEED_DIAL)
511                     .setIsVideoCall(true)
512                     .setAllowAssistedDial(true)));
513       }
514 
515       // Add sms module
516       if (!TextUtils.isEmpty(defaultChannel.number())) {
517         modules.add(
518             IntentModule.newModuleForSendingTextMessage(getContext(), defaultChannel.number()));
519       }
520 
521       modules.add(new DividerModule());
522 
523       modules.add(new StarContactModule(speedDialUiItem));
524       // TODO(calderwoodra): remove from strequent module
525 
526       // Contact info module
527       modules.add(
528           new ContactInfoModule(
529               getContext(),
530               new Intent(
531                   Intent.ACTION_VIEW,
532                   Uri.withAppendedPath(
533                       Contacts.CONTENT_URI, String.valueOf(speedDialUiItem.contactId()))),
534               R.string.contact_menu_contact_info,
535               R.drawable.context_menu_contact_icon));
536 
537       bottomSheet = HistoryItemActionBottomSheet.show(getContext(), headerInfo, modules);
538     }
539 
540     @Override
onRowClicked(Channel channel)541     public void onRowClicked(Channel channel) {
542       if (channel.technology() == Channel.DUO) {
543         Logger.get(getContext())
544             .logImpression(
545                 DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_SUGGESTED_CONTACT);
546       }
547       PreCall.start(
548           getContext(),
549           new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL)
550               .setAllowAssistedDial(true)
551               .setIsVideoCall(channel.isVideoTechnology())
552               .setIsDuoCall(channel.technology() == Channel.DUO));
553     }
554 
555     private final class StarContactModule implements HistoryItemActionModule {
556 
557       private final SpeedDialUiItem speedDialUiItem;
558 
StarContactModule(SpeedDialUiItem speedDialUiItem)559       StarContactModule(SpeedDialUiItem speedDialUiItem) {
560         this.speedDialUiItem = speedDialUiItem;
561       }
562 
563       @Override
getStringId()564       public int getStringId() {
565         return R.string.suggested_contact_bottom_sheet_add_favorite_option;
566       }
567 
568       @Override
getDrawableId()569       public int getDrawableId() {
570         return R.drawable.quantum_ic_star_vd_theme_24;
571       }
572 
573       @Override
onClick()574       public boolean onClick() {
575         speedDialLoaderListener.listen(
576             getContext(),
577             UiItemLoaderComponent.get(getContext())
578                 .speedDialUiItemMutator()
579                 .starContact(
580                     Uri.withAppendedPath(
581                         Phone.CONTENT_FILTER_URI, speedDialUiItem.defaultChannel().number())),
582             SpeedDialFragment.this::onSpeedDialUiItemListLoaded,
583             throwable -> {
584               throw new RuntimeException(throwable);
585             });
586         return true;
587       }
588     }
589 
590     private final class ContactInfoModule extends IntentModule {
591 
ContactInfoModule(Context context, Intent intent, int text, int image)592       public ContactInfoModule(Context context, Intent intent, int text, int image) {
593         super(context, intent, text, image);
594       }
595 
596       @Override
tintDrawable()597       public boolean tintDrawable() {
598         return false;
599       }
600     }
601 
onPause()602     public void onPause() {
603       if (bottomSheet != null && bottomSheet.isShowing()) {
604         bottomSheet.dismiss();
605       }
606     }
607   }
608 
609   private static final class SpeedDialContactPermissionEmptyViewListener
610       implements OnEmptyViewActionButtonClickedListener {
611 
612     private final Context context;
613     private final Fragment fragment;
614 
SpeedDialContactPermissionEmptyViewListener(Context context, Fragment fragment)615     private SpeedDialContactPermissionEmptyViewListener(Context context, Fragment fragment) {
616       this.context = context;
617       this.fragment = fragment;
618     }
619 
620     @Override
onEmptyViewActionButtonClicked()621     public void onEmptyViewActionButtonClicked() {
622       String[] deniedPermissions =
623           PermissionsUtil.getPermissionsCurrentlyDenied(
624               context, PermissionsUtil.allContactsGroupPermissionsUsedInDialer);
625       Assert.checkArgument(deniedPermissions.length > 0);
626       LogUtil.i(
627           "OldSpeedDialFragment.onEmptyViewActionButtonClicked",
628           "Requesting permissions: " + Arrays.toString(deniedPermissions));
629       fragment.requestPermissions(deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
630     }
631   }
632 
633   private static final class SpeedDialNoContactsEmptyViewListener
634       implements OnEmptyViewActionButtonClickedListener {
635 
636     private final Fragment fragment;
637 
SpeedDialNoContactsEmptyViewListener(Fragment fragment)638     SpeedDialNoContactsEmptyViewListener(Fragment fragment) {
639       this.fragment = fragment;
640     }
641 
642     @Override
onEmptyViewActionButtonClicked()643     public void onEmptyViewActionButtonClicked() {
644       Intent intent = new Intent(Intent.ACTION_PICK, Phone.CONTENT_URI);
645       fragment.startActivityForResult(intent, ActivityRequestCodes.SPEED_DIAL_ADD_FAVORITE);
646     }
647   }
648 
649   /** Listener for when a SpeedDialUiItem is updated. */
650   class UpdateSpeedDialAdapterListener {
651 
updateAdapter(ImmutableList<SpeedDialUiItem> speedDialUiItems)652     void updateAdapter(ImmutableList<SpeedDialUiItem> speedDialUiItems) {
653       onSpeedDialUiItemListLoaded(speedDialUiItems);
654     }
655   }
656 
657   /** Interface for {@link SpeedDialFragment} to communicate with its host/parent. */
658   public interface HostInterface {
659 
setHasFrequents(boolean hasFrequents)660     void setHasFrequents(boolean hasFrequents);
661 
dragFavorite(boolean start)662     void dragFavorite(boolean start);
663   }
664 }
665