diff options
87 files changed, 1381 insertions, 556 deletions
diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java index b99cef11f..c2c753e43 100644 --- a/java/com/android/dialer/app/calllog/CallLogAdapter.java +++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java @@ -64,7 +64,6 @@ import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter.OnVoicemailDe import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.calldetails.CallDetailsEntries; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; -import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction; import com.android.dialer.calllogutils.PhoneCallDetails; import com.android.dialer.common.Assert; @@ -407,28 +406,7 @@ public class CallLogAdapter extends GroupingListAdapter } } expandViewHolderActions(viewHolder); - - if (isDuoCallButtonVisible(viewHolder.videoCallButtonView)) { - CallIntentBuilder.increaseLightbringerCallButtonAppearInExpandedCallLogItemCount(); - } - } - } - - private boolean isDuoCallButtonVisible(View videoCallButtonView) { - if (videoCallButtonView == null) { - return false; - } - if (videoCallButtonView.getVisibility() != View.VISIBLE) { - return false; - } - IntentProvider intentProvider = (IntentProvider) videoCallButtonView.getTag(); - if (intentProvider == null) { - return false; } - return DuoComponent.get(activity) - .getDuo() - .getIntentType(intentProvider.getIntent(activity)) - .isPresent(); } }; diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index 54748387e..9b7741df4 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -19,7 +19,6 @@ package com.android.dialer.app.calllog; import android.Manifest.permission; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -51,7 +50,6 @@ import android.view.ViewStub; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; import com.android.contacts.common.dialog.CallSubjectDialog; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; @@ -96,7 +94,6 @@ import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.UriUtils; -import com.google.common.base.Optional; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; @@ -550,7 +547,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder case CallbackAction.DUO: if (showDuoPrimaryButton()) { CallIntentBuilder.increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount(); - primaryActionButtonView.setTag(IntentProvider.getDuoVideoIntentProvider(number)); + primaryActionButtonView.setTag( + IntentProvider.getDuoVideoIntentProvider(number, isNonContactEntry(info))); } else { primaryActionButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); } @@ -684,14 +682,17 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder boolean identifiedSpamCall = isSpamFeatureEnabled && isSpam; if (duo.isReachable(context, number)) { - videoCallButtonView.setTag(IntentProvider.getDuoVideoIntentProvider(number)); + videoCallButtonView.setTag( + IntentProvider.getDuoVideoIntentProvider(number, isNonContactEntry(info))); videoCallButtonView.setVisibility(View.VISIBLE); + CallIntentBuilder.increaseLightbringerCallButtonAppearInExpandedCallLogItemCount(); } else if (duo.isActivated(context) && !identifiedSpamCall) { if (ConfigProviderBindings.get(context) .getBoolean("enable_call_log_duo_invite_button", false)) { inviteVideoButtonView.setTag(IntentProvider.getDuoInviteIntentProvider(number)); inviteVideoButtonView.setVisibility(View.VISIBLE); Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE_SHOWN); + CallIntentBuilder.increaseLightbringerCallButtonAppearInExpandedCallLogItemCount(); } } else if (duo.isEnabled(context) && !identifiedSpamCall) { if (!duo.isInstalled(context)) { @@ -701,6 +702,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder setUpVideoButtonView.setVisibility(View.VISIBLE); Logger.get(context) .logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_INSTALL_SHOWN); + CallIntentBuilder.increaseLightbringerCallButtonAppearInExpandedCallLogItemCount(); } } else { if (ConfigProviderBindings.get(context) @@ -709,6 +711,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder setUpVideoButtonView.setVisibility(View.VISIBLE); Logger.get(context) .logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE_SHOWN); + CallIntentBuilder.increaseLightbringerCallButtonAppearInExpandedCallLogItemCount(); } } } @@ -782,7 +785,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder callComposeButtonView.setVisibility(isCallComposerCapable ? View.VISIBLE : View.GONE); - updateBlockReportActions(isVoicemailNumber); + updateBlockReportActions(canPlaceCallToNumber, isVoicemailNumber); } private boolean isFullyUndialableVoicemail() { @@ -1024,20 +1027,13 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder if (intentProvider == null) { return; } - + intentProvider.logInteraction(context); final Intent intent = intentProvider.getIntent(context); // See IntentProvider.getCallDetailIntentProvider() for why this may be null. if (intent == null) { return; } - - // We check to see if we are starting a Duo intent. The reason is Duo - // intents need to be started using startActivityForResult instead of the usual startActivity - Optional<Duo.IntentType> duoIntentType = - DuoComponent.get(context).getDuo().getIntentType(intent); - if (duoIntentType.isPresent()) { - startDuoActivity(intent, duoIntentType.get()); - } else if (OldCallDetailsActivity.isLaunchIntent(intent)) { + if (OldCallDetailsActivity.isLaunchIntent(intent)) { PerformanceReport.recordClick(UiAction.Type.OPEN_CALL_DETAIL); ((Activity) context) .startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_CALL_DETAILS); @@ -1046,9 +1042,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder && intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, -1) == VideoProfile.STATE_BIDIRECTIONAL) { Logger.get(context).logImpression(DialerImpression.Type.IMS_VIDEO_REQUESTED_FROM_CALL_LOG); - } else if (intent.filterEquals( - DuoComponent.get(context).getDuo().getInstallDuoIntent().orNull())) { - Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_INSTALL); } DialerUtils.startActivityWithErrorToast(context, intent); @@ -1062,32 +1055,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder return false; } - private void startDuoActivity(Intent intent, Duo.IntentType intentType) { - switch (intentType) { - case CALL: - Logger.get(context) - .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG); - if (isNonContactEntry(info)) { - Logger.get(context) - .logImpression( - DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_VIDEO_REQUESTED_FROM_CALL_LOG); - } - break; - case INVITE: - Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE); - break; - case ACTIVATE: - Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE); - break; - } - try { - Activity activity = (Activity) context; - activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); - } catch (ActivityNotFoundException e) { - Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show(); - } - } - private DialerContact buildContact() { DialerContact.Builder contact = DialerContact.newBuilder(); contact.setPhotoId(info.photoId); @@ -1172,14 +1139,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } - private void updateBlockReportActions(boolean isVoicemailNumber) { + private void updateBlockReportActions(boolean canPlaceCallToNumber, boolean isVoicemailNumber) { // Set block/spam actions. blockReportView.setVisibility(View.GONE); blockView.setVisibility(View.GONE); unblockView.setVisibility(View.GONE); reportNotSpamView.setVisibility(View.GONE); String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); - if (isVoicemailNumber + if (!canPlaceCallToNumber + || isVoicemailNumber || !FilteredNumbersUtil.canBlockNumber(context, e164Number, number) || !FilteredNumberCompat.canAttemptBlockOperations(context)) { return; @@ -1260,7 +1228,9 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); boolean isVoicemailNumber = callLogCache.isVoicemailNumber(accountHandle, number); - if (!isVoicemailNumber + boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation); + if (canPlaceCallToNumber + && !isVoicemailNumber && FilteredNumbersUtil.canBlockNumber(context, e164Number, number) && FilteredNumberCompat.canAttemptBlockOperations(context)) { boolean isBlocked = blockId != null; diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java index 1bc726f64..21f341815 100644 --- a/java/com/android/dialer/app/calllog/IntentProvider.java +++ b/java/com/android/dialer/app/calllog/IntentProvider.java @@ -32,6 +32,8 @@ import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.duo.DuoComponent; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCall; import com.android.dialer.util.IntentUtil; import java.util.ArrayList; @@ -93,11 +95,26 @@ public abstract class IntentProvider { }; } - public static IntentProvider getDuoVideoIntentProvider(String number) { + public static IntentProvider getDuoVideoIntentProvider(String number, boolean isNonContact) { return new IntentProvider() { @Override public Intent getIntent(Context context) { - return DuoComponent.get(context).getDuo().getCallIntent(number).orNull(); + return PreCall.getIntent( + context, + new CallIntentBuilder(number, CallInitiationType.Type.CALL_LOG) + .setIsDuoCall(true) + .setIsVideoCall(true)); + } + + @Override + public void logInteraction(Context context) { + Logger.get(context) + .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG); + if (isNonContact) { + Logger.get(context) + .logImpression( + DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_VIDEO_REQUESTED_FROM_CALL_LOG); + } } }; } @@ -108,6 +125,11 @@ public abstract class IntentProvider { public Intent getIntent(Context context) { return DuoComponent.get(context).getDuo().getInstallDuoIntent().orNull(); } + + @Override + public void logInteraction(Context context) { + Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_INSTALL); + } }; } @@ -117,6 +139,11 @@ public abstract class IntentProvider { public Intent getIntent(Context context) { return DuoComponent.get(context).getDuo().getActivateIntent().orNull(); } + + @Override + public void logInteraction(Context context) { + Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE); + } }; } @@ -126,6 +153,11 @@ public abstract class IntentProvider { public Intent getIntent(Context context) { return DuoComponent.get(context).getDuo().getInviteIntent(number).orNull(); } + + @Override + public void logInteraction(Context context) { + Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE); + } }; } @@ -244,4 +276,6 @@ public abstract class IntentProvider { } public abstract Intent getIntent(Context context); + + public void logInteraction(Context context) {} } diff --git a/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java b/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java index 80a41919d..86a2676f5 100644 --- a/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java +++ b/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java @@ -19,7 +19,6 @@ package com.android.dialer.calldetails; import android.Manifest.permission; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -35,7 +34,6 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; -import android.widget.Toast; import com.android.dialer.assisteddialing.ui.AssistedDialingSettingActivity; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; import com.android.dialer.callintent.CallInitiationType; @@ -48,9 +46,6 @@ import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.UiListener; import com.android.dialer.common.database.Selection; -import com.android.dialer.constants.ActivityRequestCodes; -import com.android.dialer.duo.Duo; -import com.android.dialer.duo.DuoComponent; import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; @@ -63,7 +58,6 @@ import com.android.dialer.postcall.PostCall; import com.android.dialer.precall.PreCall; import com.android.dialer.rtt.RttTranscriptActivity; import com.android.dialer.rtt.RttTranscriptUtil; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.i18n.phonenumbers.NumberParseException; @@ -327,19 +321,11 @@ abstract class CallDetailsActivityCommon extends AppCompatActivity { public void placeDuoVideoCall(String phoneNumber) { Logger.get(getActivity()) .logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK); - Duo duo = DuoComponent.get(getActivity()).getDuo(); - Optional<Intent> intentOptional = duo.getCallIntent(phoneNumber); - if (!duo.isReachable(getActivity(), phoneNumber) || !intentOptional.isPresent()) { - placeImsVideoCall(phoneNumber); - return; - } - - try { - getActivity() - .startActivityForResult(intentOptional.get(), ActivityRequestCodes.DIALTACTS_DUO); - } catch (ActivityNotFoundException e) { - Toast.makeText(getActivity(), R.string.activity_not_available, Toast.LENGTH_SHORT).show(); - } + PreCall.start( + getActivity(), + new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS) + .setIsDuoCall(true) + .setIsVideoCall(true)); } @Override diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java index cd1752d74..44b5a4319 100644 --- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java @@ -190,7 +190,13 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder .loadQuickContactBadge(contactPhoto, headerInfo.getPhotoInfo()); nameView.setText(headerInfo.getPrimaryText()); - numberView.setText(headerInfo.getSecondaryText()); + if (!headerInfo.getSecondaryText().isEmpty()) { + numberView.setVisibility(View.VISIBLE); + numberView.setText(headerInfo.getSecondaryText()); + } else { + numberView.setVisibility(View.GONE); + numberView.setText(null); + } setCallbackAction(callbackAction); } diff --git a/java/com/android/dialer/callintent/CallIntent.java b/java/com/android/dialer/callintent/CallIntent.java new file mode 100644 index 000000000..ba61d5619 --- /dev/null +++ b/java/com/android/dialer/callintent/CallIntent.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.callintent; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telecom.VideoProfile; +import android.text.TextUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.performancereport.PerformanceReport; +import com.android.dialer.util.CallUtil; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.InvalidProtocolBufferException; + +/** Creates an intent to start a new outgoing call. */ +@AutoValue +public abstract class CallIntent implements Parcelable { + private static int lightbringerButtonAppearInExpandedCallLogItemCount = 0; + private static int lightbringerButtonAppearInCollapsedCallLogItemCount = 0; + private static int lightbringerButtonAppearInSearchCount = 0; + + abstract Uri number(); + + abstract CallSpecificAppData callSpecificAppData(); + + @Nullable + abstract PhoneAccountHandle phoneAccountHandle(); + + abstract boolean isVideoCall(); + + @Nullable + abstract String callSubject(); + + abstract boolean allowAssistedDial(); + + abstract ImmutableMap<String, String> stringInCallUiIntentExtras(); + + abstract ImmutableMap<String, Long> longInCallUiIntentExtras(); + + abstract ImmutableMap<String, String> stringPlaceCallExtras(); + + abstract ImmutableMap<String, Long> longPlaceCallExtras(); + + public static Builder builder() { + return new AutoValue_CallIntent.Builder().setIsVideoCall(false).setAllowAssistedDial(false); + } + + public abstract Builder toBuilder(); + + /** Builder class for CallIntent info. */ + @AutoValue.Builder + public abstract static class Builder { + public Builder setTelNumber(String number) { + return setNumber(CallUtil.getCallUri(Assert.isNotNull(number))); + } + + public Builder setVoicemailNumber(@Nullable PhoneAccountHandle phoneAccountHandle) { + return setNumber(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null)) + .setPhoneAccountHandle(phoneAccountHandle); + } + + public abstract Builder setNumber(@NonNull Uri number); + + public Builder setCallInitiationType(CallInitiationType.Type callInitiationType) { + return setCallSpecificAppData( + CallSpecificAppData.newBuilder().setCallInitiationType(callInitiationType).build()); + } + + abstract CallSpecificAppData callSpecificAppData(); + + public abstract Builder setCallSpecificAppData( + @NonNull CallSpecificAppData callSpecificAppData); + + public abstract Builder setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle); + + public abstract Builder setIsVideoCall(boolean isVideoCall); + + public abstract Builder setCallSubject(String callSubject); + + public abstract Builder setAllowAssistedDial(boolean allowAssistedDial); + + abstract ImmutableMap.Builder<String, String> stringInCallUiIntentExtrasBuilder(); + + abstract ImmutableMap.Builder<String, Long> longInCallUiIntentExtrasBuilder(); + + public Builder addInCallUiIntentExtra(String key, String value) { + stringInCallUiIntentExtrasBuilder().put(key, value); + return this; + } + + public Builder addInCallUiIntentExtra(String key, Long value) { + longInCallUiIntentExtrasBuilder().put(key, value); + return this; + } + + abstract ImmutableMap.Builder<String, String> stringPlaceCallExtrasBuilder(); + + abstract ImmutableMap.Builder<String, Long> longPlaceCallExtrasBuilder(); + + public Builder addPlaceCallExtra(String key, String value) { + stringPlaceCallExtrasBuilder().put(key, value); + return this; + } + + public Builder addPlaceCallExtra(String key, Long value) { + longPlaceCallExtrasBuilder().put(key, value); + return this; + } + + abstract CallIntent autoBuild(); + + public Intent build() { + CallSpecificAppData.Builder builder = + CallSpecificAppData.newBuilder(callSpecificAppData()) + .setLightbringerButtonAppearInExpandedCallLogItemCount( + lightbringerButtonAppearInExpandedCallLogItemCount) + .setLightbringerButtonAppearInCollapsedCallLogItemCount( + lightbringerButtonAppearInCollapsedCallLogItemCount) + .setLightbringerButtonAppearInSearchCount(lightbringerButtonAppearInSearchCount); + lightbringerButtonAppearInExpandedCallLogItemCount = 0; + lightbringerButtonAppearInCollapsedCallLogItemCount = 0; + lightbringerButtonAppearInSearchCount = 0; + + if (PerformanceReport.isRecording()) { + builder + .setTimeSinceAppLaunch(PerformanceReport.getTimeSinceAppLaunch()) + .setTimeSinceFirstClick(PerformanceReport.getTimeSinceFirstClick()) + .addAllUiActionsSinceAppLaunch(PerformanceReport.getActions()) + .addAllUiActionTimestampsSinceAppLaunch(PerformanceReport.getActionTimestamps()) + .setStartingTabIndex(PerformanceReport.getStartingTabIndex()) + .build(); + PerformanceReport.stopRecording(); + } + + setCallSpecificAppData(builder.build()); + + // Validate CallIntent. + CallIntent callIntent = autoBuild(); + Assert.isNotNull(callIntent.number()); + Assert.isNotNull(callIntent.callSpecificAppData()); + Assert.checkArgument( + callIntent.callSpecificAppData().getCallInitiationType() + != CallInitiationType.Type.UNKNOWN_INITIATION); + + return autoBuild().newIntent(); + } + } + + // Creates the intent which can start a call + private Intent newIntent() { + Intent intent = new Intent(Intent.ACTION_CALL, number()); + + intent.putExtra( + TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, + isVideoCall() ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY); + + Bundle inCallUiIntentExtras = createInCallUiIntentExtras(); + inCallUiIntentExtras.putLong( + Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime()); + + intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, inCallUiIntentExtras); + + if (phoneAccountHandle() != null) { + intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle()); + } + + if (!TextUtils.isEmpty(callSubject())) { + intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject()); + } + + intent.putExtras(createPlaceCallExtras()); + + return intent; + } + + @SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs. + private Bundle createInCallUiIntentExtras() { + Bundle bundle = new Bundle(); + stringInCallUiIntentExtras().forEach(bundle::putString); + longInCallUiIntentExtras().forEach(bundle::putLong); + CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData()); + return bundle; + } + + @SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs. + private Bundle createPlaceCallExtras() { + Bundle bundle = new Bundle(); + stringPlaceCallExtras().forEach(bundle::putString); + longPlaceCallExtras().forEach(bundle::putLong); + CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData()); + return bundle; + } + + public static void increaseLightbringerCallButtonAppearInExpandedCallLogItemCount() { + CallIntent.lightbringerButtonAppearInExpandedCallLogItemCount++; + } + + public static void increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount() { + CallIntent.lightbringerButtonAppearInCollapsedCallLogItemCount++; + } + + public static void increaseLightbringerCallButtonAppearInSearchCount() { + CallIntent.lightbringerButtonAppearInSearchCount++; + } + + @VisibleForTesting + public static int getLightbringerButtonAppearInExpandedCallLogItemCount() { + return lightbringerButtonAppearInExpandedCallLogItemCount; + } + + @VisibleForTesting + public static int getLightbringerButtonAppearInCollapsedCallLogItemCount() { + return lightbringerButtonAppearInCollapsedCallLogItemCount; + } + + @VisibleForTesting + public static int getLightbringerButtonAppearInSearchCount() { + return lightbringerButtonAppearInSearchCount; + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public static void clearLightbringerCounts() { + lightbringerButtonAppearInCollapsedCallLogItemCount = 0; + lightbringerButtonAppearInExpandedCallLogItemCount = 0; + lightbringerButtonAppearInSearchCount = 0; + } + + @Override + public int describeContents() { + return 0; + } + + @SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs. + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(number(), flags); + dest.writeByteArray(callSpecificAppData().toByteArray()); + dest.writeParcelable(phoneAccountHandle(), flags); + dest.writeInt(isVideoCall() ? 1 : 0); + dest.writeString(callSubject()); + dest.writeInt(allowAssistedDial() ? 1 : 0); + Bundle stringInCallUiIntentExtrasBundle = new Bundle(); + stringInCallUiIntentExtras().forEach(stringInCallUiIntentExtrasBundle::putString); + dest.writeBundle(stringInCallUiIntentExtrasBundle); + Bundle longInCallUiIntentExtrasBundle = new Bundle(); + longInCallUiIntentExtras().forEach(longInCallUiIntentExtrasBundle::putLong); + dest.writeBundle(longInCallUiIntentExtrasBundle); + } + + // @TODO(justinmcclain): Investigate deleting the parcelable logic and instead switching + // to using an internal proto for serialization. + public static final Creator<CallIntent> CREATOR = + new Creator<CallIntent>() { + @Override + public CallIntent createFromParcel(Parcel source) { + CallIntent.Builder callIntentBuilder = builder(); + ClassLoader classLoader = CallIntent.class.getClassLoader(); + callIntentBuilder.setNumber(source.readParcelable(classLoader)); + CallSpecificAppData data; + try { + data = CallSpecificAppData.parseFrom(source.createByteArray()); + } catch (InvalidProtocolBufferException e) { + data = CallSpecificAppData.getDefaultInstance(); + } + callIntentBuilder + .setCallSpecificAppData(data) + .setPhoneAccountHandle(source.readParcelable(classLoader)) + .setIsVideoCall(source.readInt() != 0) + .setCallSubject(source.readString()) + .setAllowAssistedDial(source.readInt() != 0); + Bundle stringInCallUiIntentExtrasBundle = source.readBundle(classLoader); + for (String key : stringInCallUiIntentExtrasBundle.keySet()) { + callIntentBuilder.addInCallUiIntentExtra( + key, stringInCallUiIntentExtrasBundle.getString(key)); + } + Bundle longInCallUiIntentExtrasBundle = source.readBundle(classLoader); + for (String key : longInCallUiIntentExtrasBundle.keySet()) { + callIntentBuilder.addInCallUiIntentExtra( + key, longInCallUiIntentExtrasBundle.getLong(key)); + } + return callIntentBuilder.autoBuild(); + } + + @Override + public CallIntent[] newArray(int size) { + return new CallIntent[0]; + } + }; +} diff --git a/java/com/android/dialer/callintent/CallIntentBuilder.java b/java/com/android/dialer/callintent/CallIntentBuilder.java index 92efd392b..613fdf6a3 100644 --- a/java/com/android/dialer/callintent/CallIntentBuilder.java +++ b/java/com/android/dialer/callintent/CallIntentBuilder.java @@ -43,6 +43,7 @@ public class CallIntentBuilder implements Parcelable { private final CallSpecificAppData callSpecificAppData; @Nullable private PhoneAccountHandle phoneAccountHandle; private boolean isVideoCall; + private boolean isDuoCall; private String callSubject; private boolean allowAssistedDial; @@ -109,6 +110,7 @@ public class CallIntentBuilder implements Parcelable { callSpecificAppData = data; phoneAccountHandle = parcel.readParcelable(classLoader); isVideoCall = parcel.readInt() != 0; + isDuoCall = parcel.readInt() != 0; callSubject = parcel.readString(); allowAssistedDial = parcel.readInt() != 0; inCallUiIntentExtras.putAll(parcel.readBundle(classLoader)); @@ -152,6 +154,15 @@ public class CallIntentBuilder implements Parcelable { return isVideoCall; } + public CallIntentBuilder setIsDuoCall(boolean isDuoCall) { + this.isDuoCall = isDuoCall; + return this; + } + + public boolean isDuoCall() { + return isDuoCall; + } + /** Default false. Should only be set to true if the number has a lookup URI. */ public CallIntentBuilder setAllowAssistedDial(boolean allowAssistedDial) { this.allowAssistedDial = allowAssistedDial; @@ -267,6 +278,7 @@ public class CallIntentBuilder implements Parcelable { dest.writeByteArray(callSpecificAppData.toByteArray()); dest.writeParcelable(phoneAccountHandle, flags); dest.writeInt(isVideoCall ? 1 : 0); + dest.writeInt(isDuoCall ? 1 : 0); dest.writeString(callSubject); dest.writeInt(allowAssistedDial ? 1 : 0); dest.writeBundle(inCallUiIntentExtras); diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java index 32c278838..fb3700efe 100644 --- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -156,8 +156,7 @@ public class RefreshAnnotatedCallLogWorker { for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { ListenableFuture<Boolean> dataSourceDirty = dataSource.isDirty(); isDirtyFutures.add(dataSourceDirty); - String eventName = - String.format(Metrics.IS_DIRTY_TEMPLATE, dataSource.getClass().getSimpleName()); + String eventName = String.format(Metrics.IS_DIRTY_TEMPLATE, dataSource.getLoggingName()); futureTimer.applyTiming(dataSourceDirty, eventName, LogCatMode.LOG_VALUES); } // Simultaneously invokes isDirty on all data sources, returning as soon as one returns true. @@ -242,7 +241,7 @@ public class RefreshAnnotatedCallLogWorker { private static String eventNameForFill(CallLogDataSource dataSource, boolean isBuilt) { return String.format( !isBuilt ? Metrics.INITIAL_FILL_TEMPLATE : Metrics.FILL_TEMPLATE, - dataSource.getClass().getSimpleName()); + dataSource.getLoggingName()); } private static String eventNameForOverallFill(boolean isBuilt) { @@ -255,7 +254,7 @@ public class RefreshAnnotatedCallLogWorker { !isBuilt ? Metrics.INITIAL_ON_SUCCESSFUL_FILL_TEMPLATE : Metrics.ON_SUCCESSFUL_FILL_TEMPLATE, - dataSource.getClass().getSimpleName()); + dataSource.getLoggingName()); } private static String eventNameForOverallOnSuccessfulFill(boolean isBuilt) { diff --git a/java/com/android/dialer/calllog/database/contract/number_attributes.proto b/java/com/android/dialer/calllog/database/contract/number_attributes.proto index f42974d36..2c46d1b12 100644 --- a/java/com/android/dialer/calllog/database/contract/number_attributes.proto +++ b/java/com/android/dialer/calllog/database/contract/number_attributes.proto @@ -24,7 +24,7 @@ package com.android.dialer; import "java/com/android/dialer/logging/contact_source.proto"; // Information related to the phone number of the call. -// Next ID: 14 +// Next ID: 15 message NumberAttributes { // The name (which may be a person's name or business name, but not a number) // formatted exactly as it should appear to the user. If the user's locale or @@ -74,4 +74,7 @@ message NumberAttributes { // Description of the number's geolocation (e.g., "Mountain View, CA"). // This string is for display purpose only. optional string geolocation = 13; + + // Whether the number is an emergency number. + optional bool is_emergency_number = 14; }
\ No newline at end of file diff --git a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java index f6796c767..75f06d5f6 100644 --- a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java @@ -113,4 +113,11 @@ public interface CallLogDataSource { */ @MainThread ListenableFuture<Void> clearData(); + + /** + * The name of this daa source for logging purposes. This is generally the same as the class name + * (but should not use methods from {@link Class} because the class names are generally obfuscated + * by Proguard. + */ + String getLoggingName(); } diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 0de987308..66d29a7ef 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -320,6 +320,11 @@ public final class PhoneLookupDataSource implements CallLogDataSource { MoreExecutors.directExecutor()); } + @Override + public String getLoggingName() { + return "PhoneLookupDataSource"; + } + private static ImmutableSet<DialerPhoneNumber> queryDistinctDialerPhoneNumbersFromAnnotatedCallLog(Context appContext) { ImmutableSet.Builder<DialerPhoneNumber> numbers = ImmutableSet.builder(); diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index b5067daac..a08b50eb8 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -148,6 +148,11 @@ public class SystemCallLogDataSource implements CallLogDataSource { } @Override + public String getLoggingName() { + return "SystemCallLogDataSource"; + } + + @Override public ListenableFuture<Boolean> isDirty() { return backgroundExecutorService.submit(this::isDirtyInternal); } diff --git a/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java b/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java index ab9288a1e..7a230220e 100644 --- a/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java +++ b/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java @@ -121,4 +121,9 @@ public class VoicemailDataSource implements CallLogDataSource { public ListenableFuture<Void> clearData() { return Futures.immediateFuture(null); } + + @Override + public String getLoggingName() { + return "VoicemailDataSource"; + } } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java index f373a7c69..7fd8132aa 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java @@ -15,6 +15,7 @@ */ package com.android.dialer.calllog.ui; +import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; @@ -72,7 +73,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { } private final Clock clock; - private final Context context; + private final Activity activity; private final RealtimeRowProcessor realtimeRowProcessor; private final PopCounts popCounts = new PopCounts(); private final SharedPreferences sharedPref; @@ -93,12 +94,12 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { /** Position of the "Older" header. Null when it should not be displayed. */ @Nullable private Integer olderHeaderPosition; - NewCallLogAdapter(Context context, Cursor cursor, Clock clock) { - this.context = context; + NewCallLogAdapter(Activity activity, Cursor cursor, Clock clock) { + this.activity = activity; this.cursor = cursor; this.clock = clock; - this.realtimeRowProcessor = CallLogUiComponent.get(context).realtimeRowProcessor(); - this.sharedPref = StorageComponent.get(context).unencryptedSharedPrefs(); + this.realtimeRowProcessor = CallLogUiComponent.get(activity).realtimeRowProcessor(); + this.sharedPref = StorageComponent.get(activity).unencryptedSharedPrefs(); this.onScrollListenerForRecordingDuoDisclosureFirstViewTime = new OnScrollListenerForRecordingDuoDisclosureFirstViewTime(sharedPref, clock); @@ -175,8 +176,8 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { // Don't show the Duo disclosure card if // (1) Duo integration is not enabled on the device, or // (2) Duo is not activated. - Duo duo = DuoComponent.get(context).getDuo(); - if (!duo.isEnabled(context) || !duo.isActivated(context)) { + Duo duo = DuoComponent.get(activity).getDuo(); + if (!duo.isEnabled(activity) || !duo.isActivated(activity)) { return false; } @@ -218,7 +219,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { switch (viewType) { case RowType.DUO_DISCLOSURE_CARD: return new DuoDisclosureCardViewHolder( - LayoutInflater.from(context) + LayoutInflater.from(activity) .inflate( R.layout.new_call_log_duo_disclosure_card, viewGroup, @@ -227,11 +228,12 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { case RowType.HEADER_YESTERDAY: case RowType.HEADER_OLDER: return new HeaderViewHolder( - LayoutInflater.from(context) + LayoutInflater.from(activity) .inflate(R.layout.new_call_log_header, viewGroup, /* attachToRoot = */ false)); case RowType.CALL_LOG_ENTRY: return new NewCallLogViewHolder( - LayoutInflater.from(context) + activity, + LayoutInflater.from(activity) .inflate(R.layout.new_call_log_entry, viewGroup, /* attachToRoot = */ false), clock, realtimeRowProcessor, @@ -249,7 +251,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { ((DuoDisclosureCardViewHolder) viewHolder) .setDismissListener( unused -> { - StorageComponent.get(context) + StorageComponent.get(activity) .unencryptedSharedPrefs() .edit() .putBoolean(SHARED_PREF_KEY_DUO_DISCLOSURE_DISMISSED, true) diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 0f1c2510a..bc5750770 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -15,6 +15,7 @@ */ package com.android.dialer.calllog.ui; +import android.app.Activity; import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; @@ -30,6 +31,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.ThreadUtil; @@ -229,8 +231,11 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback // TODO(zachh): Handle empty cursor by showing empty view. if (recyclerView.getAdapter() == null) { recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + // Note: It's not clear if this callback can be invoked when there's no associated activity, + // but if crashes are observed here it may be possible to use getContext() instead. + Activity activity = Assert.isNotNull(getActivity()); recyclerView.setAdapter( - new NewCallLogAdapter(getContext(), newCursor, System::currentTimeMillis)); + new NewCallLogAdapter(activity, newCursor, System::currentTimeMillis)); } else { ((NewCallLogAdapter) recyclerView.getAdapter()).updateCursor(newCursor); } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java index c02d80ede..5f3cd96c4 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java @@ -72,9 +72,13 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { private long currentRowId; NewCallLogViewHolder( - View view, Clock clock, RealtimeRowProcessor realtimeRowProcessor, PopCounts popCounts) { + Activity activity, + View view, + Clock clock, + RealtimeRowProcessor realtimeRowProcessor, + PopCounts popCounts) { super(view); - this.activity = (Activity) view.getContext(); + this.activity = activity; contactPhotoView = view.findViewById(R.id.contact_photo_view); primaryTextView = view.findViewById(R.id.primary_text); callCountTextView = view.findViewById(R.id.call_count); diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java index b06e0fb1a..cfeca1059 100644 --- a/java/com/android/dialer/calllog/ui/menu/Modules.java +++ b/java/com/android/dialer/calllog/ui/menu/Modules.java @@ -126,6 +126,7 @@ final class Modules { .setCanSupportAssistedDialing(canSupportAssistedDialing(row)) .setCanSupportCarrierVideoCall(row.getNumberAttributes().getCanSupportCarrierVideoCall()) .setIsBlocked(row.getNumberAttributes().getIsBlocked()) + .setIsEmergencyNumber(row.getNumberAttributes().getIsEmergencyNumber()) .setIsSpam(row.getNumberAttributes().getIsSpam()) .setIsVoicemailCall(row.getIsVoicemailCall()) .setContactSource(row.getNumberAttributes().getContactSource()) diff --git a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml index e3052c097..2fc8e7bb8 100644 --- a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml +++ b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml @@ -17,6 +17,7 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp"> @@ -29,22 +30,34 @@ android:layout_marginEnd="10dp" android:layout_centerVertical="true"/> - <!-- The frame layout is necessary to avoid clipping the icons and ellipsize the text when the - content is too wide to fit. - --> - <FrameLayout - android:id="@+id/primary_row" + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="14dp" android:layout_toEndOf="@+id/contact_photo_view" - android:layout_toStartOf="@+id/menu_button"> + android:layout_toStartOf="@+id/menu_button" + android:orientation="vertical"> + <!-- 1st row: primary info --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="14dp" android:orientation="horizontal"> + <!-- + Important note: + + The following TextView is the only widget that defines a weight in the containing + LinearLayout, of which the purpose is to avoid pushing the widgets after it out of the + boundary when the text is too long. + + Generally it is more efficient to assign a width/height of 0dp so that the TextView does + not have to measure its own size since it will absorb all the remaining space anyway. + + However, as the TextView is part of an entry in the call log's RecyclerView, we must set + layout_width to "wrap_content" so that the TextView can adjust its size when recycled for + text of different lengths. + --> <TextView android:id="@+id/primary_text" style="@style/PrimaryText" @@ -54,32 +67,29 @@ android:layout_marginEnd="6dp" android:ellipsize="end" android:lineSpacingMultiplier="1.5" - android:singleLine="true"/> - + android:singleLine="true" + tools:ignore="InefficientWeight"/> <ImageView android:id="@+id/hd_icon" android:layout_width="wrap_content" android:layout_height="18dp" android:layout_gravity="center_vertical" - android:src="@drawable/quantum_ic_hd_vd_theme_24" - /> + android:src="@drawable/quantum_ic_hd_vd_theme_24"/> <ImageView android:id="@+id/wifi_icon" android:layout_width="wrap_content" android:layout_height="18dp" android:layout_gravity="center_vertical" - android:src="@drawable/quantum_ic_signal_wifi_4_bar_vd_theme_24" - /> + android:src="@drawable/quantum_ic_signal_wifi_4_bar_vd_theme_24"/> <ImageView android:id="@+id/assisted_dial_icon" android:layout_width="wrap_content" android:layout_height="18dp" android:layout_gravity="center_vertical" - android:src="@drawable/quantum_ic_language_vd_theme_24" - /> + android:src="@drawable/quantum_ic_language_vd_theme_24"/> <TextView android:id="@+id/call_count" @@ -90,43 +100,40 @@ android:lineSpacingMultiplier="1.5"/> </LinearLayout> - </FrameLayout> - - <LinearLayout - android:id="@+id/secondary_row" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@+id/primary_row" - android:layout_toEndOf="@+id/contact_photo_view" - android:orientation="horizontal"> - <ImageView - android:id="@+id/call_type_icon" + <!-- 2nd row: secondary info --> + <LinearLayout android:layout_width="wrap_content" - android:layout_height="18dp" - android:layout_gravity="center_vertical" - /> + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:id="@+id/call_type_icon" + android:layout_width="wrap_content" + android:layout_height="18dp" + android:layout_gravity="center_vertical"/> + + <TextView + android:id="@+id/secondary_text" + style="@style/SecondaryText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lineSpacingMultiplier="1.4" + android:singleLine="true"/> + + </LinearLayout> + + <!-- 3rd row: phone account info --> <TextView - android:id="@+id/secondary_text" + android:id="@+id/phone_account" style="@style/SecondaryText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" - android:lineSpacingMultiplier="1.4" android:singleLine="true"/> - </LinearLayout> - <TextView - android:id="@+id/phone_account" - style="@style/SecondaryText" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@+id/secondary_row" - android:layout_toEndOf="@+id/contact_photo_view" - android:ellipsize="end" - android:singleLine="true" - android:visibility="visible"/> + </LinearLayout> <ImageView android:id="@+id/menu_button" diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java index 1b7bb06fa..54b1e195a 100644 --- a/java/com/android/dialer/calllogutils/CallLogEntryText.java +++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java @@ -42,28 +42,35 @@ public final class CallLogEntryText { * following the primary text.) */ public static CharSequence buildPrimaryText(Context context, CoalescedRow row) { - // Always prefer the presentation name, like "Restricted". + // Calls to emergency services should be shown as "Emergency number". + if (row.getNumberAttributes().getIsEmergencyNumber()) { + return context.getText(R.string.emergency_number); + } + + // Otherwise, follow the following order of preferences. + // 1st preference: the presentation name, like "Restricted". Optional<String> presentationName = PhoneNumberDisplayUtil.getNameForPresentation(context, row.getNumberPresentation()); if (presentationName.isPresent()) { return presentationName.get(); } + // 2nd preference: the voicemail tag if the call is one made to a voicemail box. if (row.getIsVoicemailCall() && !TextUtils.isEmpty(row.getVoicemailCallTag())) { return row.getVoicemailCallTag(); } - // Otherwise prefer the name. + // 3rd preference: the name associated with the number. if (!TextUtils.isEmpty(row.getNumberAttributes().getName())) { return row.getNumberAttributes().getName(); } - // Otherwise prefer the formatted number. + // 4th preference: the formatted number. if (!TextUtils.isEmpty(row.getFormattedNumber())) { return row.getFormattedNumber(); } - // If there's no formatted number, just return "Unknown". + // Last resort: show "Unknown". return context.getText(R.string.new_call_log_unknown); } @@ -73,6 +80,7 @@ public final class CallLogEntryText { * <p>Rules: * * <ul> + * <li>For emergency numbers: Date * <li>For numbers that are not spam or blocked: $Label(, Duo video|Carrier video)?|$Location • * Date * <li>For blocked non-spam numbers: Blocked • $Label(, Duo video|Carrier video)?|$Location • @@ -100,6 +108,12 @@ public final class CallLogEntryText { */ public static CharSequence buildSecondaryTextForEntries( Context context, Clock clock, CoalescedRow row) { + // For emergency numbers, the secondary text should contain only the timestamp. + if (row.getNumberAttributes().getIsEmergencyNumber()) { + return CallLogDates.newCallLogTimestampLabel( + context, clock.currentTimeMillis(), row.getTimestamp()); + } + List<CharSequence> components = new ArrayList<>(); if (row.getNumberAttributes().getIsBlocked()) { @@ -127,6 +141,8 @@ public final class CallLogEntryText { public static CharSequence buildSecondaryTextForBottomSheet(Context context, CoalescedRow row) { /* * Rules: + * For emergency numbers: + * Number * For numbers that are not spam or blocked: * $Label(, Duo video|Carrier video)?|$Location [• NumberIfNoName]? * For blocked non-spam numbers: @@ -149,6 +165,14 @@ public final class CallLogEntryText { * Mobile • 555-1234 * Brooklyn, NJ */ + + // For emergency numbers, the secondary text should contain only the number. + if (row.getNumberAttributes().getIsEmergencyNumber()) { + return !row.getFormattedNumber().isEmpty() + ? row.getFormattedNumber() + : row.getNumber().getNormalizedNumber(); + } + List<CharSequence> components = new ArrayList<>(); if (row.getNumberAttributes().getIsBlocked()) { diff --git a/java/com/android/dialer/calllogutils/CallLogRowActions.java b/java/com/android/dialer/calllogutils/CallLogRowActions.java index d23a15f78..65f3c5fd6 100644 --- a/java/com/android/dialer/calllogutils/CallLogRowActions.java +++ b/java/com/android/dialer/calllogutils/CallLogRowActions.java @@ -20,6 +20,7 @@ import android.provider.CallLog.Calls; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.calllog.model.CoalescedRow; +import com.android.dialer.duo.DuoComponent; import com.android.dialer.precall.PreCall; /** Actions which can be performed on a call log row. */ @@ -37,6 +38,10 @@ public final class CallLogRowActions { activity, new CallIntentBuilder( row.getNumber().getNormalizedNumber(), CallInitiationType.Type.CALL_LOG) - .setIsVideoCall((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)); + .setIsVideoCall((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) + .setIsDuoCall( + DuoComponent.get(activity) + .getDuo() + .isDuoAccount(row.getPhoneAccountComponentName()))); } } diff --git a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java index 9f07fdac5..8081c4b3f 100644 --- a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java +++ b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java @@ -57,6 +57,7 @@ public final class NumberAttributesConverter { .setIsCp2InfoIncomplete(phoneLookupInfoConsolidator.isDefaultCp2InfoIncomplete()) .setContactSource(phoneLookupInfoConsolidator.getContactSource()) .setCanSupportCarrierVideoCall(phoneLookupInfoConsolidator.canSupportCarrierVideoCall()) - .setGeolocation(phoneLookupInfoConsolidator.getGeolocation()); + .setGeolocation(phoneLookupInfoConsolidator.getGeolocation()) + .setIsEmergencyNumber(phoneLookupInfoConsolidator.isEmergencyNumber()); } } diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java index f4552b203..6a2217416 100644 --- a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java +++ b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java @@ -23,6 +23,7 @@ import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; import com.android.dialer.common.concurrent.Annotations.NonUiParallel; import com.android.dialer.common.concurrent.Annotations.Ui; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import com.google.common.util.concurrent.ListeningExecutorService; import dagger.Subcomponent; import java.util.concurrent.ExecutorService; @@ -66,6 +67,7 @@ public abstract class DialerExecutorComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { DialerExecutorComponent dialerExecutorComponent(); } diff --git a/java/com/android/dialer/configprovider/ConfigProviderComponent.java b/java/com/android/dialer/configprovider/ConfigProviderComponent.java index 10d52e749..e974e30bf 100644 --- a/java/com/android/dialer/configprovider/ConfigProviderComponent.java +++ b/java/com/android/dialer/configprovider/ConfigProviderComponent.java @@ -19,6 +19,7 @@ package com.android.dialer.configprovider; import android.content.Context; import android.support.annotation.NonNull; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** Dagger component to provide a {@link ConfigProvider}. */ @@ -36,6 +37,7 @@ public abstract class ConfigProviderComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { ConfigProviderComponent configProviderComponent(); } diff --git a/java/com/android/dialer/configprovider/SharedPrefConfigProviderModule.java b/java/com/android/dialer/configprovider/SharedPrefConfigProviderModule.java index 4af8bfe17..81bed19bd 100644 --- a/java/com/android/dialer/configprovider/SharedPrefConfigProviderModule.java +++ b/java/com/android/dialer/configprovider/SharedPrefConfigProviderModule.java @@ -16,12 +16,15 @@ package com.android.dialer.configprovider; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; import com.android.dialer.storage.StorageModule; import dagger.Binds; import dagger.Module; import javax.inject.Singleton; /** Dagger module providing {@link ConfigProvider} based on shared preferences. */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) @Module(includes = StorageModule.class) public abstract class SharedPrefConfigProviderModule { diff --git a/java/com/android/dialer/database/CallLogQueryHandler.java b/java/com/android/dialer/database/CallLogQueryHandler.java index e974cc48f..a18023c4b 100644 --- a/java/com/android/dialer/database/CallLogQueryHandler.java +++ b/java/com/android/dialer/database/CallLogQueryHandler.java @@ -112,6 +112,7 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { .appendOmtpVoicemailStatusSelectionClause(context, where, selectionArgs); if (TelecomUtil.hasReadWriteVoicemailPermissions(context)) { + LogUtil.i("CallLogQueryHandler.fetchVoicemailStatus", "fetching voicemail status"); startQuery( QUERY_VOICEMAIL_STATUS_TOKEN, null, @@ -120,6 +121,10 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { where.toString(), selectionArgs.toArray(new String[selectionArgs.size()]), null); + } else { + LogUtil.i( + "CallLogQueryHandler.fetchVoicemailStatus", + "fetching voicemail status failed due to permissions"); } } diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index c45c77689..d48870dfc 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -477,7 +477,13 @@ public class DialpadFragment extends Fragment return; } digits.setContentDescription(null); - digitsHint.setVisibility(View.GONE); + + // TOOD(77908301): Investigate why this is the case + // It's not clear why digitsHint would be null when digits is initialized as the time, so adding + // a todo to investigate why. + if (digitsHint != null) { + digitsHint.setVisibility(View.GONE); + } } /** diff --git a/java/com/android/dialer/duo/Duo.java b/java/com/android/dialer/duo/Duo.java index 06a3db063..85fe9fbc1 100644 --- a/java/com/android/dialer/duo/Duo.java +++ b/java/com/android/dialer/duo/Duo.java @@ -27,6 +27,8 @@ import android.telecom.Call; import android.telecom.PhoneAccountHandle; import com.google.auto.value.AutoValue; import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ListenableFuture; import java.util.List; /** Interface for Duo video call integration. */ @@ -61,7 +63,8 @@ public interface Duo { /** Starts a task to update the reachability of the parameter numbers asynchronously. */ @MainThread - void updateReachability(@NonNull Context context, @NonNull List<String> numbers); + ListenableFuture<ImmutableMap<String, ReachabilityData>> updateReachability( + @NonNull Context context, @NonNull List<String> numbers); /** * Clears the current reachability data and starts a task to load the latest reachability data @@ -95,19 +98,6 @@ public interface Duo { */ Optional<Intent> getInviteIntent(String number); - /** Return value of {@link #getIntentType(Intent)} */ - enum IntentType { - /** The intent is returned by {@link #getCallIntent(String)} */ - CALL, - /** The intent is returned by {@link #getActivateIntent()} */ - ACTIVATE, - /** The intent is returned by {@link #getInviteIntent(String)} */ - INVITE - } - - /** Classifies a Duo intent. Absent if the intent is not a Duo intent. */ - Optional<IntentType> getIntentType(Intent intent); - Optional<Intent> getInstallDuoIntent(); /** Requests upgrading the parameter ongoing call to a Duo video call. */ diff --git a/java/com/android/dialer/duo/DuoComponent.java b/java/com/android/dialer/duo/DuoComponent.java index 031ee9e53..307832aaf 100644 --- a/java/com/android/dialer/duo/DuoComponent.java +++ b/java/com/android/dialer/duo/DuoComponent.java @@ -19,6 +19,7 @@ package com.android.dialer.duo; import android.content.Context; import android.support.annotation.NonNull; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** @@ -35,6 +36,7 @@ public abstract class DuoComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { DuoComponent duoComponent(); } diff --git a/java/com/android/dialer/duo/PlaceDuoCallNotifier.java b/java/com/android/dialer/duo/PlaceDuoCallNotifier.java deleted file mode 100644 index 8fde981a0..000000000 --- a/java/com/android/dialer/duo/PlaceDuoCallNotifier.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.duo; - -import android.content.Context; -import android.content.Intent; -import android.support.v4.content.LocalBroadcastManager; -import com.android.dialer.common.LogUtil; - -/** Notifies that a Duo video call should be started. */ -public final class PlaceDuoCallNotifier { - - private PlaceDuoCallNotifier() {} - - /** - * Broadcasts an intent notifying that a Duo call should be started. - * - * <p>See {@link PlaceDuoCallReceiver} for how the intent is handled. - * - * @param phoneNumber The number to start a Duo call. It can be of any format. - */ - public static void notify(Context context, String phoneNumber) { - LogUtil.enterBlock("PlaceDuoCallNotifier.notify"); - - Intent intent = new Intent(); - intent.setAction(PlaceDuoCallReceiver.ACTION_START_DUO_CALL); - intent.putExtra(PlaceDuoCallReceiver.EXTRA_PHONE_NUMBER, phoneNumber); - - LocalBroadcastManager.getInstance(context).sendBroadcast(intent); - } -} diff --git a/java/com/android/dialer/duo/PlaceDuoCallReceiver.java b/java/com/android/dialer/duo/PlaceDuoCallReceiver.java deleted file mode 100644 index f504aef57..000000000 --- a/java/com/android/dialer/duo/PlaceDuoCallReceiver.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.duo; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import com.android.dialer.constants.ActivityRequestCodes; - -/** A {@link BroadcastReceiver} that starts a Duo video call. */ -public final class PlaceDuoCallReceiver extends BroadcastReceiver { - - static final String ACTION_START_DUO_CALL = "start_duo_call"; - static final String EXTRA_PHONE_NUMBER = "phone_number"; - - /** - * {@link Activity} needed to launch Duo. - * - * <p>A Duo call can only be placed via {@link Activity#startActivityForResult(Intent, int)}. - */ - private final Activity activity; - - /** Returns an {@link IntentFilter} containing all actions accepted by this broadcast receiver. */ - public static IntentFilter getIntentFilter() { - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_START_DUO_CALL); - return intentFilter; - } - - public PlaceDuoCallReceiver(Activity activity) { - this.activity = activity; - } - - @Override - public void onReceive(Context context, Intent intent) { - LogUtil.enterBlock("PlaceDuoCallReceiver.onReceive"); - - String action = intent.getAction(); - - switch (Assert.isNotNull(action)) { - case ACTION_START_DUO_CALL: - startDuoCall(context, intent); - break; - default: - throw new IllegalStateException("Unsupported action: " + action); - } - } - - private void startDuoCall(Context context, Intent intent) { - LogUtil.enterBlock("PlaceDuoCallReceiver.startDuoCall"); - - Assert.checkArgument(intent.hasExtra(EXTRA_PHONE_NUMBER)); - String phoneNumber = intent.getStringExtra(EXTRA_PHONE_NUMBER); - - Duo duo = DuoComponent.get(context).getDuo(); - activity.startActivityForResult( - duo.getCallIntent(phoneNumber).orNull(), ActivityRequestCodes.DIALTACTS_DUO); - } -} diff --git a/java/com/android/dialer/duo/stub/DuoStub.java b/java/com/android/dialer/duo/stub/DuoStub.java index b0867148f..2131d16f7 100644 --- a/java/com/android/dialer/duo/stub/DuoStub.java +++ b/java/com/android/dialer/duo/stub/DuoStub.java @@ -29,6 +29,9 @@ import com.android.dialer.common.Assert; import com.android.dialer.duo.Duo; import com.android.dialer.duo.DuoListener; import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import java.util.List; import javax.inject.Inject; @@ -72,10 +75,12 @@ public class DuoStub implements Duo { } @Override - public void updateReachability(@NonNull Context context, @NonNull List<String> numbers) { + public ListenableFuture<ImmutableMap<String, ReachabilityData>> updateReachability( + @NonNull Context context, @NonNull List<String> numbers) { Assert.isMainThread(); Assert.isNotNull(context); Assert.isNotNull(numbers); + return Futures.immediateFuture(ImmutableMap.of()); } @Override @@ -115,11 +120,6 @@ public class DuoStub implements Duo { } @Override - public Optional<IntentType> getIntentType(Intent intent) { - return Optional.absent(); - } - - @Override public Optional<Intent> getInstallDuoIntent() { return null; } diff --git a/java/com/android/dialer/duo/stub/StubDuoModule.java b/java/com/android/dialer/duo/stub/StubDuoModule.java index 604406a94..8966602bb 100644 --- a/java/com/android/dialer/duo/stub/StubDuoModule.java +++ b/java/com/android/dialer/duo/stub/StubDuoModule.java @@ -17,10 +17,13 @@ package com.android.dialer.duo.stub; import com.android.dialer.duo.Duo; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; import dagger.Binds; import dagger.Module; import javax.inject.Singleton; +@InstallIn(variants = {DialerVariant.DIALER_TEST}) @Module public abstract class StubDuoModule { diff --git a/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java b/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java index 2ed2e94dc..46afd848d 100644 --- a/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java +++ b/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java @@ -19,6 +19,7 @@ package com.android.dialer.enrichedcall; import android.content.Context; import android.support.annotation.NonNull; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** Subcomponent that can be used to access the enriched call implementation. */ @@ -37,6 +38,7 @@ public abstract class EnrichedCallComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { EnrichedCallComponent enrichedCallComponent(); } diff --git a/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java b/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java index 93e15790a..5a0618d3c 100644 --- a/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java +++ b/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java @@ -18,12 +18,15 @@ package com.android.dialer.enrichedcall.stub; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.enrichedcall.RcsVideoShareFactory; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; import com.android.incallui.videotech.empty.EmptyVideoTech; import dagger.Module; import dagger.Provides; import javax.inject.Singleton; /** Module which binds {@link EnrichedCallManagerStub}. */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) @Module public class StubEnrichedCallModule { diff --git a/java/com/android/dialer/glidephotomanager/impl/DefaultLookupUriGenerator.java b/java/com/android/dialer/glidephotomanager/impl/DefaultLookupUriGenerator.java index 6b90e803c..2c4acd4df 100644 --- a/java/com/android/dialer/glidephotomanager/impl/DefaultLookupUriGenerator.java +++ b/java/com/android/dialer/glidephotomanager/impl/DefaultLookupUriGenerator.java @@ -48,7 +48,7 @@ final class DefaultLookupUriGenerator { try { lookupJson.put(Contacts.DISPLAY_NAME, photoInfo.getFormattedNumber()); // DISPLAY_NAME_SOURCE required by contacts, otherwise the URI will not be recognized. - lookupJson.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); + lookupJson.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.PHONE); JSONObject contactRows = new JSONObject(); JSONObject phone = new JSONObject(); phone.put(CommonDataKinds.Phone.NUMBER, photoInfo.getFormattedNumber()); diff --git a/java/com/android/dialer/historyitemactions/DuoCallModule.java b/java/com/android/dialer/historyitemactions/DuoCallModule.java deleted file mode 100644 index e6f31e293..000000000 --- a/java/com/android/dialer/historyitemactions/DuoCallModule.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.historyitemactions; - -import android.Manifest.permission; -import android.content.Context; -import android.support.annotation.RequiresPermission; -import com.android.dialer.duo.PlaceDuoCallNotifier; - -/** {@link HistoryItemActionModule} for making a Duo call. */ -public class DuoCallModule implements HistoryItemActionModule { - - private final Context context; - private final String phoneNumber; - - /** - * Creates a module for making a Duo call. - * - * @param phoneNumber The number to start a Duo call. It can be of any format. - */ - public DuoCallModule(Context context, String phoneNumber) { - this.context = context; - this.phoneNumber = phoneNumber; - } - - @Override - public int getStringId() { - return R.string.video_call; - } - - @Override - public int getDrawableId() { - return R.drawable.quantum_ic_videocam_vd_white_24; - } - - @Override - @RequiresPermission(permission.READ_PHONE_STATE) - public boolean onClick() { - PlaceDuoCallNotifier.notify(context, phoneNumber); - return true; // Close the bottom sheet. - } -} diff --git a/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java b/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java index 9af08be50..e1c6c9666 100644 --- a/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java +++ b/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java @@ -118,6 +118,7 @@ public final class HistoryItemActionModulesBuilder { * <p>This method is a no-op if * * <ul> + * <li>the call is one made to/received from an emergency number, * <li>the call is one made to a voicemail box, * <li>the number is blocked, or * <li>the number is marked as spam. @@ -138,25 +139,24 @@ public final class HistoryItemActionModulesBuilder { * capability, and Duo is available, add a Duo video call module. */ public HistoryItemActionModulesBuilder addModuleForVideoCall() { - if (moduleInfo.getIsVoicemailCall() || moduleInfo.getIsBlocked() || moduleInfo.getIsSpam()) { + if (moduleInfo.getIsEmergencyNumber() + || moduleInfo.getIsVoicemailCall() + || moduleInfo.getIsBlocked() + || moduleInfo.getIsSpam()) { return this; } // Do not set PhoneAccountHandle so that regular PreCall logic will be used. The account used to // place or receive the call should be ignored for carrier video calls. // TODO(a bug): figure out the correct video call behavior - HistoryItemActionModule carrierVideoCallModule = - IntentModule.newCallModule( - context, - new CallIntentBuilder(moduleInfo.getNormalizedNumber(), getCallInitiationType()) - .setAllowAssistedDial(moduleInfo.getCanSupportAssistedDialing()) - .setIsVideoCall(true)); - HistoryItemActionModule duoVideoCallModule = - new DuoCallModule(context, moduleInfo.getNormalizedNumber()); + CallIntentBuilder callIntentBuilder = + new CallIntentBuilder(moduleInfo.getNormalizedNumber(), getCallInitiationType()) + .setAllowAssistedDial(moduleInfo.getCanSupportAssistedDialing()) + .setIsVideoCall(true); // If the module info is for a video call, add an appropriate video call module. if ((moduleInfo.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) { - modules.add(isDuoCall() && canPlaceDuoCall() ? duoVideoCallModule : carrierVideoCallModule); + modules.add(IntentModule.newCallModule(context, callIntentBuilder.setIsDuoCall(isDuoCall()))); return this; } @@ -165,9 +165,9 @@ public final class HistoryItemActionModulesBuilder { // // The carrier video call module takes precedence over the Duo module. if (canPlaceCarrierVideoCall()) { - modules.add(carrierVideoCallModule); + modules.add(IntentModule.newCallModule(context, callIntentBuilder)); } else if (canPlaceDuoCall()) { - modules.add(duoVideoCallModule); + modules.add(IntentModule.newCallModule(context, callIntentBuilder.setIsDuoCall(true))); } return this; } @@ -178,6 +178,7 @@ public final class HistoryItemActionModulesBuilder { * <p>The method is a no-op if * * <ul> + * <li>the call is one made to/received from an emergency number, * <li>the call is one made to a voicemail box, * <li>the number is blocked, or * <li>the number is empty. @@ -186,7 +187,8 @@ public final class HistoryItemActionModulesBuilder { public HistoryItemActionModulesBuilder addModuleForSendingTextMessage() { // TODO(zachh): There are other conditions where this module should not be shown // (e.g., business numbers). - if (moduleInfo.getIsVoicemailCall() + if (moduleInfo.getIsEmergencyNumber() + || moduleInfo.getIsVoicemailCall() || moduleInfo.getIsBlocked() || TextUtils.isEmpty(moduleInfo.getNormalizedNumber())) { return this; @@ -217,6 +219,7 @@ public final class HistoryItemActionModulesBuilder { * <p>The method is a no-op if * * <ul> + * <li>the call is one made to/received from an emergency number, * <li>the call is one made to a voicemail box, * <li>the number is blocked, * <li>the number is marked as spam, @@ -225,7 +228,8 @@ public final class HistoryItemActionModulesBuilder { * </ul> */ public HistoryItemActionModulesBuilder addModuleForAddingToContacts() { - if (moduleInfo.getIsVoicemailCall() + if (moduleInfo.getIsEmergencyNumber() + || moduleInfo.getIsVoicemailCall() || moduleInfo.getIsBlocked() || moduleInfo.getIsSpam() || isExistingContact() @@ -253,7 +257,12 @@ public final class HistoryItemActionModulesBuilder { /** * Add modules for blocking/unblocking a number and/or marking it as spam/not spam. * - * <p>The method is a no-op if the call is one made to a voicemail box. + * <p>The method is a no-op if + * + * <ul> + * <li>the call is one made to/received from an emergency number, or + * <li>the call is one made to a voicemail box. + * </ul> * * <p>If a number is marked as spam, add two modules: * @@ -267,7 +276,7 @@ public final class HistoryItemActionModulesBuilder { * <p>If a number is not blocked or marked as spam, add the "Block/Report spam" module. */ public HistoryItemActionModulesBuilder addModuleForBlockedOrSpamNumber() { - if (moduleInfo.getIsVoicemailCall()) { + if (moduleInfo.getIsEmergencyNumber() || moduleInfo.getIsVoicemailCall()) { return this; } diff --git a/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto b/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto index 99071a7cd..f7022c28c 100644 --- a/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto +++ b/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto @@ -10,7 +10,7 @@ package com.android.dialer.historyitemactions; import "java/com/android/dialer/logging/contact_source.proto"; // Contains information needed to construct items (modules) in a bottom sheet. -// Next ID: 16 +// Next ID: 17 message HistoryItemActionModuleInfo { // The dialer-normalized version of a phone number. // See DialerPhoneNumber.normalized_number. @@ -66,4 +66,7 @@ message HistoryItemActionModuleInfo { VOICEMAIL = 2; } optional Host host = 15; + + // Whether the number is an emergency number. + optional bool is_emergency_number = 16; } diff --git a/java/com/android/dialer/historyitemactions/res/layout/contact_layout.xml b/java/com/android/dialer/historyitemactions/res/layout/contact_layout.xml index 721740f97..0790cf470 100644 --- a/java/com/android/dialer/historyitemactions/res/layout/contact_layout.xml +++ b/java/com/android/dialer/historyitemactions/res/layout/contact_layout.xml @@ -20,7 +20,8 @@ android:layout_height="match_parent" android:paddingTop="12dp" android:paddingBottom="12dp" - android:paddingEnd="8dp" + android:paddingStart="10dp" + android:paddingEnd="10dp" android:gravity="center_vertical" android:orientation="horizontal" android:background="#FFFFFF"> @@ -29,7 +30,6 @@ android:id="@+id/contact_photo_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:minHeight="@dimen/contact_actions_image_size"/> diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index 3f660f56c..2046b048f 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -24,7 +24,6 @@ import com.android.dialer.blockreportspam.ShowBlockReportSpamDialogReceiver; import com.android.dialer.calllog.config.CallLogConfigComponent; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; -import com.android.dialer.duo.PlaceDuoCallReceiver; import com.android.dialer.interactions.PhoneNumberInteraction.DisambigDialogDismissedListener; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorListener; @@ -48,9 +47,6 @@ public class MainActivity extends TransactionSafeActivity */ private ShowBlockReportSpamDialogReceiver showBlockReportSpamDialogReceiver; - /** {@link android.content.BroadcastReceiver} that starts a Duo call. */ - private PlaceDuoCallReceiver placeDuoCallReceiver; - public static Intent getShowCallLogIntent(Context context) { return getShowTabIntent(context, TabIndex.CALL_LOG); } @@ -83,7 +79,6 @@ public class MainActivity extends TransactionSafeActivity activePeer.onActivityCreate(savedInstanceState); showBlockReportSpamDialogReceiver = new ShowBlockReportSpamDialogReceiver(getFragmentManager()); - placeDuoCallReceiver = new PlaceDuoCallReceiver(/* activity = */ this); } protected MainActivityPeer getNewPeer() { @@ -109,8 +104,6 @@ public class MainActivity extends TransactionSafeActivity LocalBroadcastManager.getInstance(this) .registerReceiver( showBlockReportSpamDialogReceiver, ShowBlockReportSpamDialogReceiver.getIntentFilter()); - LocalBroadcastManager.getInstance(this) - .registerReceiver(placeDuoCallReceiver, PlaceDuoCallReceiver.getIntentFilter()); } @Override @@ -125,7 +118,6 @@ public class MainActivity extends TransactionSafeActivity activePeer.onActivityPause(); LocalBroadcastManager.getInstance(this).unregisterReceiver(showBlockReportSpamDialogReceiver); - LocalBroadcastManager.getInstance(this).unregisterReceiver(placeDuoCallReceiver); } @Override diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java index b9a6654b6..72c46cc7a 100644 --- a/java/com/android/dialer/main/impl/MainSearchController.java +++ b/java/com/android/dialer/main/impl/MainSearchController.java @@ -35,7 +35,6 @@ import com.android.contacts.common.dialog.ClearFrequentsDialog; import com.android.dialer.app.calllog.CallLogActivity; import com.android.dialer.app.settings.DialerSettingsActivity; import com.android.dialer.callintent.CallInitiationType; -import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.dialpadview.DialpadFragment; @@ -136,7 +135,10 @@ public class MainSearchController implements SearchBarListener { } private void showDialpad(boolean animate, boolean fromNewIntent) { - Assert.checkArgument(!isDialpadVisible()); + if (isDialpadVisible()) { + LogUtil.e("MainSearchController.showDialpad", "Dialpad is already visible."); + return; + } Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_DIALPAD, activity); @@ -187,14 +189,32 @@ public class MainSearchController implements SearchBarListener { */ private void hideDialpad(boolean animate) { LogUtil.enterBlock("MainSearchController.hideDialpad"); - assertDialpadVisible(); + DialpadFragment dialpadFragment = getDialpadFragment(); + if (dialpadFragment == null) { + LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is null."); + return; + } + + if (!dialpadFragment.isAdded()) { + LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is not added."); + return; + } + + if (dialpadFragment.isHidden()) { + LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is already hidden."); + return; + } + + if (!dialpadFragment.isDialpadSlideUp()) { + LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is already slide down."); + return; + } fab.show(); toolbar.slideDown(animate, fragmentContainer); toolbar.transferQueryFromDialpad(getDialpadFragment().getQuery()); activity.setTitle(R.string.main_activity_label); - DialpadFragment dialpadFragment = getDialpadFragment(); dialpadFragment.setAnimate(animate); dialpadFragment.slideDown( animate, @@ -295,7 +315,22 @@ public class MainSearchController implements SearchBarListener { /** Calls {@link #hideDialpad(boolean)}, removes the search fragment and clears the dialpad. */ private void closeSearch(boolean animate) { LogUtil.enterBlock("MainSearchController.closeSearch"); - assertSearchIsVisible(); + NewSearchFragment searchFragment = getSearchFragment(); + if (searchFragment == null) { + LogUtil.e("MainSearchController.closeSearch", "Search fragment is null."); + return; + } + + if (!searchFragment.isAdded()) { + LogUtil.e("MainSearchController.closeSearch", "Search fragment isn't added."); + return; + } + + if (searchFragment.isHidden()) { + LogUtil.e("MainSearchController.closeSearch", "Search fragment is already hidden."); + return; + } + if (isDialpadVisible()) { hideDialpad(animate); } else if (!fab.isShown()) { @@ -304,7 +339,7 @@ public class MainSearchController implements SearchBarListener { showBottomNav(); toolbar.collapse(animate); toolbarShadow.setVisibility(View.GONE); - activity.getFragmentManager().beginTransaction().hide(getSearchFragment()).commit(); + activity.getFragmentManager().beginTransaction().hide(searchFragment).commit(); // Clear the dialpad so the phone number isn't persisted between search sessions. DialpadFragment dialpadFragment = getDialpadFragment(); @@ -341,26 +376,11 @@ public class MainSearchController implements SearchBarListener { && fragment.isDialpadSlideUp(); } - private void assertDialpadVisible() { - DialpadFragment fragment = getDialpadFragment(); - Assert.checkArgument(fragment != null, "Dialpad Fragment is null"); - Assert.checkArgument(fragment.isAdded(), "Dialpad Fragment is no added"); - Assert.checkArgument(!fragment.isHidden(), "Dialpad Fragment is hidden"); - Assert.checkArgument(fragment.isDialpadSlideUp(), "Dialpad Fragment is slide down"); - } - private boolean isSearchVisible() { NewSearchFragment fragment = getSearchFragment(); return fragment != null && fragment.isAdded() && !fragment.isHidden(); } - private void assertSearchIsVisible() { - NewSearchFragment fragment = getSearchFragment(); - Assert.checkArgument(fragment != null, "Search Fragment is null"); - Assert.checkArgument(fragment.isAdded(), "Search Fragment is not added"); - Assert.checkArgument(!fragment.isHidden(), "Search Fragment is hidden."); - } - /** Returns true if the search UI is visible. */ public boolean isInSearch() { return isSearchVisible(); diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index e3d42fa9e..e426ed215 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -460,6 +460,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @SuppressLint("MissingPermission") @Override public void onActivityResume() { + LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onActivityResume"); callLogFragmentListener.onActivityResume(); // Start the thread that updates the smart dial database if the activity is recreated with a // language change. @@ -866,6 +867,10 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { + LogUtil.i( + "MainCallLogFragmentListener", + "voicemailStatusObserver.onChange selfChange:%b", + selfChange); super.onChange(selfChange); callLogQueryHandler.fetchVoicemailStatus(); } @@ -885,9 +890,10 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } private void registerVoicemailStatusContentObserver(Context context) { - + LogUtil.enterBlock("MainCallLogFragmentListener.registerVoicemailStatusContentObserver"); if (PermissionsUtil.hasReadVoicemailPermissions(context) && PermissionsUtil.hasAddVoicemailPermissions(context)) { + LogUtil.i("MainCallLogFragmentListener.registerVoicemailStatusContentObserver", "register"); context .getContentResolver() .registerContentObserver( @@ -1013,11 +1019,12 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } public void onActivityResume() { + LogUtil.enterBlock("MainCallLogFragmentListener.onActivityResume"); activityIsAlive = true; registerVoicemailStatusContentObserver(context); - if (!bottomNavTabListener.newVoicemailFragmentActive()) { - callLogQueryHandler.fetchVoicemailStatus(); - } + // TODO(a bug): Don't use callLogQueryHandler + callLogQueryHandler.fetchVoicemailStatus(); + if (!bottomNavTabListener.newCallLogFragmentActive()) { callLogQueryHandler.fetchMissedCallsUnreadCount(); } @@ -1234,7 +1241,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private static final String CONTACTS_TAG = "contacts"; private static final String VOICEMAIL_TAG = "voicemail"; - private final AppCompatActivity activity; + private final TransactionSafeActivity activity; private final FragmentManager fragmentManager; private final android.support.v4.app.FragmentManager supportFragmentManager; private final FloatingActionButton fab; @@ -1242,7 +1249,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @TabIndex private int selectedTab = -1; private MainBottomNavBarBottomNavTabListener( - AppCompatActivity activity, + TransactionSafeActivity activity, FragmentManager fragmentManager, android.support.v4.app.FragmentManager supportFragmentManager, FloatingActionButton fab) { @@ -1300,7 +1307,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen android.support.v4.app.Fragment supportFragment = supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); if (supportFragment != null) { - supportFragmentManager.beginTransaction().remove(supportFragment).commit(); + supportFragmentManager.beginTransaction().remove(supportFragment).commitAllowingStateLoss(); // If the NewCallLogFragment was showing, immediately show the old call log fragment // instead. if (selectedTab == TabIndex.CALL_LOG) { @@ -1317,7 +1324,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen android.support.v4.app.Fragment supportFragment = supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); if (supportFragment != null) { - supportFragmentManager.beginTransaction().remove(supportFragment).commit(); + supportFragmentManager.beginTransaction().remove(supportFragment).commitAllowingStateLoss(); // If the NewVoicemailFragment was showing, immediately show the old voicemail fragment // instead. if (selectedTab == TabIndex.VOICEMAIL) { @@ -1445,7 +1452,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen "MainBottomNavBarBottomNavTabListener.showFragment", "Not added yet: " + fragment); transaction.add(R.id.fragment_container, fragment, tag); } - transaction.commit(); + if (activity.isSafeToCommitTransactions()) { + transaction.commit(); + } // Handle support fragments. // TODO(calderwoodra): Handle other new fragments. @@ -1471,7 +1480,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen "Not added yet: " + supportFragment); supportTransaction.add(R.id.fragment_container, supportFragment, tag); } - supportTransaction.commit(); + if (activity.isSafeToCommitTransactions()) { + supportTransaction.commit(); + } } private void showSupportFragment( diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java index 851d7a0b4..475383bd3 100644 --- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java +++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java @@ -28,6 +28,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageButton; import android.widget.PopupMenu; import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; import com.android.dialer.util.ViewUtil; import com.google.common.base.Optional; @@ -41,7 +42,6 @@ public final class MainToolbar extends Toolbar implements PopupMenu.OnMenuItemCl private SearchBarView searchBar; private SearchBarListener listener; private MainToolbarMenu overflowMenu; - private boolean isSlideUp; private boolean hasGlobalLayoutListener; public MainToolbar(Context context, AttributeSet attrs) { @@ -78,7 +78,6 @@ public final class MainToolbar extends Toolbar implements PopupMenu.OnMenuItemCl return; } - Assert.checkArgument(!isSlideUp); if (getHeight() == 0) { hasGlobalLayoutListener = true; ViewUtil.doOnGlobalLayout( @@ -89,7 +88,12 @@ public final class MainToolbar extends Toolbar implements PopupMenu.OnMenuItemCl }); return; } - isSlideUp = true; + + if (isSlideUp()) { + LogUtil.e("MainToolbar.slideDown", "Already slide up."); + return; + } + animate() .translationY(-getHeight()) .setDuration(animate ? SLIDE_DURATION : 0) @@ -105,8 +109,10 @@ public final class MainToolbar extends Toolbar implements PopupMenu.OnMenuItemCl /** Slides the toolbar down and back onto the screen. */ public void slideDown(boolean animate, View container) { - Assert.checkArgument(isSlideUp); - isSlideUp = false; + if (getTranslationY() == 0) { + LogUtil.e("MainToolbar.slideDown", "Already slide down."); + return; + } animate() .translationY(0) .setDuration(animate ? SLIDE_DURATION : 0) @@ -131,7 +137,7 @@ public final class MainToolbar extends Toolbar implements PopupMenu.OnMenuItemCl } public boolean isSlideUp() { - return isSlideUp; + return getHeight() != 0 && getTranslationY() == -getHeight(); } public boolean isExpanded() { diff --git a/java/com/android/dialer/metrics/MetricsComponent.java b/java/com/android/dialer/metrics/MetricsComponent.java index a3570db10..c0d957ba4 100644 --- a/java/com/android/dialer/metrics/MetricsComponent.java +++ b/java/com/android/dialer/metrics/MetricsComponent.java @@ -18,6 +18,7 @@ package com.android.dialer.metrics; import android.content.Context; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** Component for metrics. */ @@ -37,6 +38,7 @@ public abstract class MetricsComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { MetricsComponent metricsComponent(); } diff --git a/java/com/android/dialer/phonelookup/PhoneLookup.java b/java/com/android/dialer/phonelookup/PhoneLookup.java index 0b9cbf6eb..1043ee775 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/PhoneLookup.java @@ -135,4 +135,11 @@ public interface PhoneLookup<T> { * disabled (because for example there was a problem with it). */ ListenableFuture<Void> clearData(); + + /** + * The name of this lookup for logging purposes. This is generally the same as the class name (but + * should not use methods from {@link Class} because the class names are generally obfuscated by + * Proguard. + */ + String getLoggingName(); } diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java index c6e91c4a4..16aa8e516 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java +++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java @@ -21,6 +21,7 @@ import com.android.dialer.phonelookup.cequint.CequintPhoneLookup; import com.android.dialer.phonelookup.cnap.CnapPhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2DefaultDirectoryPhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2ExtendedDirectoryPhoneLookup; +import com.android.dialer.phonelookup.emergency.EmergencyPhoneLookup; import com.android.dialer.phonelookup.spam.SpamPhoneLookup; import com.google.common.collect.ImmutableList; import dagger.Module; @@ -37,6 +38,7 @@ public abstract class PhoneLookupModule { CnapPhoneLookup cnapPhoneLookup, Cp2DefaultDirectoryPhoneLookup cp2DefaultDirectoryPhoneLookup, Cp2ExtendedDirectoryPhoneLookup cp2ExtendedDirectoryPhoneLookup, + EmergencyPhoneLookup emergencyPhoneLookup, SystemBlockedNumberPhoneLookup systemBlockedNumberPhoneLookup, SpamPhoneLookup spamPhoneLookup) { return ImmutableList.of( @@ -44,6 +46,7 @@ public abstract class PhoneLookupModule { cnapPhoneLookup, cp2DefaultDirectoryPhoneLookup, cp2ExtendedDirectoryPhoneLookup, + emergencyPhoneLookup, systemBlockedNumberPhoneLookup, spamPhoneLookup); } diff --git a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java index fe6642eef..4192e7932 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java @@ -181,4 +181,9 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked public ListenableFuture<Void> clearData() { return Futures.immediateFuture(null); } + + @Override + public String getLoggingName() { + return "SystemBlockedNumberPhoneLookup"; + } } diff --git a/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java b/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java index 36d0be40f..b045d0396 100644 --- a/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java @@ -141,6 +141,11 @@ public class CequintPhoneLookup implements PhoneLookup<CequintInfo> { return Futures.immediateFuture(null); } + @Override + public String getLoggingName() { + return "CequintPhoneLookup"; + } + /** * Builds a {@link CequintInfo} proto based on the given {@link CequintCallerIdContact} returned * by {@link CequintCallerIdManager}. diff --git a/java/com/android/dialer/phonelookup/cnap/CnapPhoneLookup.java b/java/com/android/dialer/phonelookup/cnap/CnapPhoneLookup.java index db7c210fc..1b78a8e94 100644 --- a/java/com/android/dialer/phonelookup/cnap/CnapPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cnap/CnapPhoneLookup.java @@ -157,4 +157,9 @@ public final class CnapPhoneLookup implements PhoneLookup<CnapInfo> { public ListenableFuture<Void> clearData() { return Futures.immediateFuture(null); } + + @Override + public String getLoggingName() { + return "CnapPhoneLookup"; + } } diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index 1ac13df33..83223295b 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -89,13 +89,12 @@ public final class CompositePhoneLookup { for (PhoneLookup<?> phoneLookup : phoneLookups) { ListenableFuture<?> lookupFuture = phoneLookup.lookup(appContext, call); String eventName = - String.format(Metrics.LOOKUP_FOR_CALL_TEMPLATE, phoneLookup.getClass().getSimpleName()); + String.format(Metrics.LOOKUP_FOR_CALL_TEMPLATE, phoneLookup.getLoggingName()); futureTimer.applyTiming(lookupFuture, eventName); futures.add(lookupFuture); } ListenableFuture<PhoneLookupInfo> combinedFuture = combineSubMessageFutures(futures); - String eventName = - String.format(Metrics.LOOKUP_FOR_CALL_TEMPLATE, CompositePhoneLookup.class.getSimpleName()); + String eventName = String.format(Metrics.LOOKUP_FOR_CALL_TEMPLATE, getLoggingName()); futureTimer.applyTiming(combinedFuture, eventName); return combinedFuture; } @@ -114,14 +113,12 @@ public final class CompositePhoneLookup { for (PhoneLookup<?> phoneLookup : phoneLookups) { ListenableFuture<?> lookupFuture = phoneLookup.lookup(dialerPhoneNumber); String eventName = - String.format(Metrics.LOOKUP_FOR_NUMBER_TEMPLATE, phoneLookup.getClass().getSimpleName()); + String.format(Metrics.LOOKUP_FOR_NUMBER_TEMPLATE, phoneLookup.getLoggingName()); futureTimer.applyTiming(lookupFuture, eventName); futures.add(lookupFuture); } ListenableFuture<PhoneLookupInfo> combinedFuture = combineSubMessageFutures(futures); - String eventName = - String.format( - Metrics.LOOKUP_FOR_NUMBER_TEMPLATE, CompositePhoneLookup.class.getSimpleName()); + String eventName = String.format(Metrics.LOOKUP_FOR_NUMBER_TEMPLATE, getLoggingName()); futureTimer.applyTiming(combinedFuture, eventName); return combinedFuture; } @@ -133,6 +130,7 @@ public final class CompositePhoneLookup { return Futures.transform( Futures.allAsList(subMessageFutures), subMessages -> { + Preconditions.checkNotNull(subMessages); Builder mergedInfo = PhoneLookupInfo.newBuilder(); for (int i = 0; i < subMessages.size(); i++) { PhoneLookup phoneLookup = phoneLookups.get(i); @@ -152,16 +150,14 @@ public final class CompositePhoneLookup { for (PhoneLookup<?> phoneLookup : phoneLookups) { ListenableFuture<Boolean> isDirtyFuture = phoneLookup.isDirty(phoneNumbers); futures.add(isDirtyFuture); - String eventName = - String.format(Metrics.IS_DIRTY_TEMPLATE, phoneLookup.getClass().getSimpleName()); + String eventName = String.format(Metrics.IS_DIRTY_TEMPLATE, phoneLookup.getLoggingName()); futureTimer.applyTiming(isDirtyFuture, eventName, LogCatMode.LOG_VALUES); } // Executes all child lookups (possibly in parallel), completing when the first composite lookup // which returns "true" completes, and cancels the others. ListenableFuture<Boolean> firstMatching = DialerFutures.firstMatching(futures, Preconditions::checkNotNull, false /* defaultValue */); - String eventName = - String.format(Metrics.IS_DIRTY_TEMPLATE, CompositePhoneLookup.class.getSimpleName()); + String eventName = String.format(Metrics.IS_DIRTY_TEMPLATE, getLoggingName()); futureTimer.applyTiming(firstMatching, eventName, LogCatMode.LOG_VALUES); return firstMatching; } @@ -178,6 +174,7 @@ public final class CompositePhoneLookup { return Futures.transformAsync( callLogState.isBuilt(), isBuilt -> { + Preconditions.checkNotNull(isBuilt); List<ListenableFuture<ImmutableMap<DialerPhoneNumber, ?>>> futures = new ArrayList<>(); for (PhoneLookup phoneLookup : phoneLookups) { futures.add(buildSubmapAndGetMostRecentInfo(existingInfoMap, phoneLookup, isBuilt)); @@ -186,6 +183,7 @@ public final class CompositePhoneLookup { Futures.transform( Futures.allAsList(futures), (allMaps) -> { + Preconditions.checkNotNull(allMaps); ImmutableMap.Builder<DialerPhoneNumber, PhoneLookupInfo> combinedMap = ImmutableMap.builder(); for (DialerPhoneNumber dialerPhoneNumber : existingInfoMap.keySet()) { @@ -206,7 +204,7 @@ public final class CompositePhoneLookup { return combinedMap.build(); }, lightweightExecutorService); - String eventName = getMostRecentInfoEventName(this, isBuilt); + String eventName = getMostRecentInfoEventName(getLoggingName(), isBuilt); futureTimer.applyTiming(combinedFuture, eventName); return combinedFuture; }, @@ -224,7 +222,7 @@ public final class CompositePhoneLookup { phoneLookup.getSubMessage(existingInfoMap.get(dialerPhoneNumber))); ListenableFuture<ImmutableMap<DialerPhoneNumber, T>> mostRecentInfoFuture = phoneLookup.getMostRecentInfo(ImmutableMap.copyOf(submap)); - String eventName = getMostRecentInfoEventName(phoneLookup, isBuilt); + String eventName = getMostRecentInfoEventName(phoneLookup.getLoggingName(), isBuilt); futureTimer.applyTiming(mostRecentInfoFuture, eventName); return mostRecentInfoFuture; } @@ -234,17 +232,19 @@ public final class CompositePhoneLookup { return Futures.transformAsync( callLogState.isBuilt(), isBuilt -> { + Preconditions.checkNotNull(isBuilt); List<ListenableFuture<Void>> futures = new ArrayList<>(); for (PhoneLookup<?> phoneLookup : phoneLookups) { ListenableFuture<Void> phoneLookupFuture = phoneLookup.onSuccessfulBulkUpdate(); futures.add(phoneLookupFuture); - String eventName = onSuccessfulBulkUpdatedEventName(phoneLookup, isBuilt); + String eventName = + onSuccessfulBulkUpdatedEventName(phoneLookup.getLoggingName(), isBuilt); futureTimer.applyTiming(phoneLookupFuture, eventName); } ListenableFuture<Void> combinedFuture = Futures.transform( Futures.allAsList(futures), unused -> null, lightweightExecutorService); - String eventName = onSuccessfulBulkUpdatedEventName(this, isBuilt); + String eventName = onSuccessfulBulkUpdatedEventName(getLoggingName(), isBuilt); futureTimer.applyTiming(combinedFuture, eventName); return combinedFuture; }, @@ -278,19 +278,23 @@ public final class CompositePhoneLookup { Futures.allAsList(futures), unused -> null, lightweightExecutorService); } - private static String getMostRecentInfoEventName(Object classNameSource, boolean isBuilt) { + private static String getMostRecentInfoEventName(String loggingName, boolean isBuilt) { return String.format( !isBuilt ? Metrics.INITIAL_GET_MOST_RECENT_INFO_TEMPLATE : Metrics.GET_MOST_RECENT_INFO_TEMPLATE, - classNameSource.getClass().getSimpleName()); + loggingName); } - private static String onSuccessfulBulkUpdatedEventName(Object classNameSource, boolean isBuilt) { + private static String onSuccessfulBulkUpdatedEventName(String loggingName, boolean isBuilt) { return String.format( !isBuilt ? Metrics.INITIAL_ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE : Metrics.ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE, - classNameSource.getClass().getSimpleName()); + loggingName); + } + + private String getLoggingName() { + return "CompositePhoneLookup"; } } diff --git a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java index 23ecc8301..29a0de56b 100644 --- a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java +++ b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java @@ -347,6 +347,14 @@ public final class PhoneLookupInfoConsolidator { /** * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method + * returns whether the number is an emergency number (e.g., 911 in the U.S.). + */ + public boolean isEmergencyNumber() { + return phoneLookupInfo.getEmergencyInfo().getIsEmergencyNumber(); + } + + /** + * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method * returns whether the number can be reported as invalid. * * <p>As we currently report invalid numbers via the People API, only numbers from the People API diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java index c5d4e53f2..1642f9b23 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java @@ -644,6 +644,11 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info }); } + @Override + public String getLoggingName() { + return "Cp2DefaultDirectoryPhoneLookup"; + } + /** * 1. get all contact ids. if the id is unset, add the number to the list of contacts to look up. * 2. reduce our list of contact ids to those that were updated after lastModified. 3. Now we have diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java index 2b98f265c..77a95e79f 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java @@ -234,4 +234,9 @@ public final class Cp2ExtendedDirectoryPhoneLookup implements PhoneLookup<Cp2Inf public ListenableFuture<Void> clearData() { return Futures.immediateFuture(null); } + + @Override + public String getLoggingName() { + return "Cp2ExtendedDirectoryPhoneLookup"; + } } diff --git a/java/com/android/dialer/phonelookup/emergency/EmergencyPhoneLookup.java b/java/com/android/dialer/phonelookup/emergency/EmergencyPhoneLookup.java new file mode 100644 index 000000000..d31614a12 --- /dev/null +++ b/java/com/android/dialer/phonelookup/emergency/EmergencyPhoneLookup.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.phonelookup.emergency; + +import android.content.Context; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.phonelookup.PhoneLookup; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.EmergencyInfo; +import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import javax.inject.Inject; + +/** + * PhoneLookup implementation for checking if a number is an emergency number. + * + * <p>The check has to be done in a PhoneLookup as it involves detecting the user's location and + * obtaining SIM info, which are expensive operations. Doing it in the main thread will make the UI + * super janky. + */ +public class EmergencyPhoneLookup implements PhoneLookup<EmergencyInfo> { + + private final Context appContext; + private final ListeningExecutorService backgroundExecutorService; + + @Inject + EmergencyPhoneLookup( + @ApplicationContext Context appContext, + @BackgroundExecutor ListeningExecutorService backgroundExecutorService) { + this.appContext = appContext; + this.backgroundExecutorService = backgroundExecutorService; + } + + @Override + public ListenableFuture<EmergencyInfo> lookup(DialerPhoneNumber dialerPhoneNumber) { + return backgroundExecutorService.submit( + () -> + EmergencyInfo.newBuilder() + .setIsEmergencyNumber( + PhoneNumberHelper.isLocalEmergencyNumber( + appContext, dialerPhoneNumber.getNormalizedNumber())) + .build()); + } + + @Override + public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) { + return Futures.immediateFuture(false); + } + + @Override + public ListenableFuture<ImmutableMap<DialerPhoneNumber, EmergencyInfo>> getMostRecentInfo( + ImmutableMap<DialerPhoneNumber, EmergencyInfo> existingInfoMap) { + // We can update EmergencyInfo for all numbers in the provided map, but the negative impact on + // performance is intolerable as checking a single number involves detecting the user's location + // and obtaining SIM info, which will take more than 100ms (see + // android.telephony.PhoneNumberUtils#isLocalEmergencyNumber(Context, int, String) for details). + // + // As emergency numbers won't change in a country, the only case we will miss is that + // (1) a number is an emergency number in country A but not in country B, + // (2) a user has an emergency call entry when they are in country A, and + // (3) they travel from A to B, + // which is a rare event. + // + // We can update the implementation if telecom supports batch check in the future. + return Futures.immediateFuture(existingInfoMap); + } + + @Override + public void setSubMessage(PhoneLookupInfo.Builder destination, EmergencyInfo subMessage) { + destination.setEmergencyInfo(subMessage); + } + + @Override + public EmergencyInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) { + return phoneLookupInfo.getEmergencyInfo(); + } + + @Override + public ListenableFuture<Void> onSuccessfulBulkUpdate() { + return Futures.immediateFuture(null); + } + + @Override + public void registerContentObservers() { + // No content observer to register. + } + + @Override + public void unregisterContentObservers() { + // Nothing to be done as no content observer is registered. + } + + @Override + public ListenableFuture<Void> clearData() { + return Futures.immediateFuture(null); + } + + @Override + public String getLoggingName() { + return "EmergencyPhoneLookup"; + } +} diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto index 50817c4e3..9e9dfa918 100644 --- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto +++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto @@ -14,7 +14,7 @@ package com.android.dialer.phonelookup; // "cp2_info_in_default_directory" corresponds to class // Cp2DefaultDirectoryPhoneLookup, and class Cp2DefaultDirectoryPhoneLookup // alone is responsible for populating it. -// Next ID: 9 +// Next ID: 10 message PhoneLookupInfo { // Information about a PhoneNumber retrieved from CP2. message Cp2Info { @@ -175,4 +175,11 @@ message PhoneLookupInfo { optional string photo_uri = 3; } optional CequintInfo cequint_info = 8; + + // Message indicating whether a number is an emergency number. + // Next ID: 2 + message EmergencyInfo { + optional bool is_emergency_number = 1; + } + optional EmergencyInfo emergency_info = 9; }
\ No newline at end of file diff --git a/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java b/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java index 6b77036cf..7e5c9734d 100644 --- a/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java @@ -27,7 +27,7 @@ import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; import com.android.dialer.phonelookup.PhoneLookupInfo.SpamInfo; import com.android.dialer.spam.Spam; -import com.android.dialer.spam.SpamStatus; +import com.android.dialer.spam.status.SpamStatus; import com.android.dialer.storage.Unencrypted; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; @@ -167,4 +167,9 @@ public final class SpamPhoneLookup implements PhoneLookup<SpamInfo> { return null; }); } + + @Override + public String getLoggingName() { + return "SpamPhoneLookup"; + } } diff --git a/java/com/android/dialer/precall/PreCallCoordinator.java b/java/com/android/dialer/precall/PreCallCoordinator.java index 9c24e0d69..4d4859a80 100644 --- a/java/com/android/dialer/precall/PreCallCoordinator.java +++ b/java/com/android/dialer/precall/PreCallCoordinator.java @@ -68,8 +68,8 @@ public interface PreCallCoordinator { @NonNull PendingAction startPendingAction(); - <Output> void listen( - ListenableFuture<Output> future, - Consumer<Output> successListener, + <OutputT> void listen( + ListenableFuture<OutputT> future, + Consumer<OutputT> successListener, Consumer<Throwable> failureListener); } diff --git a/java/com/android/dialer/precall/impl/DuoAction.java b/java/com/android/dialer/precall/impl/DuoAction.java new file mode 100644 index 000000000..c05fbe12f --- /dev/null +++ b/java/com/android/dialer/precall/impl/DuoAction.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.precall.impl; + +import android.content.Context; +import android.content.Intent; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.Annotations.Ui; +import com.android.dialer.duo.Duo.ReachabilityData; +import com.android.dialer.duo.DuoComponent; +import com.android.dialer.precall.PreCallAction; +import com.android.dialer.precall.PreCallCoordinator; +import com.android.dialer.precall.PreCallCoordinator.PendingAction; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import javax.inject.Inject; + +/** + * Checks if a duo call is actually callable, and request an activity for {@link + * android.app.Activity#startActivityForResult(Intent, int)} + */ +public class DuoAction implements PreCallAction { + + private final ListeningExecutorService uiExecutor; + + @Inject + DuoAction(@Ui ListeningExecutorService uiExecutor) { + this.uiExecutor = uiExecutor; + } + + @Override + public boolean requiresUi(Context context, CallIntentBuilder builder) { + // Duo call must be started with startActivityForResult() which needs a activity. + return builder.isDuoCall(); + } + + @Override + public void runWithoutUi(Context context, CallIntentBuilder builder) {} + + @Override + public void runWithUi(PreCallCoordinator coordinator) { + if (!requiresUi(coordinator.getActivity(), coordinator.getBuilder())) { + return; + } + String number = coordinator.getBuilder().getUri().getSchemeSpecificPart(); + ListenableFuture<ImmutableMap<String, ReachabilityData>> reachabilities = + DuoComponent.get(coordinator.getActivity()) + .getDuo() + .updateReachability(coordinator.getActivity(), ImmutableList.of(number)); + PendingAction pendingAction = coordinator.startPendingAction(); + + Futures.addCallback( + reachabilities, + new FutureCallback<ImmutableMap<String, ReachabilityData>>() { + @Override + public void onSuccess(ImmutableMap<String, ReachabilityData> result) { + if (!result.containsKey(number) || !result.get(number).videoCallable()) { + LogUtil.w( + "DuoAction.runWithUi", + number + " number no longer duo reachable, falling back to carrier video call"); + coordinator.getBuilder().setIsDuoCall(false); + } + pendingAction.finish(); + } + + @Override + public void onFailure(Throwable throwable) { + LogUtil.e("DuoAction.runWithUi", "reachability query failed", throwable); + coordinator.getBuilder().setIsDuoCall(false); + pendingAction.finish(); + } + }, + uiExecutor); + } + + @Override + public void onDiscard() {} +} diff --git a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java index f2ff0e3e3..240549ca5 100644 --- a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java +++ b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java @@ -26,6 +26,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.UiListener; +import com.android.dialer.duo.DuoComponent; import com.android.dialer.function.Consumer; import com.android.dialer.logging.DialerImpression.Type; import com.android.dialer.logging.Logger; @@ -33,6 +34,7 @@ import com.android.dialer.precall.PreCallAction; import com.android.dialer.precall.PreCallComponent; import com.android.dialer.precall.PreCallCoordinator; import com.android.dialer.telecom.TelecomUtil; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -101,7 +103,7 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator { LogUtil.enterBlock("PreCallCoordinatorImpl.runNextAction"); Assert.checkArgument(currentAction == null); if (currentActionIndex >= actions.size()) { - TelecomUtil.placeCall(activity, builder.build()); + placeCall(); activity.finish(); return; } @@ -177,4 +179,20 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator { output -> successListener.accept((OutputT) output), failureListener::accept); } + + private void placeCall() { + if (builder.isDuoCall()) { + Optional<Intent> intent = + DuoComponent.get(activity) + .getDuo() + .getCallIntent(builder.getUri().getSchemeSpecificPart()); + if (intent.isPresent()) { + activity.startActivityForResult(intent.get(), 0); + return; + } else { + LogUtil.e("PreCallCoordinatorImpl.placeCall", "duo.getCallIntent() returned absent"); + } + } + TelecomUtil.placeCall(activity, builder.build()); + } } diff --git a/java/com/android/dialer/precall/impl/PreCallModule.java b/java/com/android/dialer/precall/impl/PreCallModule.java index 9820e2b66..455453ef3 100644 --- a/java/com/android/dialer/precall/impl/PreCallModule.java +++ b/java/com/android/dialer/precall/impl/PreCallModule.java @@ -37,12 +37,13 @@ public abstract class PreCallModule { @Provides @Singleton public static ImmutableList<PreCallAction> provideActions( - CallingAccountSelector callingAccountSelector) { + DuoAction duoAction, CallingAccountSelector callingAccountSelector) { return ImmutableList.of( new PermissionCheckAction(), new MalformedNumberRectifier( ImmutableList.of(new UkRegionPrefixInInternationalFormatHandler())), callingAccountSelector, + duoAction, new AssistedDialAction()); } } diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index f1eed91ba..abb3aecd4 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -51,9 +51,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; -import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.dialercontact.DialerContact; -import com.android.dialer.duo.DuoComponent; import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; import com.android.dialer.logging.DialerImpression; @@ -549,8 +547,11 @@ public final class NewSearchFragment extends Fragment public void placeDuoCall(String phoneNumber) { Logger.get(getContext()) .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_SEARCH); - Intent intent = DuoComponent.get(getContext()).getDuo().getCallIntent(phoneNumber).orNull(); - getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); + PreCall.start( + getContext(), + new CallIntentBuilder(phoneNumber, CallInitiationType.Type.REGULAR_SEARCH) + .setIsVideoCall(true) + .setIsDuoCall(true)); FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).onCallPlacedFromSearch(); } diff --git a/java/com/android/dialer/spam/Spam.java b/java/com/android/dialer/spam/Spam.java index 21d770ed3..181a55dea 100644 --- a/java/com/android/dialer/spam/Spam.java +++ b/java/com/android/dialer/spam/Spam.java @@ -24,6 +24,7 @@ import com.android.dialer.DialerPhoneNumber; import com.android.dialer.logging.ContactLookupResult; import com.android.dialer.logging.ContactSource; import com.android.dialer.logging.ReportingLocation; +import com.android.dialer.spam.status.SpamStatus; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; @@ -41,6 +42,25 @@ public interface Spam { ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers); /** + * Checks if the given number is suspected of being spam. + * + * @param dialerPhoneNumber the phone number. + * @return the {@link SpamStatus} for the given number. + */ + ListenableFuture<SpamStatus> checkSpamStatus(DialerPhoneNumber dialerPhoneNumber); + + /** + * Checks if the given number is suspected of being spam. + * + * <p>See {@link #checkSpamStatus(DialerPhoneNumber)}. + * + * @param number the phone number. + * @param defaultCountryIso the default country to use if it's not part of the number. + * @return the {@link SpamStatus} for the given number. + */ + ListenableFuture<SpamStatus> checkSpamStatus(String number, @Nullable String defaultCountryIso); + + /** * Called as an indication that the Spam implementation should check whether downloading a spam * list needs to occur or not. * @@ -55,15 +75,6 @@ public interface Spam { ListenableFuture<Void> updateSpamListDownload(boolean isEnabledByUser); /** - * Checks if the given number is suspected of being a spam. - * - * @param number The phone number of the call. - * @param countryIso The country ISO of the call. - * @param listener The callback to be invoked after {@code Info} is fetched. - */ - void checkSpamStatus(String number, String countryIso, Listener listener); - - /** * @param number The number to check if the number is in the user's white list (non spam list) * @param countryIso The country ISO of the call. * @param listener The callback to be invoked after {@code Info} is fetched. diff --git a/java/com/android/dialer/spam/SpamComponent.java b/java/com/android/dialer/spam/SpamComponent.java index 2b70b6fe1..a0ffcce1f 100644 --- a/java/com/android/dialer/spam/SpamComponent.java +++ b/java/com/android/dialer/spam/SpamComponent.java @@ -18,6 +18,7 @@ package com.android.dialer.spam; import android.content.Context; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** Dagger component to get Spam. */ @@ -35,6 +36,7 @@ public abstract class SpamComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { SpamComponent spamComponent(); } diff --git a/java/com/android/dialer/spam/SpamStub.java b/java/com/android/dialer/spam/SpamStub.java index 8851fd09e..2789c01e7 100644 --- a/java/com/android/dialer/spam/SpamStub.java +++ b/java/com/android/dialer/spam/SpamStub.java @@ -16,12 +16,14 @@ package com.android.dialer.spam; +import android.support.annotation.Nullable; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.logging.ContactLookupResult; import com.android.dialer.logging.ContactSource; import com.android.dialer.logging.ReportingLocation; -import com.google.common.base.Optional; +import com.android.dialer.spam.status.SimpleSpamStatus; +import com.android.dialer.spam.status.SpamStatus; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; @@ -47,33 +49,27 @@ public class SpamStub implements Spam { ImmutableMap.Builder<DialerPhoneNumber, SpamStatus> resultBuilder = new ImmutableMap.Builder<>(); for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) { - resultBuilder.put( - dialerPhoneNumber, - new SpamStatus() { - @Override - public boolean isSpam() { - return false; - } - - @Override - public Optional<Long> getTimestampMillis() { - return Optional.absent(); - } - }); + resultBuilder.put(dialerPhoneNumber, SimpleSpamStatus.notSpam()); } return resultBuilder.build(); }); } @Override - public ListenableFuture<Void> updateSpamListDownload(boolean isEnabledByUser) { - // no-op - return Futures.immediateFuture(null); + public ListenableFuture<SpamStatus> checkSpamStatus(DialerPhoneNumber dialerPhoneNumber) { + return Futures.immediateFuture(SimpleSpamStatus.notSpam()); } @Override - public void checkSpamStatus(String number, String countryIso, Listener listener) { - listener.onComplete(false); + public ListenableFuture<SpamStatus> checkSpamStatus( + String number, @Nullable String defaultCountryIso) { + return Futures.immediateFuture(SimpleSpamStatus.notSpam()); + } + + @Override + public ListenableFuture<Void> updateSpamListDownload(boolean isEnabledByUser) { + // no-op + return Futures.immediateFuture(null); } @Override diff --git a/java/com/android/dialer/spam/StubSpamModule.java b/java/com/android/dialer/spam/StubSpamModule.java index 5540408ad..b60967475 100644 --- a/java/com/android/dialer/spam/StubSpamModule.java +++ b/java/com/android/dialer/spam/StubSpamModule.java @@ -16,10 +16,13 @@ package com.android.dialer.spam; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; import dagger.Binds; import dagger.Module; /** Module which binds {@link SpamStub}. */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) @Module public abstract class StubSpamModule { diff --git a/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java b/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java index a117e1908..42fb39f38 100644 --- a/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java +++ b/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java @@ -42,6 +42,8 @@ public class SpamBlockingPromoHelper { static final String SPAM_BLOCKING_PROMO_PERIOD_MILLIS = "spam_blocking_promo_period_millis"; static final String SPAM_BLOCKING_PROMO_LAST_SHOW_MILLIS = "spam_blocking_promo_last_show_millis"; public static final String ENABLE_SPAM_BLOCKING_PROMO = "enable_spam_blocking_promo"; + public static final String ENABLE_AFTER_CALL_SPAM_BLOCKING_PROMO = + "enable_after_call_spam_blocking_promo"; private final Context context; private final SpamSettings spamSettings; @@ -77,6 +79,13 @@ public class SpamBlockingPromoHelper { return lastShowMillis == 0 || System.currentTimeMillis() - lastShowMillis > showPeriodMillis; } + /* Returns true if we should show a spam blocking promo in after call notification scenario. */ + public boolean shouldShowAfterCallSpamBlockingPromo() { + return shouldShowSpamBlockingPromo() + && ConfigProviderBindings.get(context) + .getBoolean(ENABLE_AFTER_CALL_SPAM_BLOCKING_PROMO, false); + } + /** * Shows a spam blocking promo dialog. * diff --git a/java/com/android/dialer/spam/status/GlobalSpamListStatus.java b/java/com/android/dialer/spam/status/GlobalSpamListStatus.java new file mode 100644 index 000000000..741d6e869 --- /dev/null +++ b/java/com/android/dialer/spam/status/GlobalSpamListStatus.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.spam.status; + +import android.support.annotation.IntDef; +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** A value class representing a number's spam status in the global spam list. */ +@AutoValue +public abstract class GlobalSpamListStatus { + + /** Integers representing the spam status in the global spam list. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Status.NOT_ON_LIST, Status.ON_LIST}) + public @interface Status { + int NOT_ON_LIST = 1; + int ON_LIST = 2; + } + + public abstract @Status int getStatus(); + + /** + * Returns the timestamp (in milliseconds) representing when a number's spam status was put on the + * list, or {@code Optional.absent()} if the number is not on the list. + */ + public abstract Optional<Long> getTimestampMillis(); + + public static GlobalSpamListStatus notOnList() { + return new AutoValue_GlobalSpamListStatus(Status.NOT_ON_LIST, Optional.absent()); + } + + public static GlobalSpamListStatus onList(long timestampMillis) { + return new AutoValue_GlobalSpamListStatus(Status.ON_LIST, Optional.of(timestampMillis)); + } +} diff --git a/java/com/android/dialer/spam/status/SimpleSpamStatus.java b/java/com/android/dialer/spam/status/SimpleSpamStatus.java new file mode 100644 index 000000000..ec28b9dce --- /dev/null +++ b/java/com/android/dialer/spam/status/SimpleSpamStatus.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.spam.status; + +import android.support.annotation.Nullable; +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; + +/** Holds a boolean and long to represent spam status. */ +@AutoValue +public abstract class SimpleSpamStatus implements SpamStatus { + + /** Returns a SimpleSpamStatus with the given boolean and timestamp. */ + public static SimpleSpamStatus create(boolean isSpam, @Nullable Long timestampMillis) { + return new AutoValue_SimpleSpamStatus(isSpam, Optional.fromNullable(timestampMillis)); + } + + /** Returns a SimpleSpamStatus that's not marked as spam and has no timestamp. */ + public static SimpleSpamStatus notSpam() { + return create(false, null); + } +} diff --git a/java/com/android/dialer/spam/SpamStatus.java b/java/com/android/dialer/spam/status/SpamStatus.java index 0b859d1c7..8186ac5cc 100644 --- a/java/com/android/dialer/spam/SpamStatus.java +++ b/java/com/android/dialer/spam/status/SpamStatus.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.dialer.spam; +package com.android.dialer.spam.status; import com.google.common.base.Optional; diff --git a/java/com/android/dialer/spam/status/UserSpamListStatus.java b/java/com/android/dialer/spam/status/UserSpamListStatus.java new file mode 100644 index 000000000..01f99872b --- /dev/null +++ b/java/com/android/dialer/spam/status/UserSpamListStatus.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.spam.status; + +import android.support.annotation.IntDef; +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** A value class representing a number's spam status in the user spam list. */ +@AutoValue +@SuppressWarnings("Guava") +public abstract class UserSpamListStatus { + + /** Integers representing the spam status in the user spam list. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Status.NOT_ON_LIST, Status.WHITELISTED, Status.BLACKLISTED}) + public @interface Status { + int NOT_ON_LIST = 1; + int WHITELISTED = 2; + int BLACKLISTED = 3; + } + + public abstract @Status int getStatus(); + + /** + * Returns the timestamp (in milliseconds) representing when a number's spam status was put on the + * list, or {@code Optional.absent()} if the number is not on the list. + */ + public abstract Optional<Long> getTimestampMillis(); + + public static UserSpamListStatus notOnList() { + return new AutoValue_UserSpamListStatus(Status.NOT_ON_LIST, Optional.absent()); + } + + public static UserSpamListStatus whitelisted(long timestampMillis) { + return new AutoValue_UserSpamListStatus(Status.WHITELISTED, Optional.of(timestampMillis)); + } + + public static UserSpamListStatus blacklisted(long timestampMillis) { + return new AutoValue_UserSpamListStatus(Status.BLACKLISTED, Optional.of(timestampMillis)); + } +} diff --git a/java/com/android/dialer/speeddial/DisambigDialog.java b/java/com/android/dialer/speeddial/DisambigDialog.java index 98fc992f8..2dd43fa0a 100644 --- a/java/com/android/dialer/speeddial/DisambigDialog.java +++ b/java/com/android/dialer/speeddial/DisambigDialog.java @@ -18,7 +18,6 @@ package com.android.dialer.speeddial; import android.app.Dialog; import android.content.Context; -import android.content.Intent; import android.os.Bundle; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; @@ -40,8 +39,6 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.constants.ActivityRequestCodes; -import com.android.dialer.duo.DuoComponent; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCall; @@ -184,16 +181,13 @@ public class DisambigDialog extends DialogFragment { Logger.get(getContext()) .logImpression( DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT_DISAMBIG); - Intent intent = - DuoComponent.get(getContext()).getDuo().getCallIntent(channel.number()).orNull(); - getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); - return; } PreCall.start( getContext(), new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL) - .setIsVideoCall(true)); + .setIsVideoCall(true) + .setIsDuoCall(channel.technology() == Channel.DUO)); dismiss(); } diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index aa306d214..fac9a13d2 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -41,7 +41,6 @@ import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.SupportUiListener; import com.android.dialer.constants.ActivityRequestCodes; -import com.android.dialer.duo.DuoComponent; import com.android.dialer.historyitemactions.DividerModule; import com.android.dialer.historyitemactions.HistoryItemActionBottomSheet; import com.android.dialer.historyitemactions.HistoryItemActionModule; @@ -243,16 +242,13 @@ public class SpeedDialFragment extends Fragment { if (channel.technology() == Channel.DUO) { Logger.get(activity) .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT); - Intent intent = - DuoComponent.get(activity).getDuo().getCallIntent(channel.number()).orNull(); - activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); - return; } PreCall.start( activity, new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL) - .setIsVideoCall(channel.isVideoTechnology())); + .setIsVideoCall(channel.isVideoTechnology()) + .setIsDuoCall(channel.technology() == Channel.DUO)); } @Override @@ -341,15 +337,12 @@ public class SpeedDialFragment extends Fragment { Logger.get(getContext()) .logImpression( DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_SUGGESTED_CONTACT); - Intent intent = - DuoComponent.get(getContext()).getDuo().getCallIntent(channel.number()).orNull(); - getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); - return; } PreCall.start( getContext(), new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL) - .setIsVideoCall(channel.isVideoTechnology())); + .setIsVideoCall(channel.isVideoTechnology()) + .setIsDuoCall(channel.technology() == Channel.DUO)); } private final class StarContactModule implements HistoryItemActionModule { @@ -426,15 +419,12 @@ public class SpeedDialFragment extends Fragment { if (channel.technology() == Channel.DUO) { Logger.get(activity) .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT); - Intent intent = - DuoComponent.get(activity).getDuo().getCallIntent(channel.number()).orNull(); - activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); - return; } PreCall.start( activity, new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL) - .setIsVideoCall(channel.isVideoTechnology())); + .setIsVideoCall(channel.isVideoTechnology()) + .setIsDuoCall(channel.technology() == Channel.DUO)); } @Override diff --git a/java/com/android/dialer/storage/StorageComponent.java b/java/com/android/dialer/storage/StorageComponent.java index cb5c4a879..4754cc6db 100644 --- a/java/com/android/dialer/storage/StorageComponent.java +++ b/java/com/android/dialer/storage/StorageComponent.java @@ -19,6 +19,7 @@ package com.android.dialer.storage; import android.content.Context; import android.content.SharedPreferences; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** Dagger component for storage. */ @@ -42,6 +43,7 @@ public abstract class StorageComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { StorageComponent storageComponent(); } diff --git a/java/com/android/dialer/storage/StorageModule.java b/java/com/android/dialer/storage/StorageModule.java index e1c5b4b08..370998cdb 100644 --- a/java/com/android/dialer/storage/StorageModule.java +++ b/java/com/android/dialer/storage/StorageModule.java @@ -20,11 +20,14 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.v4.content.ContextCompat; import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; import dagger.Module; import dagger.Provides; import javax.inject.Singleton; /** Module for the storage component. */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) @Module public class StorageModule { diff --git a/java/com/android/dialer/voicemail/listui/menu/Modules.java b/java/com/android/dialer/voicemail/listui/menu/Modules.java index dcd9116e9..5a9a7110c 100644 --- a/java/com/android/dialer/voicemail/listui/menu/Modules.java +++ b/java/com/android/dialer/voicemail/listui/menu/Modules.java @@ -58,6 +58,7 @@ final class Modules { .setCanSupportCarrierVideoCall( voicemailEntry.getNumberAttributes().getCanSupportCarrierVideoCall()) .setIsBlocked(voicemailEntry.getNumberAttributes().getIsBlocked()) + .setIsEmergencyNumber(voicemailEntry.getNumberAttributes().getIsEmergencyNumber()) .setIsSpam(voicemailEntry.getNumberAttributes().getIsSpam()) // A voicemail call is an outgoing call to the voicemail box. // Voicemail entries are not voicemail calls. diff --git a/java/com/android/incallui/AnswerScreenPresenter.java b/java/com/android/incallui/AnswerScreenPresenter.java index 0b79e4be7..e41bac606 100644 --- a/java/com/android/incallui/AnswerScreenPresenter.java +++ b/java/com/android/incallui/AnswerScreenPresenter.java @@ -24,6 +24,7 @@ import android.support.v4.os.UserManagerCompat; import android.telecom.VideoProfile; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; @@ -35,6 +36,9 @@ import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; import com.android.incallui.call.DialerCallListener; import com.android.incallui.incalluilock.InCallUiLock; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; /** Manages changes for an incoming call screen. */ public class AnswerScreenPresenter @@ -90,6 +94,39 @@ public class AnswerScreenPresenter @Override public void onAnswer(boolean answerVideoAsAudio) { + + DialerCall incomingCall = CallList.getInstance().getIncomingCall(); + InCallActivity inCallActivity = + (InCallActivity) answerScreen.getAnswerScreenFragment().getActivity(); + ListenableFuture<Void> answerPrecondition; + + if (incomingCall != null && inCallActivity != null) { + answerPrecondition = inCallActivity.getSpeakEasyCallManager().onNewIncomingCall(incomingCall); + } else { + answerPrecondition = Futures.immediateFuture(null); + } + + Futures.addCallback( + answerPrecondition, + new FutureCallback<Void>() { + @Override + public void onSuccess(Void result) { + onAnswerCallback(answerVideoAsAudio); + } + + @Override + public void onFailure(Throwable t) { + onAnswerCallback(answerVideoAsAudio); + // TODO(erfanian): Enumerate all error states and specify recovery strategies. + throw new RuntimeException("Failed to successfully complete pre call tasks.", t); + } + }, + DialerExecutorComponent.get(context).uiExecutor()); + addTimeoutCheck(); + } + + private void onAnswerCallback(boolean answerVideoAsAudio) { + if (answerScreen.isVideoUpgradeRequest()) { if (answerVideoAsAudio) { Logger.get(context) @@ -113,7 +150,6 @@ public class AnswerScreenPresenter call.answer(); } } - addTimeoutCheck(); } @Override diff --git a/java/com/android/incallui/NotificationBroadcastReceiver.java b/java/com/android/incallui/NotificationBroadcastReceiver.java index 52d01f5c4..602eb5c5a 100644 --- a/java/com/android/incallui/NotificationBroadcastReceiver.java +++ b/java/com/android/incallui/NotificationBroadcastReceiver.java @@ -20,15 +20,21 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Build.VERSION_CODES; +import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.telecom.CallAudioState; import android.telecom.VideoProfile; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; import com.android.incallui.call.TelecomAdapter; +import com.android.incallui.speakeasy.SpeakEasyCallManager; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; /** * Accepts broadcast Intents which will be prepared by {@link StatusBarNotifier} and thus sent from @@ -72,9 +78,9 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { // TODO: Commands of this nature should exist in the CallList. if (action.equals(ACTION_ANSWER_VIDEO_INCOMING_CALL)) { - answerIncomingCall(VideoProfile.STATE_BIDIRECTIONAL); + answerIncomingCall(VideoProfile.STATE_BIDIRECTIONAL, context); } else if (action.equals(ACTION_ANSWER_VOICE_INCOMING_CALL)) { - answerIncomingCall(VideoProfile.STATE_AUDIO_ONLY); + answerIncomingCall(VideoProfile.STATE_AUDIO_ONLY, context); } else if (action.equals(ACTION_DECLINE_INCOMING_CALL)) { Logger.get(context) .logImpression(DialerImpression.Type.REJECT_INCOMING_CALL_FROM_NOTIFICATION); @@ -140,7 +146,7 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { } } - private void answerIncomingCall(int videoState) { + private void answerIncomingCall(int videoState, @NonNull Context context) { CallList callList = InCallPresenter.getInstance().getCallList(); if (callList == null) { StatusBarNotifier.clearAllCallNotifications(); @@ -148,13 +154,42 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { } else { DialerCall call = callList.getIncomingCall(); if (call != null) { - call.answer(videoState); - InCallPresenter.getInstance() - .showInCall(false /* showDialpad */, false /* newOutgoingCall */); + + SpeakEasyCallManager speakEasyCallManager = + InCallPresenter.getInstance().getSpeakEasyCallManager(); + ListenableFuture<Void> answerPrecondition; + + if (speakEasyCallManager != null) { + answerPrecondition = speakEasyCallManager.onNewIncomingCall(call); + } else { + answerPrecondition = Futures.immediateFuture(null); + } + + Futures.addCallback( + answerPrecondition, + new FutureCallback<Void>() { + @Override + public void onSuccess(Void result) { + answerIncomingCallCallback(call, videoState); + } + + @Override + public void onFailure(Throwable t) { + answerIncomingCallCallback(call, videoState); + // TODO(erfanian): Enumerate all error states and specify recovery strategies. + throw new RuntimeException("Failed to successfully complete pre call tasks.", t); + } + }, + DialerExecutorComponent.get(context).uiExecutor()); } } } + private void answerIncomingCallCallback(@NonNull DialerCall call, int videoState) { + call.answer(videoState); + InCallPresenter.getInstance().showInCall(false /* showDialpad */, false /* newOutgoingCall */); + } + private void declineIncomingCall() { CallList callList = InCallPresenter.getInstance().getCallList(); if (callList == null) { diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java index 96bdda1ba..7c4585ca1 100644 --- a/java/com/android/incallui/ReturnToCallController.java +++ b/java/com/android/incallui/ReturnToCallController.java @@ -196,12 +196,13 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio newInCallState != inCallState && newInCallState == InCallState.OUTGOING && shouldStartInBubbleMode; + boolean bubbleNeverVisible = (bubble == null || !(bubble.isVisible() || bubble.isDismissed())); if (bubble != null && isNewBackgroundCall) { // If new outgoing call is in bubble mode, update bubble info. // We don't update if new call is not in bubble mode even if the existing call is. bubble.setBubbleInfo(generateBubbleInfoForBackgroundCalling()); } - if ((bubble == null || !(bubble.isVisible() || bubble.isDismissed()) || isNewBackgroundCall) + if (((bubbleNeverVisible && newInCallState != InCallState.OUTGOING) || isNewBackgroundCall) && getCall() != null && !InCallPresenter.getInstance().isShowingInCallUi()) { LogUtil.i("ReturnToCallController.onCallListChange", "going to show bubble"); diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java index 5d9db32d9..0e89ac75d 100644 --- a/java/com/android/incallui/call/CallList.java +++ b/java/com/android/incallui/call/CallList.java @@ -32,6 +32,7 @@ import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.blocking.FilteredNumbersUtil; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.logging.DialerImpression; @@ -41,10 +42,14 @@ import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.shortcuts.ShortcutUsageReporter; import com.android.dialer.spam.Spam; import com.android.dialer.spam.SpamComponent; +import com.android.dialer.spam.status.SpamStatus; import com.android.dialer.telecom.TelecomCallUtil; import com.android.incallui.call.state.DialerCallState; import com.android.incallui.latencyreport.LatencyReport; import com.android.incallui.videotech.utils.SessionModificationState; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -146,46 +151,53 @@ public class CallList implements DialerCallDelegate { LogUtil.d("CallList.onCallAdded", "callState=" + call.getState()); if (SpamComponent.get(context).spamSettings().isSpamEnabled()) { String number = TelecomCallUtil.getNumber(telecomCall); - SpamComponent.get(context) - .spam() - .checkSpamStatus( - number, - call.getCountryIso(), - new Spam.Listener() { - @Override - public void onComplete(boolean isSpam) { - boolean isIncomingCall = - call.getState() == DialerCallState.INCOMING - || call.getState() == DialerCallState.CALL_WAITING; - if (isSpam) { - if (!isIncomingCall) { - LogUtil.i( - "CallList.onCallAdded", - "marking spam call as not spam because it's not an incoming call"); - isSpam = false; - } else if (isPotentialEmergencyCallback(context, call)) { - LogUtil.i( - "CallList.onCallAdded", - "marking spam call as not spam because an emergency call was made on this" - + " device recently"); - isSpam = false; - } - } - - if (isIncomingCall) { - Logger.get(context) - .logCallImpression( - isSpam - ? DialerImpression.Type.INCOMING_SPAM_CALL - : DialerImpression.Type.INCOMING_NON_SPAM_CALL, - call.getUniqueCallId(), - call.getTimeAddedMs()); - } - call.setSpam(isSpam); - onUpdateCall(call); - notifyGenericListeners(); + ListenableFuture<SpamStatus> futureSpamStatus = + SpamComponent.get(context).spam().checkSpamStatus(number, call.getCountryIso()); + + Futures.addCallback( + futureSpamStatus, + new FutureCallback<SpamStatus>() { + @Override + public void onSuccess(@Nullable SpamStatus result) { + boolean isIncomingCall = + call.getState() == DialerCallState.INCOMING + || call.getState() == DialerCallState.CALL_WAITING; + boolean isSpam = result.isSpam(); + if (isSpam) { + if (!isIncomingCall) { + LogUtil.i( + "CallList.onCallAdded", + "marking spam call as not spam because it's not an incoming call"); + isSpam = false; + } else if (isPotentialEmergencyCallback(context, call)) { + LogUtil.i( + "CallList.onCallAdded", + "marking spam call as not spam because an emergency call was made on this" + + " device recently"); + isSpam = false; } - }); + } + + if (isIncomingCall) { + Logger.get(context) + .logCallImpression( + isSpam + ? DialerImpression.Type.INCOMING_SPAM_CALL + : DialerImpression.Type.INCOMING_NON_SPAM_CALL, + call.getUniqueCallId(), + call.getTimeAddedMs()); + } + call.setSpam(isSpam); + onUpdateCall(call); + notifyGenericListeners(); + } + + @Override + public void onFailure(Throwable t) { + LogUtil.e("CallList.onFailure", "unable to query spam status", t); + } + }, + DialerExecutorComponent.get(context).uiExecutor()); Trace.beginSection("updateUserMarkedSpamStatus"); updateUserMarkedSpamStatus(call, context, number); diff --git a/java/com/android/incallui/contactgrid/ContactGridManager.java b/java/com/android/incallui/contactgrid/ContactGridManager.java index d8b1f5004..493f2d583 100644 --- a/java/com/android/incallui/contactgrid/ContactGridManager.java +++ b/java/com/android/incallui/contactgrid/ContactGridManager.java @@ -23,6 +23,8 @@ import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.telephony.PhoneNumberUtils; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -416,7 +418,9 @@ public class ContactGridManager { // This is used for carriers like Project Fi to show the callback number for emergency calls. deviceNumberTextView.setText( context.getString( - R.string.contact_grid_callback_number, primaryCallState.callbackNumber())); + R.string.contact_grid_callback_number, + BidiFormatter.getInstance() + .unicodeWrap(primaryCallState.callbackNumber(), TextDirectionHeuristics.LTR))); deviceNumberTextView.setVisibility(View.VISIBLE); if (primaryInfo.shouldShowLocation()) { deviceNumberDivider.setVisibility(View.VISIBLE); diff --git a/java/com/android/incallui/spam/SpamNotificationActivity.java b/java/com/android/incallui/spam/SpamNotificationActivity.java index e10dea381..2cf486874 100644 --- a/java/com/android/incallui/spam/SpamNotificationActivity.java +++ b/java/com/android/incallui/spam/SpamNotificationActivity.java @@ -528,7 +528,7 @@ public class SpamNotificationActivity extends FragmentActivity { } private void maybeShowSpamBlockingPromoAndFinish() { - if (!spamBlockingPromoHelper.shouldShowSpamBlockingPromo()) { + if (!spamBlockingPromoHelper.shouldShowAfterCallSpamBlockingPromo()) { finish(); return; } diff --git a/java/com/android/incallui/spam/SpamNotificationService.java b/java/com/android/incallui/spam/SpamNotificationService.java index b418ea23e..82a943da7 100644 --- a/java/com/android/incallui/spam/SpamNotificationService.java +++ b/java/com/android/incallui/spam/SpamNotificationService.java @@ -122,7 +122,7 @@ public class SpamNotificationService extends Service { ReportingLocation.Type.FEEDBACK_PROMPT, contactLookupResultType); new FilteredNumberAsyncQueryHandler(this).blockNumber(null, number, countryIso); - if (spamBlockingPromoHelper.shouldShowSpamBlockingPromo()) { + if (spamBlockingPromoHelper.shouldShowAfterCallSpamBlockingPromo()) { spamBlockingPromoHelper.showSpamBlockingPromoNotification( notificationTag, notificationId, diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java index 8a815d385..b060f64cb 100644 --- a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java +++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java @@ -21,6 +21,7 @@ import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import com.android.incallui.call.DialerCall; import com.google.common.base.Optional; +import com.google.common.util.concurrent.ListenableFuture; /** Provides operations necessary to SpeakEasy. */ public interface SpeakEasyCallManager { @@ -40,6 +41,13 @@ public interface SpeakEasyCallManager { void onCallRemoved(@NonNull DialerCall call); /** + * Indicates there is a new incoming call that is about to be answered. + * + * @param call The call which is about to become active. + */ + ListenableFuture<Void> onNewIncomingCall(@NonNull DialerCall call); + + /** * Indicates the feature is available. * * @param context The application context. diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java index a0409737b..da5e88aa3 100644 --- a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java +++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java @@ -22,6 +22,8 @@ import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import com.android.incallui.call.DialerCall; import com.google.common.base.Optional; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import javax.inject.Inject; /** Default implementation of SpeakEasyCallManager. */ @@ -41,6 +43,11 @@ public class SpeakEasyCallManagerStub implements SpeakEasyCallManager { @Override public void onCallRemoved(DialerCall call) {} + @Override + public ListenableFuture<Void> onNewIncomingCall(@NonNull DialerCall call) { + return Futures.immediateFuture(null); + } + /** Always returns false. */ @Override public boolean isAvailable(@NonNull Context unused) { diff --git a/java/com/android/incallui/videotech/duo/DuoVideoTech.java b/java/com/android/incallui/videotech/duo/DuoVideoTech.java index fdaed077b..ac74e54df 100644 --- a/java/com/android/incallui/videotech/duo/DuoVideoTech.java +++ b/java/com/android/incallui/videotech/duo/DuoVideoTech.java @@ -23,6 +23,7 @@ import android.telecom.Call; import android.telecom.PhoneAccountHandle; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.duo.Duo; import com.android.dialer.duo.DuoListener; @@ -33,6 +34,8 @@ import com.android.incallui.videotech.VideoTech; import com.android.incallui.videotech.utils.SessionModificationState; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; public class DuoVideoTech implements VideoTech, DuoListener { private final Duo duo; @@ -77,7 +80,10 @@ public class DuoVideoTech implements VideoTech, DuoListener { if (!isRemoteUpgradeAvailabilityQueried) { LogUtil.v("DuoVideoTech.isAvailable", "reachability unknown, starting remote query"); isRemoteUpgradeAvailabilityQueried = true; - duo.updateReachability(context, ImmutableList.of(callingNumber)); + Futures.addCallback( + duo.updateReachability(context, ImmutableList.of(callingNumber)), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); } return false; diff --git a/java/com/android/voicemail/VoicemailComponent.java b/java/com/android/voicemail/VoicemailComponent.java index bed75f0ef..0e09627b3 100644 --- a/java/com/android/voicemail/VoicemailComponent.java +++ b/java/com/android/voicemail/VoicemailComponent.java @@ -18,6 +18,7 @@ package com.android.voicemail; import android.content.Context; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; import dagger.Subcomponent; /** Subcomponent that can be used to access the voicemail implementation. */ @@ -32,6 +33,7 @@ public abstract class VoicemailComponent { } /** Used to refer to the root application component. */ + @IncludeInDialerRoot public interface HasComponent { VoicemailComponent voicemailComponent(); } diff --git a/java/com/android/voicemail/stub/StubVoicemailModule.java b/java/com/android/voicemail/stub/StubVoicemailModule.java index 6c1552c15..efcb4cffa 100644 --- a/java/com/android/voicemail/stub/StubVoicemailModule.java +++ b/java/com/android/voicemail/stub/StubVoicemailModule.java @@ -16,6 +16,8 @@ package com.android.voicemail.stub; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; import com.android.voicemail.VoicemailClient; import dagger.Binds; import dagger.Module; @@ -24,6 +26,7 @@ import javax.inject.Singleton; /** * A no-op version of the voicemail module for build targets that don't support the new OTMP client. */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) @Module public abstract class StubVoicemailModule { |