diff options
author | Eric Erfanian <erfanian@google.com> | 2018-04-29 05:32:38 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-04-29 05:32:38 +0000 |
commit | eac73f3067acefffaa8eec7b7a9d8b3cc176e674 (patch) | |
tree | b0856f5ceba502b88d8e8970cd6cf9295fc83561 /java/com/android/dialer | |
parent | c28ed7444c25d52fd3996f8edf076a1d740d4e05 (diff) | |
parent | 16b77d4d484dca3d4a070709cfb046c6fba8c993 (diff) |
Merge changes I7be0efad,I4d6fb104,Idc15d873,I6b3241bc,If8c6e798, ...
* changes:
Updated logic on showing voice/video/sms options in favorites menus.
Implemented remove starred contact from speed dial fragment.
Remove audio change behavior for background calling.
Move DialerCall.State to an independent package.
Logging for background calling and HFP device support.
HFP device support change.
Handle missing permission.READ_CALL_LOG when initializing the badge for missed calls.
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} */ |