diff options
Diffstat (limited to 'java/com/android/dialer')
7 files changed, 218 insertions, 131 deletions
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index 7cd22079c..55cef9a38 100644 --- a/java/com/android/dialer/logging/dialer_impression.proto +++ b/java/com/android/dialer/logging/dialer_impression.proto @@ -12,7 +12,7 @@ message DialerImpression { // Event enums to be used for Impression Logging in Dialer. // It's perfectly acceptable for this enum to be large // Values should be from 1000 to 100000. - // Next Tag: 1392 + // Next Tag: 1397 enum Type { UNKNOWN_AOSP_EVENT_TYPE = 1000; @@ -770,5 +770,14 @@ message DialerImpression { // Send button clicked in RTT call, this includes send button on keyboard. RTT_SEND_BUTTON_CLICKED = 1387; RTT_KEYBOARD_SEND_BUTTON_CLICKED = 1388; + + // For background calling + START_CALL_IN_BUBBLE_MODE = 1392; + + // Switch audio route + IN_CALL_SWITCH_AUDIO_ROUTE_SPEAKER = 1393; + IN_CALL_SWITCH_AUDIO_ROUTE_WIRED_HEADSET = 1394; + IN_CALL_SWITCH_AUDIO_ROUTE_EARPIECE = 1395; + IN_CALL_SWITCH_AUDIO_ROUTE_BLUETOOTH = 1396; } } diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 9ac351094..e3d42fa9e 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -457,6 +457,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen return DialpadFragment.isAddCallMode(intent); } + @SuppressLint("MissingPermission") @Override public void onActivityResume() { callLogFragmentListener.onActivityResume(); @@ -492,11 +493,16 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen bottomNavTabListener.ensureCorrectCallLogShown(); bottomNavTabListener.ensureCorrectVoicemailShown(); + // Config the badge of missed calls for the new call log. if (bottomNavTabListener.newCallLogFragmentActive()) { - missedCallCountObserver.onChange(false); // Set the initial value for the badge - activity - .getContentResolver() - .registerContentObserver(Calls.CONTENT_URI, true, missedCallCountObserver); + if (PermissionsUtil.hasCallLogReadPermissions(activity)) { + missedCallCountObserver.onChange(false); // Set the initial value for the badge + activity + .getContentResolver() + .registerContentObserver(Calls.CONTENT_URI, true, missedCallCountObserver); + } else { + bottomNav.setNotificationCount(TabIndex.CALL_LOG, 0); + } } // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. diff --git a/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java b/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java index ee7f6d006..a4995c1e6 100644 --- a/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java +++ b/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java @@ -16,15 +16,15 @@ package com.android.dialer.main.impl.bottomnav; -import android.annotation.SuppressLint; +import android.Manifest; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.provider.CallLog.Calls; +import android.support.annotation.RequiresPermission; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.UiListener; import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; -import com.android.dialer.util.PermissionsUtil; import com.google.common.util.concurrent.ListenableFuture; /** @@ -45,7 +45,7 @@ public final class MissedCallCountObserver extends ContentObserver { this.uiListener = uiListener; } - @SuppressLint("MissingPermission") + @RequiresPermission(Manifest.permission.READ_CALL_LOG) @Override public void onChange(boolean selfChange) { ListenableFuture<Integer> countFuture = @@ -53,9 +53,6 @@ public final class MissedCallCountObserver extends ContentObserver { .backgroundExecutor() .submit( () -> { - if (!PermissionsUtil.hasCallLogReadPermissions(appContext)) { - return 0; - } try (Cursor cursor = appContext .getContentResolver() diff --git a/java/com/android/dialer/speeddial/ContextMenu.java b/java/com/android/dialer/speeddial/ContextMenu.java index 126373c12..09505ab99 100644 --- a/java/com/android/dialer/speeddial/ContextMenu.java +++ b/java/com/android/dialer/speeddial/ContextMenu.java @@ -18,10 +18,12 @@ package com.android.dialer.speeddial; import android.content.Context; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.dialer.common.Assert; import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; import com.android.dialer.speeddial.loader.SpeedDialUiItem; @@ -30,13 +32,13 @@ public class ContextMenu extends LinearLayout { private ContextMenuItemListener listener; + private TextView voiceView; private TextView videoView; private TextView smsView; private SpeedDialUiItem speedDialUiItem; private Channel voiceChannel; private Channel videoChannel; - private Channel smsChannel; public ContextMenu(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -50,9 +52,11 @@ public class ContextMenu extends LinearLayout { videoView.setOnClickListener(v -> placeVideoCall()); smsView = findViewById(R.id.send_message_container); - smsView.setOnClickListener(v -> listener.openSmsConversation(smsChannel.number())); + smsView.setOnClickListener(v -> listener.openSmsConversation(voiceChannel.number())); + + voiceView = findViewById(R.id.voice_call_container); + voiceView.setOnClickListener(v -> placeVoiceCall()); - findViewById(R.id.voice_call_container).setOnClickListener(v -> placeVoiceCall()); findViewById(R.id.remove_container) .setOnClickListener(v -> listener.removeFavoriteContact(speedDialUiItem)); findViewById(R.id.contact_info_container) @@ -76,14 +80,11 @@ public class ContextMenu extends LinearLayout { setX((float) (childLocation[0] + .5 * childLayout.getWidth() - .5 * getWidth())); setY(childLocation[1] - parentLocation[1] + childLayout.getHeight()); - voiceChannel = speedDialUiItem.getDeterministicVoiceChannel(); - videoChannel = speedDialUiItem.getDeterministicVideoChannel(); - videoView.setVisibility( - videoChannel == null && !speedDialUiItem.hasVideoChannels() ? View.GONE : View.VISIBLE); - - // TODO(calderwoodra): disambig dialog for texts? - smsChannel = voiceChannel; - smsView.setVisibility(smsChannel == null ? View.GONE : View.VISIBLE); + voiceChannel = speedDialUiItem.getDefaultVoiceChannel(); + videoChannel = speedDialUiItem.getDefaultVideoChannel(); + voiceView.setVisibility(videoChannel == null ? View.GONE : View.VISIBLE); + videoView.setVisibility(videoChannel == null ? View.GONE : View.VISIBLE); + smsView.setVisibility(voiceChannel == null ? View.GONE : View.VISIBLE); // TODO(calderwoodra): a11y // TODO(calderwoodra): animate this similar to the bubble menu @@ -102,19 +103,11 @@ public class ContextMenu extends LinearLayout { } private void placeVoiceCall() { - if (voiceChannel == null) { - listener.disambiguateCall(speedDialUiItem); - } else { - listener.placeCall(voiceChannel); - } + listener.placeCall(Assert.isNotNull(voiceChannel)); } private void placeVideoCall() { - if (videoChannel == null) { - listener.disambiguateCall(speedDialUiItem); - } else { - listener.placeCall(videoChannel); - } + listener.placeCall(Assert.isNotNull(videoChannel)); } public boolean isVisible() { @@ -122,19 +115,12 @@ public class ContextMenu extends LinearLayout { } /** Listener to report user clicks on menu items. */ + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public interface ContextMenuItemListener { /** Called when the user selects "voice call" or "video call" option from the context menu. */ void placeCall(Channel channel); - /** - * Called when the user selects "voice call" or "video call" option from the context menu, but - * it's not clear which channel they want to call. - * - * <p>TODO(calderwoodra): discuss with product how we want to handle these cases - */ - void disambiguateCall(SpeedDialUiItem speedDialUiItem); - /** Called when the user selects "send message" from the context menu. */ void openSmsConversation(String number); diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index 018f97888..97a5facab 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -105,6 +105,10 @@ public class SpeedDialFragment extends Fragment { LogUtil.enterBlock("SpeedDialFragment.onCreateView"); View rootLayout = inflater.inflate(R.layout.fragment_speed_dial, container, false); + speedDialLoaderListener = + DialerExecutorComponent.get(getContext()) + .createUiListener(getChildFragmentManager(), "speed_dial_loader_listener"); + // Setup favorite contact context menu contextMenu = rootLayout.findViewById(R.id.favorite_contact_context_menu); contextMenuBackground = rootLayout.findViewById(R.id.context_menu_background); @@ -124,7 +128,10 @@ public class SpeedDialFragment extends Fragment { rootLayout, contextMenu, contextMenuBackground, - new SpeedDialContextMenuItemListener(getActivity(), getChildFragmentManager()), + new SpeedDialContextMenuItemListener( + getActivity(), + new UpdateSpeedDialAdapterListener(), + speedDialLoaderListener), layoutManager); adapter = new SpeedDialAdapter(getContext(), favoritesListener, suggestedListener, headerListener); @@ -138,10 +145,6 @@ public class SpeedDialFragment extends Fragment { ItemTouchHelper touchHelper = new ItemTouchHelper(callback); touchHelper.attachToRecyclerView(recyclerView); adapter.setItemTouchHelper(touchHelper); - - speedDialLoaderListener = - DialerExecutorComponent.get(getContext()) - .createUiListener(getChildFragmentManager(), "speed_dial_loader_listener"); return rootLayout; } @@ -317,19 +320,17 @@ public class SpeedDialFragment extends Fragment { Channel defaultChannel = speedDialUiItem.defaultChannel(); // Add voice call module - Channel voiceChannel = speedDialUiItem.getDeterministicVoiceChannel(); + Channel voiceChannel = speedDialUiItem.getDefaultVoiceChannel(); if (voiceChannel != null) { modules.add( IntentModule.newCallModule( getContext(), new CallIntentBuilder(voiceChannel.number(), CallInitiationType.Type.SPEED_DIAL) .setAllowAssistedDial(true))); - } else { - modules.add(new DisambigDialogModule(speedDialUiItem, /* isVideo = */ false)); } // Add video if we can determine the correct channel - Channel videoChannel = speedDialUiItem.getDeterministicVideoChannel(); + Channel videoChannel = speedDialUiItem.getDefaultVideoChannel(); if (videoChannel != null) { modules.add( IntentModule.newCallModule( @@ -337,8 +338,6 @@ public class SpeedDialFragment extends Fragment { new CallIntentBuilder(videoChannel.number(), CallInitiationType.Type.SPEED_DIAL) .setIsVideoCall(true) .setAllowAssistedDial(true))); - } else if (speedDialUiItem.hasVideoChannels()) { - modules.add(new DisambigDialogModule(speedDialUiItem, /* isVideo = */ true)); } // Add sms module @@ -396,58 +395,21 @@ public class SpeedDialFragment extends Fragment { return false; } } - - private final class DisambigDialogModule implements HistoryItemActionModule { - - private final SpeedDialUiItem speedDialUiItem; - private final boolean isVideo; - - DisambigDialogModule(SpeedDialUiItem speedDialUiItem, boolean isVideo) { - this.speedDialUiItem = speedDialUiItem; - this.isVideo = isVideo; - } - - @Override - public int getStringId() { - if (isVideo) { - return R.string.contact_menu_video_call; - } else { - return R.string.contact_menu_voice_call; - } - } - - @Override - public int getDrawableId() { - if (isVideo) { - return R.drawable.quantum_ic_videocam_vd_theme_24; - } else { - return R.drawable.quantum_ic_phone_vd_theme_24; - } - } - - @Override - public boolean onClick() { - DisambigDialog.show(speedDialUiItem, getChildFragmentManager()); - return true; - } - } } private static final class SpeedDialContextMenuItemListener implements ContextMenuItemListener { private final FragmentActivity activity; - private final FragmentManager childFragmentManager; + private final SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener; + private final UpdateSpeedDialAdapterListener updateAdapterListener; SpeedDialContextMenuItemListener( - FragmentActivity activity, FragmentManager childFragmentManager) { + FragmentActivity activity, + UpdateSpeedDialAdapterListener updateAdapterListener, + SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener) { this.activity = activity; - this.childFragmentManager = childFragmentManager; - } - - @Override - public void disambiguateCall(SpeedDialUiItem speedDialUiItem) { - // TODO(calderwoodra): show only video or voice channels in the disambig dialog - DisambigDialog.show(speedDialUiItem, childFragmentManager); + this.updateAdapterListener = updateAdapterListener; + this.speedDialLoaderListener = speedDialLoaderListener; } @Override @@ -472,7 +434,15 @@ public class SpeedDialFragment extends Fragment { @Override public void removeFavoriteContact(SpeedDialUiItem speedDialUiItem) { - // TODO(calderwoodra): implement remove + speedDialLoaderListener.listen( + activity, + UiItemLoaderComponent.get(activity) + .speedDialUiItemMutator() + .removeSpeedDialUiItem(speedDialUiItem), + updateAdapterListener::updateAdapter, + throwable -> { + throw new RuntimeException(throwable); + }); } @Override @@ -485,6 +455,14 @@ public class SpeedDialFragment extends Fragment { } } + /** Listener for when a SpeedDialUiItem is updated. */ + private class UpdateSpeedDialAdapterListener { + + void updateAdapter(ImmutableList<SpeedDialUiItem> speedDialUiItems) { + onSpeedDialUiItemListLoaded(speedDialUiItems); + } + } + /** Interface for {@link SpeedDialFragment} to communicate with its host/parent. */ public interface HostInterface { diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java index c5a3ea3ed..325af238a 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java @@ -162,61 +162,77 @@ public abstract class SpeedDialUiItem { } /** - * Returns a video channel if there is exactly one video channel or the default channel is a video - * channel. + * Returns one of the following: + * + * <ul> + * <li>The default channel if it's a video channel. + * <li>A video channel if it has the same attributes as the default channel, OR + * <li>null. (This is a deliberate product decision, even if there is only a single video + * reachable channel, we should still return null if it has different attributes from those + * in the default channel). + * </ul> */ @Nullable - public Channel getDeterministicVideoChannel() { - if (defaultChannel() != null && defaultChannel().isVideoTechnology()) { + public Channel getDefaultVideoChannel() { + if (defaultChannel() == null) { + return null; + } + + if (defaultChannel().isVideoTechnology()) { return defaultChannel(); } - Channel videoChannel = null; - for (Channel channel : channels()) { - if (channel.isVideoTechnology()) { - if (videoChannel != null) { - // We found two video channels, so we can't determine which one is correct.. - return null; - } - videoChannel = channel; - } + if (channels().size() == 1) { + // If there is only a single channel, it can't be a video channel + return null; } - // Only found one channel, so return it - return videoChannel; - } - /** Returns true if any channels are video channels. */ - public boolean hasVideoChannels() { - for (Channel channel : channels()) { - if (channel.isVideoTechnology()) { - return true; + // At this point, the default channel is a *voice* channel and there are more than + // one channel in total. + // + // Our defined assumptions about the channel list include that if a video channel + // follows a voice channel, it has the same attributes as that voice channel + // (see comments on method channels() for details). + // + // Therefore, if the default video channel exists, it must be the immediate successor + // of the default channel in the list. + // + // Note that we don't have to check if the last channel in the list is the default + // channel because even if it is, there will be no video channel under the assumption + // above. + for (int i = 0; i < channels().size() - 1; i++) { + // Find the default channel + if (Objects.equals(defaultChannel(), channels().get(i))) { + // Our defined assumptions about the list of channels is that if a video channel follows a + // voice channel, it has the same attributes as that voice channel. + Channel channel = channels().get(i + 1); + if (channel.isVideoTechnology()) { + return channel; + } + // Since the default voice channel isn't video reachable, we can't video call this number + return null; } } - return false; + throw Assert.createIllegalStateFailException("channels() doesn't contain defaultChannel()."); } /** - * Returns a voice channel if there is exactly one voice channel or the default channel is a voice + * Returns a voice channel if there is exactly one channel or the default channel is a voice * channel. */ @Nullable - public Channel getDeterministicVoiceChannel() { + public Channel getDefaultVoiceChannel() { if (defaultChannel() != null && !defaultChannel().isVideoTechnology()) { return defaultChannel(); } - Channel voiceChannel = null; - for (Channel channel : channels()) { - if (!channel.isVideoTechnology()) { - if (voiceChannel != null) { - // We found two voice channels, so we can't determine which one is correct.. - return null; - } - voiceChannel = channel; - } + if (channels().size() == 1) { + // If there is only a single channel, it must be a voice channel as per our defined + // assumptions (detailed in comments on method channels()). + return channels().get(0); } - // Only found one channel, so return it - return voiceChannel; + + return null; } /** @@ -254,6 +270,21 @@ public abstract class SpeedDialUiItem { * enumerate each one here so that the user can choose the correct one. Each channel here * represents a row in the {@link com.android.dialer.speeddial.DisambigDialog}. * + * <p>These channels have a few very strictly enforced assumption that are used heavily throughout + * the codebase. Those assumption are that: + * + * <ol> + * <li>Each of the contact's numbers are voice reachable. So if a channel has it's technology + * set to anything other than {@link Channel#VOICE}, there is gaurenteed to be another + * channel with the exact same attributes, but technology will be {@link Channel#VOICE}. + * <li>For each of the contact's phone numbers, there will be a voice channel, then the next + * channel will either be the same phone number but a video channel, or a new number. + * </ol> + * + * For example: Say a contact has two phone numbers (A & B) and A is duo reachable. Then you can + * assume the list of channels will be ordered as either {A_voice, A_duo, B_voice} or {B_voice, + * A_voice, A_duo}. + * * @see com.android.dialer.speeddial.database.SpeedDialEntry.Channel */ public abstract ImmutableList<Channel> channels(); diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java index 5dae2efab..e8892c431 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java @@ -54,6 +54,7 @@ import com.google.common.util.concurrent.ListeningExecutorService; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -109,6 +110,85 @@ public final class SpeedDialUiItemMutator { } /** + * Delete the SpeedDialUiItem. + * + * <p>If the item is starred, it's entry will be removed from the SpeedDialEntry database. + * Additionally, if the contact only has one entry in the database, it will be unstarred. + * + * <p>If the item isn't starred, it's usage data will be deleted but the suggestion can come back + * if the user calls that contact again. + * + * @return the updated list of SpeedDialUiItems. + */ + public ListenableFuture<ImmutableList<SpeedDialUiItem>> removeSpeedDialUiItem( + SpeedDialUiItem speedDialUiItem) { + return dialerFutureSerializer.submit( + () -> removeSpeedDialUiItemInternal(speedDialUiItem), backgroundExecutor); + } + + @WorkerThread + private ImmutableList<SpeedDialUiItem> removeSpeedDialUiItemInternal( + SpeedDialUiItem speedDialUiItem) { + Assert.isWorkerThread(); + if (speedDialUiItem.isStarred()) { + removeStarredSpeedDialUiItem(speedDialUiItem); + } else { + removeSuggestedSpeedDialUiItem(speedDialUiItem); + } + return loadSpeedDialUiItemsInternal(); + } + + /** + * Delete the SpeedDialEntry associated with the passed in SpeedDialUiItem. Additionally, if the + * entry being deleted is the only entry for that contact, unstar it in the cp2. + */ + @WorkerThread + private void removeStarredSpeedDialUiItem(SpeedDialUiItem speedDialUiItem) { + Assert.isWorkerThread(); + Assert.checkArgument(speedDialUiItem.isStarred()); + SpeedDialEntryDao db = getSpeedDialEntryDao(); + ImmutableList<SpeedDialEntry> entries = db.getAllEntries(); + + SpeedDialEntry entryToDelete = null; + int entriesForTheSameContact = 0; + for (SpeedDialEntry entry : entries) { + if (entry.contactId() == speedDialUiItem.contactId()) { + entriesForTheSameContact++; + } + + if (Objects.equals(entry.id(), speedDialUiItem.speedDialEntryId())) { + Assert.checkArgument(entryToDelete == null); + entryToDelete = entry; + } + } + db.delete(ImmutableList.of(entryToDelete.id())); + if (entriesForTheSameContact == 1) { + unstarContact(speedDialUiItem); + } + } + + @WorkerThread + private void unstarContact(SpeedDialUiItem speedDialUiItem) { + Assert.isWorkerThread(); + ContentValues contentValues = new ContentValues(); + contentValues.put(Phone.STARRED, 0); + appContext + .getContentResolver() + .update( + Phone.CONTENT_URI, + contentValues, + Phone.CONTACT_ID + " = ?", + new String[] {Long.toString(speedDialUiItem.contactId())}); + } + + @WorkerThread + @SuppressWarnings("unused") + private void removeSuggestedSpeedDialUiItem(SpeedDialUiItem speedDialUiItem) { + Assert.isWorkerThread(); + // TODO(calderwoodra): remove strequent contact + } + + /** * Takes a contact uri from {@link Phone#CONTENT_URI} and updates {@link Phone#STARRED} to be * true, if it isn't already or Inserts the contact into the {@link SpeedDialEntryDatabaseHelper} */ |