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