summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/quantum/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml3
-rw-r--r--java/com/android/bubble/Bubble.java3
-rw-r--r--java/com/android/bubble/stub/BubbleStub.java5
-rw-r--r--java/com/android/dialer/app/DialtactsActivity.java2
-rw-r--r--java/com/android/dialer/app/calllog/CallLogNotificationsService.java4
-rw-r--r--java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java3
-rw-r--r--java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java4
-rw-r--r--java/com/android/dialer/app/calllog/VoicemailQueryHandler.java6
-rw-r--r--java/com/android/dialer/blocking/BlockNumberDialogFragment.java1
-rw-r--r--java/com/android/dialer/blocking/BlockedNumbersAutoMigrator.java1
-rw-r--r--java/com/android/dialer/blocking/BlockedNumbersMigrator.java1
-rw-r--r--java/com/android/dialer/blocking/Blocking.java117
-rw-r--r--java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java1
-rw-r--r--java/com/android/dialer/blocking/FilteredNumberCompat.java1
-rw-r--r--java/com/android/dialer/blocking/FilteredNumberProvider.java1
-rw-r--r--java/com/android/dialer/blocking/FilteredNumbersUtil.java1
-rw-r--r--java/com/android/dialer/blocking/MigrateBlockedNumbersDialogFragment.java1
-rw-r--r--java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java151
-rw-r--r--java/com/android/dialer/blockreportspam/res/values/strings.xml5
-rw-r--r--java/com/android/dialer/calllog/ClearMissedCalls.java36
-rw-r--r--java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java1
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogAdapter.java20
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogFragment.java9
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java31
-rw-r--r--java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java8
-rw-r--r--java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java6
-rw-r--r--java/com/android/dialer/calllog/ui/res/values/styles.xml6
-rw-r--r--java/com/android/dialer/calllogutils/CallLogEntryText.java60
-rw-r--r--java/com/android/dialer/calllogutils/res/values/strings.xml7
-rw-r--r--java/com/android/dialer/commandline/CommandLineModule.java14
-rw-r--r--java/com/android/dialer/commandline/impl/BlockingCommand.java (renamed from java/com/android/dialer/commandline/impl/Blocking.java)61
-rw-r--r--java/com/android/dialer/logging/LoggingBindings.java6
-rw-r--r--java/com/android/dialer/logging/LoggingBindingsStub.java6
-rw-r--r--java/com/android/dialer/logging/dialer_impression.proto10
-rw-r--r--java/com/android/dialer/main/impl/MainSearchController.java27
-rw-r--r--java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java32
-rw-r--r--java/com/android/dialer/searchfragment/list/NewSearchFragment.java48
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchAdapter.java13
-rw-r--r--java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml2
-rw-r--r--java/com/android/dialer/speeddial/database/SpeedDialEntry.java116
-rw-r--r--java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java57
-rw-r--r--java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java221
-rw-r--r--java/com/android/dialer/telecom/TelecomUtil.java6
-rw-r--r--java/com/android/incallui/CallButtonPresenter.java9
-rw-r--r--java/com/android/incallui/CallCardPresenter.java55
-rw-r--r--java/com/android/incallui/InCallActivity.java4
-rw-r--r--java/com/android/incallui/InCallPresenter.java64
-rw-r--r--java/com/android/incallui/ProximitySensor.java2
-rw-r--r--java/com/android/incallui/ReturnToCallController.java6
-rw-r--r--java/com/android/incallui/call/CallList.java9
-rw-r--r--java/com/android/incallui/call/DialerCall.java33
-rw-r--r--java/com/android/incallui/callpending/CallPendingActivity.java3
-rw-r--r--java/com/android/incallui/incall/impl/ButtonChooserFactory.java5
-rw-r--r--java/com/android/incallui/incall/impl/ButtonController.java18
-rw-r--r--java/com/android/incallui/incall/impl/InCallFragment.java5
-rw-r--r--java/com/android/incallui/incall/impl/res/values/strings.xml4
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonIds.java2
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java2
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java2
-rw-r--r--java/com/android/incallui/rtt/impl/AudioSelectMenu.java3
-rw-r--r--java/com/android/incallui/rtt/impl/RttChatAdapter.java8
-rw-r--r--java/com/android/incallui/rtt/impl/RttChatFragment.java53
-rw-r--r--java/com/android/incallui/rtt/impl/RttChatMessage.java2
-rw-r--r--java/com/android/incallui/rtt/impl/RttOverflowMenu.java4
-rw-r--r--java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml2
-rw-r--r--java/com/android/incallui/rtt/impl/res/layout/audio_route.xml1
-rw-r--r--java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml1
-rw-r--r--java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml1
-rw-r--r--java/com/android/incallui/rtt/impl/res/values/dimens.xml1
-rw-r--r--java/com/android/incallui/rtt/impl/res/values/styles.xml6
-rw-r--r--java/com/android/incallui/videotech/ims/ImsVideoTech.java1
-rw-r--r--java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java2
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java14
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java9
74 files changed, 1117 insertions, 328 deletions
diff --git a/assets/quantum/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml
index 3a85b7dd3..12db78d99 100644
--- a/assets/quantum/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml
+++ b/assets/quantum/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml
@@ -18,7 +18,8 @@
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
- android:tint="?attr/colorControlNormal">
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
diff --git a/java/com/android/bubble/Bubble.java b/java/com/android/bubble/Bubble.java
index e192e06f4..1b853cf49 100644
--- a/java/com/android/bubble/Bubble.java
+++ b/java/com/android/bubble/Bubble.java
@@ -39,6 +39,9 @@ public interface Bubble {
/** Returns whether the bubble is currently visible */
boolean isVisible();
+ /** Returns whether the bubble is currently dismissed */
+ boolean isDismissed();
+
/**
* Set the info for this Bubble to display
*
diff --git a/java/com/android/bubble/stub/BubbleStub.java b/java/com/android/bubble/stub/BubbleStub.java
index 267f33f31..2aa55a337 100644
--- a/java/com/android/bubble/stub/BubbleStub.java
+++ b/java/com/android/bubble/stub/BubbleStub.java
@@ -40,6 +40,11 @@ public class BubbleStub implements Bubble {
}
@Override
+ public boolean isDismissed() {
+ return false;
+ }
+
+ @Override
public void setBubbleInfo(@NonNull BubbleInfo bubbleInfo) {}
@Override
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 9057cd9f9..c819fecba 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -1130,7 +1130,7 @@ public class DialtactsActivity extends TransactionSafeActivity
NewSearchFragment fragment = (NewSearchFragment) getFragmentManager().findFragmentByTag(tag);
if (fragment == null) {
- fragment = NewSearchFragment.newInstance(!isDialpadShown());
+ fragment = NewSearchFragment.newInstance();
transaction.add(R.id.dialtacts_frame, fragment, tag);
} else {
transaction.show(fragment);
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
index 10e30ff72..d84bd425b 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
@@ -150,12 +150,12 @@ public class CallLogNotificationsService extends IntentService {
LogUtil.i("CallLogNotificationsService.onHandleIntent", "action: " + action);
switch (action) {
case ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD:
- VoicemailQueryHandler.markAllNewVoicemailsAsRead(this);
+ VoicemailQueryHandler.markAllNewVoicemailsAsOld(this);
VisualVoicemailNotifier.cancelAllVoicemailNotifications(this);
break;
case ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD:
Uri voicemailUri = intent.getData();
- VoicemailQueryHandler.markSingleNewVoicemailAsRead(this, voicemailUri);
+ VoicemailQueryHandler.markSingleNewVoicemailAsOld(this, voicemailUri);
VisualVoicemailNotifier.cancelSingleVoicemailNotification(this, voicemailUri);
break;
case ACTION_LEGACY_VOICEMAIL_DISMISSED:
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
index 096488a39..680424a78 100644
--- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
+++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
@@ -356,6 +356,9 @@ public class PhoneCallDetailsHelper
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int button) {
+ VoicemailComponent.get(context)
+ .getVoicemailClient()
+ .setVoicemailDonationEnabled(context, details.accountHandle, false);
dialog.cancel();
recordPromoShown(context);
ratingView.setVisibility(View.GONE);
diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
index b353b3abc..bae30fa7b 100644
--- a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
+++ b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
@@ -212,6 +212,10 @@ class VisualVoicemailUpdateTask implements Worker<VisualVoicemailUpdateTask.Inpu
"found voicemail from spam number, suppressing notification");
Logger.get(context)
.logImpression(DialerImpression.Type.INCOMING_VOICEMAIL_AUTO_BLOCKED_AS_SPAM);
+ if (newCall.voicemailUri != null) {
+ // Mark auto blocked voicemail as old so that we don't process it again.
+ VoicemailQueryHandler.markSingleNewVoicemailAsOld(context, newCall.voicemailUri);
+ }
} else {
result.add(newCall);
}
diff --git a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
index 169d0fd35..5d8144ca9 100644
--- a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
+++ b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
@@ -42,7 +42,7 @@ public class VoicemailQueryHandler extends AsyncQueryHandler {
}
@WorkerThread
- public static void markAllNewVoicemailsAsRead(final @NonNull Context context) {
+ public static void markAllNewVoicemailsAsOld(final @NonNull Context context) {
ThreadUtil.postOnUiThread(
() -> {
new VoicemailQueryHandler(context.getContentResolver())
@@ -51,10 +51,10 @@ public class VoicemailQueryHandler extends AsyncQueryHandler {
}
@WorkerThread
- public static void markSingleNewVoicemailAsRead(
+ public static void markSingleNewVoicemailAsOld(
final @NonNull Context context, final Uri voicemailUri) {
if (voicemailUri == null) {
- LogUtil.e("VoicemailQueryHandler.markSingleNewVoicemailAsRead", "voicemail URI is null");
+ LogUtil.e("VoicemailQueryHandler.markSingleNewVoicemailAsOld", "voicemail URI is null");
return;
}
ThreadUtil.postOnUiThread(
diff --git a/java/com/android/dialer/blocking/BlockNumberDialogFragment.java b/java/com/android/dialer/blocking/BlockNumberDialogFragment.java
index 621287f6c..de974cbec 100644
--- a/java/com/android/dialer/blocking/BlockNumberDialogFragment.java
+++ b/java/com/android/dialer/blocking/BlockNumberDialogFragment.java
@@ -41,6 +41,7 @@ import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
* Fragment for confirming and enacting blocking/unblocking a number. Also invokes snackbar
* providing undo functionality.
*/
+@Deprecated
public class BlockNumberDialogFragment extends DialogFragment {
private static final String BLOCK_DIALOG_FRAGMENT = "BlockNumberDialog";
diff --git a/java/com/android/dialer/blocking/BlockedNumbersAutoMigrator.java b/java/com/android/dialer/blocking/BlockedNumbersAutoMigrator.java
index 6e9fe1315..8a57f29e7 100644
--- a/java/com/android/dialer/blocking/BlockedNumbersAutoMigrator.java
+++ b/java/com/android/dialer/blocking/BlockedNumbersAutoMigrator.java
@@ -33,6 +33,7 @@ import com.android.dialer.common.concurrent.DialerExecutorFactory;
* android.provider.BlockedNumberContract} blocking. In order for this to happen, the user cannot
* have any numbers that are blocked in the Dialer solution.
*/
+@Deprecated
public class BlockedNumbersAutoMigrator {
static final String HAS_CHECKED_AUTO_MIGRATE_KEY = "checkedAutoMigrate";
diff --git a/java/com/android/dialer/blocking/BlockedNumbersMigrator.java b/java/com/android/dialer/blocking/BlockedNumbersMigrator.java
index 61ebf2f56..101a04b2f 100644
--- a/java/com/android/dialer/blocking/BlockedNumbersMigrator.java
+++ b/java/com/android/dialer/blocking/BlockedNumbersMigrator.java
@@ -36,6 +36,7 @@ import java.util.Objects;
* {@link android.provider.BlockedNumberContract} blocking.
*/
@TargetApi(VERSION_CODES.N)
+@Deprecated
public class BlockedNumbersMigrator {
private final Context context;
diff --git a/java/com/android/dialer/blocking/Blocking.java b/java/com/android/dialer/blocking/Blocking.java
new file mode 100644
index 000000000..e86d0a6ac
--- /dev/null
+++ b/java/com/android/dialer/blocking/Blocking.java
@@ -0,0 +1,117 @@
+/*
+ * 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.blocking;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.BlockedNumberContract.BlockedNumbers;
+import android.support.annotation.Nullable;
+import android.telephony.PhoneNumberUtils;
+import com.android.dialer.common.database.Selection;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/** Blocks and unblocks number. */
+public final class Blocking {
+
+ private Blocking() {}
+
+ /**
+ * Thrown when blocking cannot be performed because dialer is not the default dialer, or the
+ * current user is not a primary user.
+ *
+ * <p>Blocking is only allowed on the primary user (the first user added). Primary user cannot be
+ * easily checked because {@link
+ * android.provider.BlockedNumberContract#canCurrentUserBlockNumbers(Context)} is a slow IPC, and
+ * UserManager.isPrimaryUser() is a system API. Since secondary users are rare cases this class
+ * choose to ignore the check and let callers handle the failure later.
+ */
+ public static final class BlockingFailedException extends Exception {
+ BlockingFailedException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Block a number.
+ *
+ * @param countryIso the current location used to guess the country code of the number if not
+ * available. If {@code null} and {@code number} does not have a country code, only the
+ * original number will be blocked.
+ * @throws BlockingFailedException in the returned future if the operation failed.
+ */
+ public static ListenableFuture<Void> block(
+ Context context,
+ ListeningExecutorService executorService,
+ String number,
+ @Nullable String countryIso) {
+ return executorService.submit(
+ () -> {
+ ContentValues values = new ContentValues();
+ values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number);
+ String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+ if (e164Number != null) {
+ values.put(BlockedNumbers.COLUMN_E164_NUMBER, e164Number);
+ }
+ try {
+ context.getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
+ } catch (SecurityException e) {
+ throw new BlockingFailedException(e);
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Unblock a number.
+ *
+ * @param countryIso the current location used to guess the country code of the number if not
+ * available. If {@code null} and {@code number} does not have a country code, only the
+ * original number will be unblocked.
+ * @throws BlockingFailedException in the returned future if the operation failed.
+ */
+ public static ListenableFuture<Void> unblock(
+ Context context,
+ ListeningExecutorService executorService,
+ String number,
+ @Nullable String countryIso) {
+ return executorService.submit(
+ () -> {
+ Selection selection =
+ Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER).is("=", number);
+ String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+ if (e164Number != null) {
+ selection =
+ selection
+ .buildUpon()
+ .or(Selection.column(BlockedNumbers.COLUMN_E164_NUMBER).is("=", e164Number))
+ .build();
+ }
+ try {
+ context
+ .getContentResolver()
+ .delete(
+ BlockedNumbers.CONTENT_URI,
+ selection.getSelection(),
+ selection.getSelectionArgs());
+ } catch (SecurityException e) {
+ throw new BlockingFailedException(e);
+ }
+ return null;
+ });
+ }
+}
diff --git a/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java b/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java
index 8be479c99..b41759259 100644
--- a/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java
+++ b/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java
@@ -38,6 +38,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** TODO(calderwoodra): documentation */
+@Deprecated
public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler {
public static final int INVALID_ID = -1;
diff --git a/java/com/android/dialer/blocking/FilteredNumberCompat.java b/java/com/android/dialer/blocking/FilteredNumberCompat.java
index b0af45c97..d263d212c 100644
--- a/java/com/android/dialer/blocking/FilteredNumberCompat.java
+++ b/java/com/android/dialer/blocking/FilteredNumberCompat.java
@@ -48,6 +48,7 @@ import java.util.Objects;
* referencing columns from either contract class in situations where both blocking solutions may be
* used.
*/
+@Deprecated
public class FilteredNumberCompat {
private static Boolean canAttemptBlockOperationsForTest;
diff --git a/java/com/android/dialer/blocking/FilteredNumberProvider.java b/java/com/android/dialer/blocking/FilteredNumberProvider.java
index 3fad4e24f..547892b41 100644
--- a/java/com/android/dialer/blocking/FilteredNumberProvider.java
+++ b/java/com/android/dialer/blocking/FilteredNumberProvider.java
@@ -34,6 +34,7 @@ import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
import com.android.dialer.location.GeoUtil;
/** Filtered number content provider. */
+@Deprecated
public class FilteredNumberProvider extends ContentProvider {
private static final int FILTERED_NUMBERS_TABLE = 1;
diff --git a/java/com/android/dialer/blocking/FilteredNumbersUtil.java b/java/com/android/dialer/blocking/FilteredNumbersUtil.java
index 6433355fd..d839ef5da 100644
--- a/java/com/android/dialer/blocking/FilteredNumbersUtil.java
+++ b/java/com/android/dialer/blocking/FilteredNumbersUtil.java
@@ -42,6 +42,7 @@ import com.android.dialer.util.PermissionsUtil;
import java.util.concurrent.TimeUnit;
/** Utility to help with tasks related to filtered numbers. */
+@Deprecated
public class FilteredNumbersUtil {
public static final String CALL_BLOCKING_NOTIFICATION_TAG = "call_blocking";
diff --git a/java/com/android/dialer/blocking/MigrateBlockedNumbersDialogFragment.java b/java/com/android/dialer/blocking/MigrateBlockedNumbersDialogFragment.java
index 9b416ff5e..9a3b647d2 100644
--- a/java/com/android/dialer/blocking/MigrateBlockedNumbersDialogFragment.java
+++ b/java/com/android/dialer/blocking/MigrateBlockedNumbersDialogFragment.java
@@ -30,6 +30,7 @@ import java.util.Objects;
* Dialog fragment shown to users when they need to migrate to use {@link
* android.provider.BlockedNumberContract} for blocking.
*/
+@Deprecated
public class MigrateBlockedNumbersDialogFragment extends DialogFragment {
private BlockedNumbersMigrator blockedNumbersMigrator;
diff --git a/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java b/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java
index cc307b6b9..fd26ab537 100644
--- a/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java
+++ b/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java
@@ -21,8 +21,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.support.annotation.Nullable;
-import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
+import android.widget.Toast;
+import com.android.dialer.blocking.Blocking;
+import com.android.dialer.blocking.Blocking.BlockingFailedException;
import com.android.dialer.blockreportspam.BlockReportSpamDialogs.DialogFragmentForBlockingNumber;
import com.android.dialer.blockreportspam.BlockReportSpamDialogs.DialogFragmentForBlockingNumberAndOptionallyReportingAsSpam;
import com.android.dialer.blockreportspam.BlockReportSpamDialogs.DialogFragmentForReportingNotSpam;
@@ -31,15 +32,16 @@ import com.android.dialer.blockreportspam.BlockReportSpamDialogs.OnConfirmListen
import com.android.dialer.blockreportspam.BlockReportSpamDialogs.OnSpamDialogClickListener;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutor.Worker;
import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.DialerImpression.Type;
import com.android.dialer.logging.Logger;
import com.android.dialer.protos.ProtoParsers;
import com.android.dialer.spam.Spam;
import com.android.dialer.spam.SpamComponent;
import com.android.dialer.spam.SpamSettings;
-import com.google.auto.value.AutoValue;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
/**
* A {@link BroadcastReceiver} that shows an appropriate dialog upon receiving notifications from
@@ -106,8 +108,6 @@ public final class ShowBlockReportSpamDialogReceiver extends BroadcastReceiver {
Spam spam = SpamComponent.get(context).spam();
SpamSettings spamSettings = SpamComponent.get(context).spamSettings();
- FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler =
- new FilteredNumberAsyncQueryHandler(context);
// Set up the positive listener for the dialog.
OnSpamDialogClickListener onSpamDialogClickListener =
@@ -132,12 +132,7 @@ public final class ShowBlockReportSpamDialogReceiver extends BroadcastReceiver {
dialogInfo.getContactSource());
}
- filteredNumberAsyncQueryHandler.blockNumber(
- unused ->
- Logger.get(context)
- .logImpression(DialerImpression.Type.USER_ACTION_BLOCKED_NUMBER),
- dialogInfo.getNormalizedNumber(),
- dialogInfo.getCountryIso());
+ blockNumber(context, dialogInfo);
};
// Create and show the dialog.
@@ -157,19 +152,11 @@ public final class ShowBlockReportSpamDialogReceiver extends BroadcastReceiver {
ProtoParsers.getTrusted(
intent, EXTRA_DIALOG_INFO, BlockReportSpamDialogInfo.getDefaultInstance());
- FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler =
- new FilteredNumberAsyncQueryHandler(context);
-
// Set up the positive listener for the dialog.
OnConfirmListener onConfirmListener =
() -> {
LogUtil.i("ShowBlockReportSpamDialogReceiver.showDialogToBlockNumber", "block number");
- filteredNumberAsyncQueryHandler.blockNumber(
- unused ->
- Logger.get(context)
- .logImpression(DialerImpression.Type.USER_ACTION_BLOCKED_NUMBER),
- dialogInfo.getNormalizedNumber(),
- dialogInfo.getCountryIso());
+ blockNumber(context, dialogInfo);
};
// Create and show the dialog.
@@ -219,46 +206,12 @@ public final class ShowBlockReportSpamDialogReceiver extends BroadcastReceiver {
ProtoParsers.getTrusted(
intent, EXTRA_DIALOG_INFO, BlockReportSpamDialogInfo.getDefaultInstance());
- FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler =
- new FilteredNumberAsyncQueryHandler(context);
-
// Set up the positive listener for the dialog.
OnConfirmListener onConfirmListener =
() -> {
LogUtil.i("ShowBlockReportSpamDialogReceiver.showDialogToUnblockNumber", "confirmed");
- DialerExecutorComponent.get(context)
- .dialerExecutorFactory()
- .createNonUiTaskBuilder(
- new GetIdForBlockedNumberWorker(filteredNumberAsyncQueryHandler))
- .onSuccess(
- idForBlockedNumber -> {
- LogUtil.i(
- "ShowBlockReportSpamDialogReceiver.showDialogToUnblockNumber",
- "ID for the blocked number retrieved");
- if (idForBlockedNumber == null) {
- throw new IllegalStateException("ID for a blocked number is null.");
- }
-
- LogUtil.i(
- "ShowBlockReportSpamDialogReceiver.showDialogToUnblockNumber",
- "unblocking number");
- filteredNumberAsyncQueryHandler.unblock(
- (rows, values) ->
- Logger.get(context)
- .logImpression(DialerImpression.Type.USER_ACTION_UNBLOCKED_NUMBER),
- idForBlockedNumber);
- })
- .onFailure(
- throwable -> {
- throw new RuntimeException(throwable);
- })
- .build()
- .executeSerial(
- NumberInfo.newBuilder()
- .setNormalizedNumber(dialogInfo.getNormalizedNumber())
- .setCountryIso(dialogInfo.getCountryIso())
- .build());
+ unblockNumber(context, dialogInfo);
};
// Create & show the dialog.
@@ -267,46 +220,58 @@ public final class ShowBlockReportSpamDialogReceiver extends BroadcastReceiver {
.show(fragmentManager, BlockReportSpamDialogs.UNBLOCK_DIALOG_TAG);
}
- /** A {@link Worker} that retrieves the ID of a blocked number from the database. */
- private static final class GetIdForBlockedNumberWorker implements Worker<NumberInfo, Integer> {
-
- private final FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler;
-
- GetIdForBlockedNumberWorker(FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler) {
- this.filteredNumberAsyncQueryHandler = filteredNumberAsyncQueryHandler;
- }
-
- @Nullable
- @Override
- public Integer doInBackground(NumberInfo input) throws Throwable {
- LogUtil.enterBlock("GetIdForBlockedNumberWorker.doInBackground");
+ private static void blockNumber(Context context, BlockReportSpamDialogInfo dialogInfo) {
+ Logger.get(context).logImpression(Type.USER_ACTION_BLOCKED_NUMBER);
+ Futures.addCallback(
+ Blocking.block(
+ context,
+ DialerExecutorComponent.get(context).backgroundExecutor(),
+ dialogInfo.getNormalizedNumber(),
+ dialogInfo.getCountryIso()),
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void unused) {
+ // Do nothing
+ }
- return filteredNumberAsyncQueryHandler.getBlockedIdSynchronous(
- input.getNormalizedNumber(), input.getCountryIso());
- }
+ @Override
+ public void onFailure(Throwable throwable) {
+ if (throwable instanceof BlockingFailedException) {
+ Logger.get(context).logImpression(Type.USER_ACTION_BLOCK_NUMBER_FAILED);
+ Toast.makeText(context, R.string.block_number_failed_toast, Toast.LENGTH_LONG).show();
+ } else {
+ throw new RuntimeException(throwable);
+ }
+ }
+ },
+ DialerExecutorComponent.get(context).uiExecutor());
}
- /**
- * Contains information about a number and serves as the input to {@link
- * GetIdForBlockedNumberWorker}.
- */
- @AutoValue
- abstract static class NumberInfo {
- static Builder newBuilder() {
- return new AutoValue_ShowBlockReportSpamDialogReceiver_NumberInfo.Builder();
- }
-
- abstract String getNormalizedNumber();
-
- abstract String getCountryIso();
-
- @AutoValue.Builder
- abstract static class Builder {
- abstract Builder setNormalizedNumber(String normalizedNumber);
-
- abstract Builder setCountryIso(String countryIso);
+ private static void unblockNumber(Context context, BlockReportSpamDialogInfo dialogInfo) {
+ Logger.get(context).logImpression(Type.USER_ACTION_UNBLOCKED_NUMBER);
+ Futures.addCallback(
+ Blocking.unblock(
+ context,
+ DialerExecutorComponent.get(context).backgroundExecutor(),
+ dialogInfo.getNormalizedNumber(),
+ dialogInfo.getCountryIso()),
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void unused) {
+ // Do nothing
+ }
- abstract NumberInfo build();
- }
+ @Override
+ public void onFailure(Throwable throwable) {
+ if (throwable instanceof BlockingFailedException) {
+ Logger.get(context).logImpression(Type.USER_ACTION_UNBLOCK_NUMBER_FAILED);
+ Toast.makeText(context, R.string.unblock_number_failed_toast, Toast.LENGTH_LONG)
+ .show();
+ } else {
+ throw new RuntimeException(throwable);
+ }
+ }
+ },
+ DialerExecutorComponent.get(context).uiExecutor());
}
}
diff --git a/java/com/android/dialer/blockreportspam/res/values/strings.xml b/java/com/android/dialer/blockreportspam/res/values/strings.xml
index aface9268..1537c2353 100644
--- a/java/com/android/dialer/blockreportspam/res/values/strings.xml
+++ b/java/com/android/dialer/blockreportspam/res/values/strings.xml
@@ -60,4 +60,9 @@
<!-- Label for checkbox in the Alert dialog to allow the user to report the number as spam as well. [CHAR LIMIT=30] -->
<string name="checkbox_report_as_spam_action">Report call as spam</string>
+ <!-- Toast if the user clicked the block button but it failed. [CHAR LIMIT=100] -->
+ <string name="block_number_failed_toast">Problem blocking this number</string>
+
+ <!-- Toast if the user clicked the unblock button but it failed. [CHAR LIMIT=100] -->
+ <string name="unblock_number_failed_toast">Problem unblocking this number</string>
</resources>
diff --git a/java/com/android/dialer/calllog/ClearMissedCalls.java b/java/com/android/dialer/calllog/ClearMissedCalls.java
index d216e7b88..78eb80294 100644
--- a/java/com/android/dialer/calllog/ClearMissedCalls.java
+++ b/java/com/android/dialer/calllog/ClearMissedCalls.java
@@ -38,8 +38,8 @@ import java.util.Collection;
import javax.inject.Inject;
/**
- * Clears missed calls. This includes cancelling notifications and updating the "NEW" status in the
- * system call log.
+ * Clears missed calls. This includes cancelling notifications and updating the "IS_READ" status in
+ * the system call log.
*/
public final class ClearMissedCalls {
@@ -58,11 +58,11 @@ public final class ClearMissedCalls {
}
/**
- * Cancels all missed call notifications and marks all "new" missed calls in the system call log
- * as "not new".
+ * Cancels all missed call notifications and marks all "unread" missed calls in the system call
+ * log as "read".
*/
public ListenableFuture<Void> clearAll() {
- ListenableFuture<Void> markNewFuture = markNotNew(ImmutableSet.of());
+ ListenableFuture<Void> markReadFuture = markRead(ImmutableSet.of());
ListenableFuture<Void> cancelNotificationsFuture =
uiThreadExecutor.submit(
() -> {
@@ -73,11 +73,11 @@ public final class ClearMissedCalls {
// Note on this usage of whenAllComplete:
// -The returned future completes when all sub-futures complete (whether they fail or not)
// -The returned future fails if any sub-future fails
- return Futures.whenAllComplete(markNewFuture, cancelNotificationsFuture)
+ return Futures.whenAllComplete(markReadFuture, cancelNotificationsFuture)
.call(
() -> {
// Calling get() is necessary to propagate failures.
- markNewFuture.get();
+ markReadFuture.get();
cancelNotificationsFuture.get();
return null;
},
@@ -86,12 +86,12 @@ public final class ClearMissedCalls {
/**
* For the provided set of IDs from the system call log, cancels their missed call notifications
- * and marks them "not new".
+ * and marks them "read".
*
* @param ids IDs from the system call log (see {@link Calls#_ID}}.
*/
public ListenableFuture<Void> clearBySystemCallLogId(Collection<Long> ids) {
- ListenableFuture<Void> markNewFuture = markNotNew(ids);
+ ListenableFuture<Void> markReadFuture = markRead(ids);
ListenableFuture<Void> cancelNotificationsFuture =
uiThreadExecutor.submit(
() -> {
@@ -105,11 +105,11 @@ public final class ClearMissedCalls {
// Note on this usage of whenAllComplete:
// -The returned future completes when all sub-futures complete (whether they fail or not)
// -The returned future fails if any sub-future fails
- return Futures.whenAllComplete(markNewFuture, cancelNotificationsFuture)
+ return Futures.whenAllComplete(markReadFuture, cancelNotificationsFuture)
.call(
() -> {
// Calling get() is necessary to propagate failures.
- markNewFuture.get();
+ markReadFuture.get();
cancelNotificationsFuture.get();
return null;
},
@@ -117,28 +117,28 @@ public final class ClearMissedCalls {
}
/**
- * Marks all provided system call log IDs as not new, or if the provided collection is empty,
- * marks all calls as not new.
+ * Marks all provided system call log IDs as read, or if the provided collection is empty, marks
+ * all calls as read.
*/
@SuppressLint("MissingPermission")
- private ListenableFuture<Void> markNotNew(Collection<Long> ids) {
+ private ListenableFuture<Void> markRead(Collection<Long> ids) {
return backgroundExecutor.submit(
() -> {
if (!UserManagerCompat.isUserUnlocked(appContext)) {
- LogUtil.e("ClearMissedCalls.markNotNew", "locked");
+ LogUtil.e("ClearMissedCalls.markRead", "locked");
return null;
}
if (!PermissionsUtil.hasCallLogWritePermissions(appContext)) {
- LogUtil.e("ClearMissedCalls.markNotNew", "no permission");
+ LogUtil.e("ClearMissedCalls.markRead", "no permission");
return null;
}
ContentValues values = new ContentValues();
- values.put(Calls.NEW, 0);
+ values.put(Calls.IS_READ, 1);
Selection.Builder selectionBuilder =
Selection.builder()
- .and(Selection.column(Calls.NEW).is("=", 1))
+ .and(Selection.column(Calls.IS_READ).is("=", 0))
.and(Selection.column(Calls.TYPE).is("=", Calls.MISSED_TYPE));
if (!ids.isEmpty()) {
selectionBuilder.and(Selection.column(Calls._ID).in(toStrings(ids)));
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index 8362a81ac..aa4260cba 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -225,6 +225,7 @@ public class SystemCallLogDataSource implements CallLogDataSource {
return new RowCombiner(individualRowsSortedByTimestampDesc)
.useMostRecentLong(AnnotatedCallLog.TIMESTAMP)
.useMostRecentLong(AnnotatedCallLog.NEW)
+ .useMostRecentLong(AnnotatedCallLog.IS_READ)
// Two different DialerPhoneNumbers could be combined if they are different but considered
// to be an "exact match" by libphonenumber; in this case we arbitrarily select the most
// recent one.
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
index 05a339978..839ba332f 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
@@ -25,6 +25,7 @@ import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.dialer.calllogutils.CallLogDates;
import com.android.dialer.common.Assert;
+import com.android.dialer.logging.Logger;
import com.android.dialer.time.Clock;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,6 +54,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
private final Clock clock;
private final RealtimeRowProcessor realtimeRowProcessor;
+ private final PopCounts popCounts = new PopCounts();
private Cursor cursor;
@@ -76,6 +78,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
void updateCursor(Cursor updatedCursor) {
this.cursor = updatedCursor;
this.realtimeRowProcessor.clearCache();
+ this.popCounts.reset();
setHeaderPositions();
notifyDataSetChanged();
@@ -85,6 +88,10 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
this.realtimeRowProcessor.clearCache();
}
+ void logMetrics(Context context) {
+ Logger.get(context).logAnnotatedCallLogMetrics(popCounts.popped, popCounts.didNotPop);
+ }
+
private void setHeaderPositions() {
// If there are no rows to display, set all header positions to null.
if (!cursor.moveToFirst()) {
@@ -138,7 +145,8 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.new_call_log_entry, viewGroup, false),
clock,
- realtimeRowProcessor);
+ realtimeRowProcessor,
+ popCounts);
default:
throw Assert.createUnsupportedOperationFailException("Unsupported view type: " + viewType);
}
@@ -207,4 +215,14 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
}
return cursor.getCount() + numberOfHeaders;
}
+
+ static class PopCounts {
+ int popped;
+ int didNotPop;
+
+ private void reset() {
+ popped = 0;
+ didNotPop = 0;
+ }
+ }
}
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java
index bb1a7303e..0f1c2510a 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java
@@ -89,6 +89,15 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback
}
@Override
+ public void onStop() {
+ super.onStop();
+
+ if (recyclerView.getAdapter() != null) {
+ ((NewCallLogAdapter) recyclerView.getAdapter()).logMetrics(getContext());
+ }
+ }
+
+ @Override
public void onPause() {
super.onPause();
LogUtil.enterBlock("NewCallLogFragment.onPause");
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index f322b562b..217208d17 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -27,6 +27,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.calllog.ui.NewCallLogAdapter.PopCounts;
import com.android.dialer.calllog.ui.menu.NewCallLogMenu;
import com.android.dialer.calllogutils.CallLogEntryText;
import com.android.dialer.calllogutils.CallLogIntents;
@@ -60,10 +61,12 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
private final Clock clock;
private final RealtimeRowProcessor realtimeRowProcessor;
private final ExecutorService uiExecutorService;
+ private final PopCounts popCounts;
private long currentRowId;
- NewCallLogViewHolder(View view, Clock clock, RealtimeRowProcessor realtimeRowProcessor) {
+ NewCallLogViewHolder(
+ View view, Clock clock, RealtimeRowProcessor realtimeRowProcessor, PopCounts popCounts) {
super(view);
this.context = view.getContext();
contactPhotoView = view.findViewById(R.id.contact_photo_view);
@@ -79,6 +82,7 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
this.clock = clock;
this.realtimeRowProcessor = realtimeRowProcessor;
+ this.popCounts = popCounts;
uiExecutorService = DialerExecutorComponent.get(context).uiExecutor();
}
@@ -105,11 +109,11 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
primaryTextView.setText(CallLogEntryText.buildPrimaryText(context, row));
secondaryTextView.setText(CallLogEntryText.buildSecondaryTextForEntries(context, clock, row));
- if (isNewMissedCall(row)) {
- primaryTextView.setTextAppearance(R.style.primary_textview_new_call);
- callCountTextView.setTextAppearance(R.style.primary_textview_new_call);
- secondaryTextView.setTextAppearance(R.style.secondary_textview_new_call);
- phoneAccountView.setTextAppearance(R.style.phoneaccount_textview_new_call);
+ if (isUnreadMissedCall(row)) {
+ primaryTextView.setTextAppearance(R.style.primary_textview_unread_call);
+ callCountTextView.setTextAppearance(R.style.primary_textview_unread_call);
+ secondaryTextView.setTextAppearance(R.style.secondary_textview_unread_call);
+ phoneAccountView.setTextAppearance(R.style.phoneaccount_textview_unread_call);
} else {
primaryTextView.setTextAppearance(R.style.primary_textview);
callCountTextView.setTextAppearance(R.style.primary_textview);
@@ -136,10 +140,11 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
}
}
- private boolean isNewMissedCall(CoalescedRow row) {
+ private boolean isUnreadMissedCall(CoalescedRow row) {
// Show missed call styling if the most recent call in the group was missed and it is still
- // marked as NEW. It is not clear what IS_READ should be used for and it is currently not used.
- return row.getCallType() == Calls.MISSED_TYPE && row.getIsNew();
+ // marked as not read. The "NEW" column is presumably used for notifications and voicemails
+ // only.
+ return row.getCallType() == Calls.MISSED_TYPE && !row.getIsRead();
}
private void setPhoto(CoalescedRow row) {
@@ -155,7 +160,7 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
ColorStateList colorStateList =
ColorStateList.valueOf(
context.getColor(
- isNewMissedCall(row)
+ isUnreadMissedCall(row)
? R.color.feature_icon_unread_color
: R.color.feature_icon_read_color));
@@ -213,7 +218,7 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
}
callTypeIcon.setImageResource(resId);
- if (isNewMissedCall(row)) {
+ if (isUnreadMissedCall(row)) {
callTypeIcon.setImageTintList(
ColorStateList.valueOf(context.getColor(R.color.call_type_icon_unread_color)));
} else {
@@ -258,13 +263,17 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
// If the user scrolled then this ViewHolder may not correspond to the completed task and
// there's nothing to do.
if (originalRow.getId() != currentRowId) {
+ popCounts.didNotPop++;
return;
}
// Only update the UI if the updated row differs from the original row (which has already
// been displayed).
if (!updatedRow.equals(originalRow)) {
displayRow(updatedRow);
+ popCounts.popped++;
+ return;
}
+ popCounts.didNotPop++;
}
@Override
diff --git a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
index b955e029b..c5148d93e 100644
--- a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
+++ b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
@@ -198,8 +198,14 @@ public final class RealtimeRowProcessor {
private CoalescedRow applyPhoneLookupInfoToRow(
PhoneLookupInfo phoneLookupInfo, CoalescedRow row) {
+ // Force the "cp2_info_incomplete" value to the original value so that it is not used when
+ // comparing the original row to the updated row.
+ // TODO(linyuh): Improve the comparison instead.
return row.toBuilder()
- .setNumberAttributes(NumberAttributesConverter.fromPhoneLookupInfo(phoneLookupInfo).build())
+ .setNumberAttributes(
+ NumberAttributesConverter.fromPhoneLookupInfo(phoneLookupInfo)
+ .setIsCp2InfoIncomplete(row.getNumberAttributes().getIsCp2InfoIncomplete())
+ .build())
.build();
}
}
diff --git a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
index dabb9bbe4..3869e78c3 100644
--- a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
+++ b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
@@ -35,9 +35,9 @@ public final class NewCallLogMenu {
HistoryItemActionBottomSheet.show(
context, BottomSheetHeader.fromRow(context, row), Modules.fromRow(context, row));
- // If the user opens the bottom sheet for a new call, clear the notifications and make the row
- // not bold immediately. To do this, mark all of the calls in group as not new.
- if (row.getIsNew() && row.getCallType() == Calls.MISSED_TYPE) {
+ // If the user opens the bottom sheet for an unread call, clear the notifications and make the
+ // row not bold immediately. To do this, mark all of the calls in group as read.
+ if (!row.getIsRead() && row.getCallType() == Calls.MISSED_TYPE) {
Futures.addCallback(
CallLogComponent.get(context)
.getClearMissedCalls()
diff --git a/java/com/android/dialer/calllog/ui/res/values/styles.xml b/java/com/android/dialer/calllog/ui/res/values/styles.xml
index d521feed4..047f1dace 100644
--- a/java/com/android/dialer/calllog/ui/res/values/styles.xml
+++ b/java/com/android/dialer/calllog/ui/res/values/styles.xml
@@ -21,7 +21,7 @@
<item name="android:fontFamily">sans-serif</item>
</style>
- <style name="primary_textview_new_call">
+ <style name="primary_textview_unread_call">
<item name="android:textColor">@color/primary_text_color</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
@@ -35,12 +35,12 @@
<item name="android:fontFamily">sans-serif</item>
</style>
- <style name="secondary_textview_new_call">
+ <style name="secondary_textview_unread_call">
<item name="android:textColor">@color/missed_call</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
- <style name="phoneaccount_textview_new_call">
+ <style name="phoneaccount_textview_unread_call">
<item name="android:fontFamily">sans-serif-medium</item>
</style>
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index c77869169..e346de011 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.provider.CallLog.Calls;
import android.text.TextUtils;
import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.duo.DuoConstants;
import com.android.dialer.time.Clock;
import com.google.common.base.Optional;
import com.google.common.collect.Collections2;
@@ -72,20 +73,22 @@ public final class CallLogEntryText {
* <p>Rules:
*
* <ul>
- * <li>For numbers that are not spam or blocked: (Duo video, )?$Label|$Location • Date
- * <li>For blocked non-spam numbers: Blocked • (Duo video, )?$Label|$Location • Date
- * <li>For spam but not blocked numbers: Spam • (Duo video, )?$Label • Date
- * <li>For blocked spam numbers: Blocked • Spam • (Duo video, )?$Label • 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 •
+ * Date
+ * <li>For spam but not blocked numbers: Spam • $Label(, Duo video|Carrier video)? • Date
+ * <li>For blocked spam numbers: Blocked • Spam • $Label(, Duo video|Carrier video)? • Date
* </ul>
*
* <p>Examples:
*
* <ul>
- * <li>Duo Video, Mobile • Now
- * <li>Duo Video • 10 min ago
+ * <li>Mobile, Duo video • Now
+ * <li>Duo video • 10 min ago
* <li>Mobile • 11:45 PM
* <li>Mobile • Sun
- * <li>Blocked • Duo Video, Mobile • Now
+ * <li>Blocked • Mobile, Duo video • Now
* <li>Blocked • Brooklyn, NJ • 10 min ago
* <li>Spam • Mobile • Now
* <li>Spam • Now
@@ -125,20 +128,20 @@ public final class CallLogEntryText {
/*
* Rules:
* For numbers that are not spam or blocked:
- * (Duo video, )?$Label|$Location [• NumberIfNoName]?
+ * $Label(, Duo video|Carrier video)?|$Location [• NumberIfNoName]?
* For blocked non-spam numbers:
- * Blocked • (Duo video, )?$Label|$Location [• NumberIfNoName]?
+ * Blocked • $Label(, Duo video|Carrier video)?|$Location [• NumberIfNoName]?
* For spam but not blocked numbers:
- * Spam • (Duo video, )?$Label [• NumberIfNoName]?
+ * Spam • $Label(, Duo video|Carrier video)? [• NumberIfNoName]?
* For blocked spam numbers:
- * Blocked • Spam • (Duo video, )?$Label [• NumberIfNoName]?
+ * Blocked • Spam • $Label(, Duo video|Carrier video)? [• NumberIfNoName]?
*
* The number is shown at the end if there is no name for the entry. (It is shown in primary
* text otherwise.)
*
* Examples:
- * Duo Video, Mobile • 555-1234
- * Duo Video • 555-1234
+ * Mobile, Duo video • 555-1234
+ * Duo video • 555-1234
* Mobile • 555-1234
* Blocked • Mobile • 555-1234
* Blocked • Brooklyn, NJ • 555-1234
@@ -178,7 +181,7 @@ public final class CallLogEntryText {
}
/**
- * Returns a value such as "Duo Video, Mobile" without the time of the call or formatted number
+ * Returns a value such as "Mobile, Duo video" without the time of the call or formatted number
* appended.
*
* <p>When the secondary text is shown in call log entry list, this prefix is suffixed with the
@@ -187,18 +190,30 @@ public final class CallLogEntryText {
*/
private static CharSequence getNumberTypeLabel(Context context, CoalescedRow row) {
StringBuilder secondaryText = new StringBuilder();
- if ((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
- // TODO(zachh): Add "Duo" prefix?
- secondaryText.append(context.getText(R.string.new_call_log_video));
- }
+
+ // The number type label comes first (e.g., "Mobile", "Work", "Home", etc).
String numberTypeLabel = row.getNumberAttributes().getNumberTypeLabel();
- if (!TextUtils.isEmpty(numberTypeLabel)) {
+ secondaryText.append(numberTypeLabel);
+
+ // Add video call info if applicable.
+ if ((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
if (secondaryText.length() > 0) {
secondaryText.append(", ");
}
- secondaryText.append(numberTypeLabel);
- } else if (!row.getNumberAttributes().getIsSpam()) {
- // Don't show the location if there's a number type label or the number is spam.
+
+ boolean isDuoCall =
+ DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
+ .flattenToString()
+ .equals(row.getPhoneAccountComponentName());
+ secondaryText.append(
+ context.getText(
+ isDuoCall ? R.string.new_call_log_duo_video : R.string.new_call_log_carrier_video));
+ }
+
+ // Show the location if
+ // (1) there is no number type label, and
+ // (2) the number is not spam.
+ if (TextUtils.isEmpty(numberTypeLabel) && !row.getNumberAttributes().getIsSpam()) {
String location = row.getGeocodedLocation();
if (!TextUtils.isEmpty(location)) {
if (secondaryText.length() > 0) {
@@ -207,6 +222,7 @@ public final class CallLogEntryText {
secondaryText.append(location);
}
}
+
return secondaryText;
}
diff --git a/java/com/android/dialer/calllogutils/res/values/strings.xml b/java/com/android/dialer/calllogutils/res/values/strings.xml
index f536ca66d..bc19ce22a 100644
--- a/java/com/android/dialer/calllogutils/res/values/strings.xml
+++ b/java/com/android/dialer/calllogutils/res/values/strings.xml
@@ -131,8 +131,11 @@
<!-- String to be displayed to indicate in the call log that a call just now occurred. -->
<string name="just_now">Just now</string>
- <!-- Text to show in call log for a video call. [CHAR LIMIT=16] -->
- <string name="new_call_log_video">Video</string>
+ <!-- Text to show in call log for a carrier video call. [CHAR LIMIT=30] -->
+ <string name="new_call_log_carrier_video">Carrier video</string>
+
+ <!-- Text to show in call log for a duo video call. [CHAR LIMIT=30] -->
+ <string name="new_call_log_duo_video">Duo video</string>
<!-- String used to display calls from unknown numbers in the call log. [CHAR LIMIT=30] -->
<string name="new_call_log_unknown">Unknown</string>
diff --git a/java/com/android/dialer/commandline/CommandLineModule.java b/java/com/android/dialer/commandline/CommandLineModule.java
index 612155662..915578722 100644
--- a/java/com/android/dialer/commandline/CommandLineModule.java
+++ b/java/com/android/dialer/commandline/CommandLineModule.java
@@ -16,7 +16,7 @@
package com.android.dialer.commandline;
-import com.android.dialer.commandline.impl.Blocking;
+import com.android.dialer.commandline.impl.BlockingCommand;
import com.android.dialer.commandline.impl.CallCommand;
import com.android.dialer.commandline.impl.Echo;
import com.android.dialer.commandline.impl.Help;
@@ -43,16 +43,20 @@ public abstract class CommandLineModule {
private final Help help;
private final Version version;
private final Echo echo;
- private final Blocking blocking;
+ private final BlockingCommand blockingCommand;
private final CallCommand callCommand;
@Inject
AospCommandInjector(
- Help help, Version version, Echo echo, Blocking blocking, CallCommand callCommand) {
+ Help help,
+ Version version,
+ Echo echo,
+ BlockingCommand blockingCommand,
+ CallCommand callCommand) {
this.help = help;
this.version = version;
this.echo = echo;
- this.blocking = blocking;
+ this.blockingCommand = blockingCommand;
this.callCommand = callCommand;
}
@@ -60,7 +64,7 @@ public abstract class CommandLineModule {
builder.addCommand("help", help);
builder.addCommand("version", version);
builder.addCommand("echo", echo);
- builder.addCommand("blocking", blocking);
+ builder.addCommand("blocking", blockingCommand);
builder.addCommand("call", callCommand);
return builder;
}
diff --git a/java/com/android/dialer/commandline/impl/Blocking.java b/java/com/android/dialer/commandline/impl/BlockingCommand.java
index 2afd16522..c8f893422 100644
--- a/java/com/android/dialer/commandline/impl/Blocking.java
+++ b/java/com/android/dialer/commandline/impl/BlockingCommand.java
@@ -18,17 +18,25 @@ package com.android.dialer.commandline.impl;
import android.content.Context;
import android.support.annotation.NonNull;
-import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.blocking.Blocking;
import com.android.dialer.commandline.Arguments;
import com.android.dialer.commandline.Command;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.phonelookup.PhoneLookupComponent;
+import com.android.dialer.phonelookup.PhoneLookupInfo;
+import com.android.dialer.phonelookup.consolidator.PhoneLookupInfoConsolidator;
+import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
import javax.inject.Inject;
/** Block or unblock a number. */
-public class Blocking implements Command {
+public class BlockingCommand implements Command {
@NonNull
@Override
@@ -46,7 +54,7 @@ public class Blocking implements Command {
private final ListeningExecutorService executorService;
@Inject
- Blocking(
+ BlockingCommand(
@ApplicationContext Context context,
@BackgroundExecutor ListeningExecutorService executorService) {
this.appContext = context;
@@ -55,42 +63,49 @@ public class Blocking implements Command {
@Override
public ListenableFuture<String> run(Arguments args) throws IllegalCommandLineArgumentException {
- // AsyncQueryHandler must be created on a thread with looper.
- // TODO(a bug): Use blocking version
- FilteredNumberAsyncQueryHandler asyncQueryHandler =
- new FilteredNumberAsyncQueryHandler(appContext);
- return executorService.submit(() -> doInBackground(args, asyncQueryHandler));
- }
-
- private String doInBackground(Arguments args, FilteredNumberAsyncQueryHandler asyncQueryHandler) {
if (args.getPositionals().isEmpty()) {
- return getUsage();
+ return Futures.immediateFuture(getUsage());
}
String command = args.getPositionals().get(0);
if ("block".equals(command)) {
String number = args.getPositionals().get(1);
- asyncQueryHandler.blockNumber((unused) -> {}, number, null);
- return "blocked " + number;
+ return Futures.transform(
+ Blocking.block(appContext, executorService, number, null),
+ (unused) -> "blocked " + number,
+ MoreExecutors.directExecutor());
}
if ("unblock".equals(command)) {
String number = args.getPositionals().get(1);
- Integer id = asyncQueryHandler.getBlockedIdSynchronous(number, null);
- if (id == null) {
- return number + " is not blocked";
- }
- asyncQueryHandler.unblock((unusedRows, unusedValues) -> {}, id);
- return "unblocked " + number;
+ return Futures.transform(
+ Blocking.unblock(appContext, executorService, number, null),
+ (unused) -> "unblocked " + number,
+ MoreExecutors.directExecutor());
}
if ("isblocked".equals(command)) {
String number = args.getPositionals().get(1);
- Integer id = asyncQueryHandler.getBlockedIdSynchronous(number, null);
- return id == null ? "false" : "true";
+ ListenableFuture<DialerPhoneNumber> dialerPhoneNumberFuture =
+ executorService.submit(
+ () -> new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()).parse(number, null));
+
+ ListenableFuture<PhoneLookupInfo> lookupFuture =
+ Futures.transformAsync(
+ dialerPhoneNumberFuture,
+ (dialerPhoneNumber) ->
+ PhoneLookupComponent.get(appContext)
+ .compositePhoneLookup()
+ .lookup(dialerPhoneNumber),
+ executorService);
+
+ return Futures.transform(
+ lookupFuture,
+ (info) -> new PhoneLookupInfoConsolidator(info).isBlocked() ? "true" : "false",
+ MoreExecutors.directExecutor());
}
- return getUsage();
+ return Futures.immediateFuture(getUsage());
}
}
diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java
index a6795ed1e..7c580cb77 100644
--- a/java/com/android/dialer/logging/LoggingBindings.java
+++ b/java/com/android/dialer/logging/LoggingBindings.java
@@ -90,4 +90,10 @@ public interface LoggingBindings {
/** Logs a call auto-blocked in call screening. */
void logAutoBlockedCall(String phoneNumber);
+
+ /** Logs annotated call log metrics. */
+ void logAnnotatedCallLogMetrics(int invalidNumbersInCallLog);
+
+ /** Logs annotated call log metrics. */
+ void logAnnotatedCallLogMetrics(int numberRowsThatDidPop, int numberRowsThatDidNotPop);
}
diff --git a/java/com/android/dialer/logging/LoggingBindingsStub.java b/java/com/android/dialer/logging/LoggingBindingsStub.java
index de08f4497..65ebd1a52 100644
--- a/java/com/android/dialer/logging/LoggingBindingsStub.java
+++ b/java/com/android/dialer/logging/LoggingBindingsStub.java
@@ -64,4 +64,10 @@ public class LoggingBindingsStub implements LoggingBindings {
@Override
public void logAutoBlockedCall(String phoneNumber) {}
+
+ @Override
+ public void logAnnotatedCallLogMetrics(int invalidNumbersInCallLog) {}
+
+ @Override
+ public void logAnnotatedCallLogMetrics(int numberRowsThatDidPop, int numberRowsThatDidNotPop) {}
}
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index 16efd137c..96a7bb6f6 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -12,7 +12,7 @@ message DialerImpression {
// Event enums to be used for Impression Logging in Dialer.
// It's perfectly acceptable for this enum to be large
// Values should be from 1000 to 100000.
- // Next Tag: 1369
+ // Next Tag: 1371
enum Type {
UNKNOWN_AOSP_EVENT_TYPE = 1000;
@@ -78,9 +78,17 @@ message DialerImpression {
;
+ // User made it to the last step but blocking failed because user is
+ // secondary or dialer is not default
+ USER_ACTION_BLOCK_NUMBER_FAILED = 1369;
+
// User made it to the last step and actually unblocked the number
USER_ACTION_UNBLOCKED_NUMBER = 1013;
+ // User made it to the last step but unblocking failed because user is
+ // secondary or dialer is not default
+ USER_ACTION_UNBLOCK_NUMBER_FAILED = 1370;
+
// User blocked a number, does not guarantee if the number was reported as
// spam or not To compute the number of blocked numbers that were reported
// as not spam and yet blocked Subtract this value from
diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java
index c2ff0512a..7b4bc3569 100644
--- a/java/com/android/dialer/main/impl/MainSearchController.java
+++ b/java/com/android/dialer/main/impl/MainSearchController.java
@@ -149,25 +149,24 @@ public class MainSearchController implements SearchBarListener {
// Show Search
if (searchFragment == null) {
- // TODO(a bug): zero suggest results aren't actually shown but this enabled the nearby
- // places promo to be shown.
- searchFragment = NewSearchFragment.newInstance(/* showZeroSuggest=*/ true);
+ searchFragment = NewSearchFragment.newInstance();
transaction.add(R.id.search_fragment_container, searchFragment, SEARCH_FRAGMENT_TAG);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
} else if (!isSearchVisible()) {
transaction.show(searchFragment);
}
- searchFragment.setQuery("", CallInitiationType.Type.DIALPAD);
// Show Dialpad
if (getDialpadFragment() == null) {
DialpadFragment dialpadFragment = new DialpadFragment();
dialpadFragment.setStartedFromNewIntent(fromNewIntent);
transaction.add(R.id.dialpad_fragment_container, dialpadFragment, DIALPAD_FRAGMENT_TAG);
+ searchFragment.setQuery("", CallInitiationType.Type.DIALPAD);
} else {
DialpadFragment dialpadFragment = getDialpadFragment();
dialpadFragment.setStartedFromNewIntent(fromNewIntent);
transaction.show(dialpadFragment);
+ searchFragment.setQuery(dialpadFragment.getQuery(), CallInitiationType.Type.DIALPAD);
}
transaction.commit();
@@ -258,7 +257,7 @@ public class MainSearchController implements SearchBarListener {
} else {
Logger.get(activity)
.logImpression(DialerImpression.Type.MAIN_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD);
- toolbar.hideKeyboard();
+ closeKeyboard();
}
}
}
@@ -348,6 +347,14 @@ public class MainSearchController implements SearchBarListener {
return isSearchVisible();
}
+ /** Closes the keyboard if necessary. */
+ private void closeKeyboard() {
+ NewSearchFragment fragment = getSearchFragment();
+ if (fragment != null && fragment.isAdded()) {
+ toolbar.hideKeyboard();
+ }
+ }
+
/**
* Opens search in regular/search bar search mode.
*
@@ -376,9 +383,7 @@ public class MainSearchController implements SearchBarListener {
// Show Search
if (searchFragment == null) {
- // TODO(a bug): zero suggest results aren't actually shown but this enabled the nearby
- // places promo to be shown.
- searchFragment = NewSearchFragment.newInstance(true);
+ searchFragment = NewSearchFragment.newInstance();
transaction.add(R.id.search_fragment_container, searchFragment, SEARCH_FRAGMENT_TAG);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
} else if (!isSearchVisible()) {
@@ -446,6 +451,9 @@ public class MainSearchController implements SearchBarListener {
@Override
public void onActivityPause() {
+ LogUtil.enterBlock("MainSearchController.onActivityPause");
+ closeKeyboard();
+
if (closeSearchOnPause) {
closeSearchOnPause = false;
if (isInSearch()) {
@@ -462,7 +470,7 @@ public class MainSearchController implements SearchBarListener {
closeSearchOnPause = !requestingPermission;
// Always hide the keyboard when the user leaves dialer (including permission requests)
- toolbar.hideKeyboard();
+ closeKeyboard();
}
}
@@ -473,6 +481,7 @@ public class MainSearchController implements SearchBarListener {
@Override
public void requestingPermission() {
+ LogUtil.enterBlock("MainSearchController.requestingPermission");
requestingPermission = true;
}
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java
index 902a2fbe3..c5d4e53f2 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2DefaultDirectoryPhoneLookup.java
@@ -33,7 +33,9 @@ import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
+import com.android.dialer.configprovider.ConfigProvider;
import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.logging.Logger;
import com.android.dialer.phonelookup.PhoneLookup;
import com.android.dialer.phonelookup.PhoneLookupInfo;
import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
@@ -64,15 +66,11 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
private static final String PREF_LAST_TIMESTAMP_PROCESSED =
"cp2DefaultDirectoryPhoneLookupLastTimestampProcessed";
- // We cannot efficiently process invalid numbers because batch queries cannot be constructed which
- // accomplish the necessary loose matching. We'll attempt to process a limited number of them,
- // but if there are too many we fall back to querying CP2 at render time.
- private static final int MAX_SUPPORTED_INVALID_NUMBERS = 5;
-
private final Context appContext;
private final SharedPreferences sharedPreferences;
private final ListeningExecutorService backgroundExecutorService;
private final ListeningExecutorService lightweightExecutorService;
+ private final ConfigProvider configProvider;
@Nullable private Long currentLastTimestampProcessed;
@@ -81,11 +79,13 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
@ApplicationContext Context appContext,
@Unencrypted SharedPreferences sharedPreferences,
@BackgroundExecutor ListeningExecutorService backgroundExecutorService,
- @LightweightExecutor ListeningExecutorService lightweightExecutorService) {
+ @LightweightExecutor ListeningExecutorService lightweightExecutorService,
+ ConfigProvider configProvider) {
this.appContext = appContext;
this.sharedPreferences = sharedPreferences;
this.backgroundExecutorService = backgroundExecutorService;
this.lightweightExecutorService = lightweightExecutorService;
+ this.configProvider = configProvider;
}
@Override
@@ -138,7 +138,7 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
@Override
public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
PartitionedNumbers partitionedNumbers = new PartitionedNumbers(phoneNumbers);
- if (partitionedNumbers.invalidNumbers().size() > MAX_SUPPORTED_INVALID_NUMBERS) {
+ if (partitionedNumbers.invalidNumbers().size() > getMaxSupportedInvalidNumbers()) {
// If there are N invalid numbers, we can't determine determine dirtiness without running N
// queries; since running this many queries is not feasible for the (lightweight) isDirty
// check, simply return true. The expectation is that this should rarely be the case as the
@@ -234,7 +234,8 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
// Then run a separate query for each invalid number. Separate queries are done to accomplish
// loose matching which couldn't be accomplished with a batch query.
- Assert.checkState(partitionedNumbers.invalidNumbers().size() <= MAX_SUPPORTED_INVALID_NUMBERS);
+ Assert.checkState(
+ partitionedNumbers.invalidNumbers().size() <= getMaxSupportedInvalidNumbers());
for (String invalidNumber : partitionedNumbers.invalidNumbers()) {
queryFutures.add(queryPhoneLookupTableForContactIdsBasedOnRawNumber(invalidNumber));
}
@@ -529,7 +530,11 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) {
ArraySet<DialerPhoneNumber> unprocessableNumbers = new ArraySet<>();
PartitionedNumbers partitionedNumbers = new PartitionedNumbers(existingInfoMap.keySet());
- if (partitionedNumbers.invalidNumbers().size() > MAX_SUPPORTED_INVALID_NUMBERS) {
+
+ int invalidNumberCount = partitionedNumbers.invalidNumbers().size();
+ Logger.get(appContext).logAnnotatedCallLogMetrics(invalidNumberCount);
+
+ if (invalidNumberCount > getMaxSupportedInvalidNumbers()) {
for (String invalidNumber : partitionedNumbers.invalidNumbers()) {
unprocessableNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForInvalid(invalidNumber));
}
@@ -928,4 +933,13 @@ public final class Cp2DefaultDirectoryPhoneLookup implements PhoneLookup<Cp2Info
}
return where.toString();
}
+
+ /**
+ * We cannot efficiently process invalid numbers because batch queries cannot be constructed which
+ * accomplish the necessary loose matching. We'll attempt to process a limited number of them, but
+ * if there are too many we fall back to querying CP2 at render time.
+ */
+ private long getMaxSupportedInvalidNumbers() {
+ return configProvider.getLong("cp2_phone_lookup_max_invalid_numbers", 5);
+ }
}
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 505f1c6b1..51befe822 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -67,7 +67,6 @@ import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Dir
import com.android.dialer.searchfragment.directories.DirectoryContactsCursorLoader;
import com.android.dialer.searchfragment.list.SearchActionViewHolder.Action;
import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader;
-import com.android.dialer.storage.StorageComponent;
import com.android.dialer.util.CallUtil;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.PermissionsUtil;
@@ -94,7 +93,6 @@ public final class NewSearchFragment extends Fragment
// updates so they are bundled together
private static final int ENRICHED_CALLING_CAPABILITIES_UPDATED_DELAY = 400;
- private static final String KEY_SHOW_ZERO_SUGGEST = "use_zero_suggest";
private static final String KEY_LOCATION_PROMPT_DISMISSED = "search_location_prompt_dismissed";
@VisibleForTesting public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
@@ -134,12 +132,8 @@ public final class NewSearchFragment extends Fragment
private Runnable updatePositionRunnable;
- public static NewSearchFragment newInstance(boolean showZeroSuggest) {
- NewSearchFragment fragment = new NewSearchFragment();
- Bundle args = new Bundle();
- args.putBoolean(KEY_SHOW_ZERO_SUGGEST, showZeroSuggest);
- fragment.setArguments(args);
- return fragment;
+ public static NewSearchFragment newInstance() {
+ return new NewSearchFragment();
}
@Nullable
@@ -150,7 +144,7 @@ public final class NewSearchFragment extends Fragment
adapter = new SearchAdapter(getContext(), new SearchCursorManager(), this);
adapter.setQuery(query, rawNumber);
adapter.setSearchActions(getActions());
- adapter.setZeroSuggestVisible(getArguments().getBoolean(KEY_SHOW_ZERO_SUGGEST));
+ showLocationPermission();
emptyContentView = view.findViewById(R.id.empty_view);
recyclerView = view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -265,13 +259,31 @@ public final class NewSearchFragment extends Fragment
if (adapter != null) {
adapter.setQuery(query, rawNumber);
adapter.setSearchActions(getActions());
- adapter.setZeroSuggestVisible(isRegularSearch());
+ showLocationPermission();
loadCp2ContactsCursor();
loadNearbyPlacesCursor();
loadDirectoryContactsCursors();
}
}
+ /** Returns true if the location permission was shown. */
+ private boolean showLocationPermission() {
+ if (adapter == null) {
+ return false;
+ }
+
+ if (PermissionsUtil.hasLocationPermissions(getContext())
+ || hasBeenDismissed()
+ || !isRegularSearch()) {
+ adapter.hideLocationPermissionRequest();
+ return false;
+ }
+
+ adapter.showLocationPermissionRequest(
+ v -> requestLocationPermission(), v -> dismissLocationPermission());
+ return true;
+ }
+
/** Translate the search fragment and resize it to fit on the screen. */
public void animatePosition(int start, int end, int duration) {
// Called before the view is ready, prepare a runnable to run in onCreateView
@@ -382,16 +394,16 @@ public final class NewSearchFragment extends Fragment
* <p>Should not be called before finishing loading info about all directories (local and remote).
*/
private void loadNearbyPlacesCursor() {
- if (!PermissionsUtil.hasLocationPermissions(getContext())
- && !StorageComponent.get(getContext())
- .unencryptedSharedPrefs()
- .getBoolean(KEY_LOCATION_PROMPT_DISMISSED, false)) {
- if (adapter != null && isRegularSearch() && !hasBeenDismissed()) {
- adapter.showLocationPermissionRequest(
- v -> requestLocationPermission(), v -> dismissLocationPermission());
- }
+ // If we're requesting the location permission, don't load nearby places cursor.
+ if (showLocationPermission()) {
return;
}
+
+ // If the user dismissed the prompt without granting us the permission, don't load the cursor.
+ if (!PermissionsUtil.hasLocationPermissions(getContext())) {
+ return;
+ }
+
// Cancel existing load if one exists.
ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable);
diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
index 805eaf524..74b60c603 100644
--- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java
+++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
@@ -42,7 +42,6 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
private final SearchCursorManager searchCursorManager;
private final Context context;
- private boolean showZeroSuggest;
private String query;
// Raw query number from dialpad, which may contain special character such as "+". This is used
// for actions to add contact or send sms.
@@ -138,21 +137,9 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
@Override
public int getItemCount() {
- if (TextUtils.isEmpty(query) && !showZeroSuggest) {
- return 0;
- }
return searchCursorManager.getCount();
}
- /**
- * @param visible If true and query is empty, the adapter won't show any list elements.
- * @see #setQuery(String, String)
- * @see #getItemCount()
- */
- public void setZeroSuggestVisible(boolean visible) {
- showZeroSuggest = visible;
- }
-
public void setQuery(String query, @Nullable String rawNumber) {
this.query = query;
this.rawNumber = rawNumber;
diff --git a/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml b/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml
index 99d0fbf0c..8b366fe8a 100644
--- a/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml
+++ b/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml
@@ -24,7 +24,7 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
- android:layout_gravity="center_vertical"
+ android:layout_gravity="center_vertical|start"
android:padding="12dp"
android:tint="@color/dialer_theme_color"/>
diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntry.java b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java
new file mode 100644
index 000000000..aa90909f1
--- /dev/null
+++ b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java
@@ -0,0 +1,116 @@
+/*
+ * 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.speeddial.database;
+
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import com.google.auto.value.AutoValue;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** POJO representation of database rows returned by {@link SpeedDialEntryDao}. */
+@AutoValue
+public abstract class SpeedDialEntry {
+
+ /** Unique ID */
+ public abstract long id();
+
+ /** @see {@link Contacts#_ID} */
+ public abstract long contactId();
+
+ /** @see {@link Contacts#LOOKUP_KEY} */
+ public abstract String lookupKey();
+
+ /**
+ * {@link Channel} that is associated with this entry.
+ *
+ * <p>Contacts with multiple channels do not have a default until specified by the user. Once the
+ * default channel is determined, all calls should be placed to this channel.
+ */
+ @Nullable
+ public abstract Channel defaultChannel();
+
+ public abstract Builder toBuilder();
+
+ public static Builder builder() {
+ return new AutoValue_SpeedDialEntry.Builder();
+ }
+
+ /** Builder class for speed dial entry. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setId(long id);
+
+ public abstract Builder setContactId(long contactId);
+
+ public abstract Builder setLookupKey(String lookupKey);
+
+ public abstract Builder setDefaultChannel(@Nullable Channel defaultChannel);
+
+ public abstract SpeedDialEntry build();
+ }
+
+ /** POJO representation of a relevant phone number columns in {@link SpeedDialEntryDao}. */
+ @AutoValue
+ public abstract static class Channel {
+
+ public static final int UNKNOWN = 0;
+ public static final int VOICE = 1;
+ public static final int VIDEO = 2;
+
+ /** Whether the Channel is for an audio or video call. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({UNKNOWN, VOICE, VIDEO})
+ public @interface Technology {}
+
+ /**
+ * Raw phone number as the user entered it.
+ *
+ * @see {@link Phone#NUMBER}
+ */
+ public abstract String number();
+
+ /**
+ * Label that the user associated with this number like {@link Phone#TYPE_WORK}, {@link
+ * Phone#TYPE_HOME}, ect.
+ *
+ * @see {@link Phone#LABEL}
+ */
+ public abstract String label();
+
+ public abstract @Technology int technology();
+
+ public static Builder builder() {
+ return new AutoValue_SpeedDialEntry_Channel.Builder();
+ }
+
+ /** Builder class for {@link Channel}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setNumber(String number);
+
+ public abstract Builder setLabel(String label);
+
+ public abstract Builder setTechnology(@Technology int technology);
+
+ public abstract Channel build();
+ }
+ }
+}
diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java
new file mode 100644
index 000000000..39cb115c8
--- /dev/null
+++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java
@@ -0,0 +1,57 @@
+/*
+ * 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.speeddial.database;
+
+import java.util.List;
+
+/** Interface that databases support speed dial entries should implement. */
+public interface SpeedDialEntryDao {
+
+ /** Return all entries in the database */
+ List<SpeedDialEntry> getAllEntries();
+
+ /**
+ * Insert new entries.
+ *
+ * <p>Fails if any of the {@link SpeedDialEntry#id()} already exist.
+ */
+ void insert(List<SpeedDialEntry> entries);
+
+ /**
+ * Insert a new entry.
+ *
+ * <p>Fails if the {@link SpeedDialEntry#id()} already exists.
+ */
+ long insert(SpeedDialEntry entry);
+
+ /**
+ * Updates existing entries based on {@link SpeedDialEntry#id}.
+ *
+ * <p>Fails if the {@link SpeedDialEntry#id()} doesn't exist.
+ */
+ void update(List<SpeedDialEntry> entries);
+
+ /**
+ * Delete the passed in entries based on {@link SpeedDialEntry#id}.
+ *
+ * <p>Fails if the {@link SpeedDialEntry#id()} doesn't exist.
+ */
+ void delete(List<Long> entries);
+
+ /** Delete all entries in the database. */
+ void deleteAll();
+}
diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java
new file mode 100644
index 000000000..1812dbdc0
--- /dev/null
+++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java
@@ -0,0 +1,221 @@
+/*
+ * 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.speeddial.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.text.TextUtils;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.database.Selection;
+import com.android.dialer.speeddial.database.SpeedDialEntry.Channel;
+import java.util.ArrayList;
+import java.util.List;
+
+/** {@link SpeedDialEntryDao} implemented as an SQLite database. */
+public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper
+ implements SpeedDialEntryDao {
+
+ private static final int DATABASE_VERSION = 1;
+ private static final String DATABASE_NAME = "CPSpeedDialEntry";
+
+ // Column names
+ private static final String TABLE_NAME = "speed_dial_entries";
+ private static final String ID = "id";
+ private static final String CONTACT_ID = "contact_id";
+ private static final String LOOKUP_KEY = "lookup_key";
+ private static final String PHONE_NUMBER = "phone_number";
+ private static final String PHONE_LABEL = "phone_label";
+ private static final String PHONE_TYPE = "phone_type";
+
+ // Column positions
+ private static final int POSITION_ID = 0;
+ private static final int POSITION_CONTACT_ID = 1;
+ private static final int POSITION_LOOKUP_KEY = 2;
+ private static final int POSITION_PHONE_NUMBER = 3;
+ private static final int POSITION_PHONE_LABEL = 4;
+ private static final int POSITION_PHONE_TYPE = 5;
+
+ // Create Table Query
+ private static final String CREATE_TABLE_SQL =
+ "create table if not exists "
+ + TABLE_NAME
+ + " ("
+ + (ID + " integer primary key, ")
+ + (CONTACT_ID + " integer, ")
+ + (LOOKUP_KEY + " text, ")
+ + (PHONE_NUMBER + " text, ")
+ + (PHONE_LABEL + " text, ")
+ + (PHONE_TYPE + " integer ")
+ + ");";
+
+ private static final String DELETE_TABLE_SQL = "drop table if exists " + TABLE_NAME;
+
+ public SpeedDialEntryDatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_SQL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO(calderwoodra): handle upgrades more elegantly
+ db.execSQL(DELETE_TABLE_SQL);
+ this.onCreate(db);
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO(calderwoodra): handle upgrades more elegantly
+ this.onUpgrade(db, oldVersion, newVersion);
+ }
+
+ @Override
+ public List<SpeedDialEntry> getAllEntries() {
+ List<SpeedDialEntry> entries = new ArrayList<>();
+
+ String query = "SELECT * FROM " + TABLE_NAME;
+ try (SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.rawQuery(query, null)) {
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ Channel channel =
+ Channel.builder()
+ .setNumber(cursor.getString(POSITION_PHONE_NUMBER))
+ .setLabel(cursor.getString(POSITION_PHONE_LABEL))
+ .setTechnology(cursor.getInt(POSITION_PHONE_TYPE))
+ .build();
+ if (TextUtils.isEmpty(channel.number())) {
+ channel = null;
+ }
+ SpeedDialEntry entry =
+ SpeedDialEntry.builder()
+ .setDefaultChannel(channel)
+ .setContactId(cursor.getLong(POSITION_CONTACT_ID))
+ .setLookupKey(cursor.getString(POSITION_LOOKUP_KEY))
+ .setId(cursor.getInt(POSITION_ID))
+ .build();
+ entries.add(entry);
+ }
+ }
+ return entries;
+ }
+
+ @Override
+ public void insert(List<SpeedDialEntry> entries) {
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+ try {
+ for (SpeedDialEntry entry : entries) {
+ if (db.insert(TABLE_NAME, null, buildContentValues(entry)) == -1L) {
+ throw Assert.createUnsupportedOperationFailException(
+ "Attempted to insert a row that already exists.");
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ db.close();
+ }
+ }
+
+ @Override
+ public long insert(SpeedDialEntry entry) {
+ long updateRowId;
+ try (SQLiteDatabase db = getWritableDatabase()) {
+ updateRowId = db.insert(TABLE_NAME, null, buildContentValues(entry));
+ }
+ if (updateRowId == -1) {
+ throw Assert.createUnsupportedOperationFailException(
+ "Attempted to insert a row that already exists.");
+ }
+ return updateRowId;
+ }
+
+ @Override
+ public void update(List<SpeedDialEntry> entries) {
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+ try {
+ for (SpeedDialEntry entry : entries) {
+ int count =
+ db.update(
+ TABLE_NAME,
+ buildContentValues(entry),
+ ID + " = ?",
+ new String[] {Long.toString(entry.id())});
+ if (count != 1) {
+ throw Assert.createUnsupportedOperationFailException(
+ "Attempted to update an undetermined number of rows: " + count);
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ db.close();
+ }
+ }
+
+ private ContentValues buildContentValues(SpeedDialEntry entry) {
+ ContentValues values = new ContentValues();
+ values.put(ID, entry.id());
+ values.put(CONTACT_ID, entry.contactId());
+ values.put(LOOKUP_KEY, entry.lookupKey());
+ if (entry.defaultChannel() != null) {
+ values.put(PHONE_NUMBER, entry.defaultChannel().number());
+ values.put(PHONE_LABEL, entry.defaultChannel().label());
+ values.put(PHONE_TYPE, entry.defaultChannel().technology());
+ }
+ return values;
+ }
+
+ @Override
+ public void delete(List<Long> ids) {
+ List<String> idStrings = new ArrayList<>();
+ for (Long id : ids) {
+ idStrings.add(Long.toString(id));
+ }
+
+ Selection selection = Selection.builder().and(Selection.column(ID).in(idStrings)).build();
+ try (SQLiteDatabase db = getWritableDatabase()) {
+ int count = db.delete(TABLE_NAME, selection.getSelection(), selection.getSelectionArgs());
+ if (count != ids.size()) {
+ throw Assert.createUnsupportedOperationFailException(
+ "Attempted to delete an undetermined number of rows: " + count);
+ }
+ }
+ }
+
+ @Override
+ public void deleteAll() {
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+ try {
+ // Passing null into where clause will delete all rows
+ db.delete(TABLE_NAME, /* whereClause=*/ null, null);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ db.close();
+ }
+ }
+}
diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java
index 2608cb2aa..f05ec202d 100644
--- a/java/com/android/dialer/telecom/TelecomUtil.java
+++ b/java/com/android/dialer/telecom/TelecomUtil.java
@@ -26,6 +26,7 @@ import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.CallLog.Calls;
+import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresPermission;
@@ -299,6 +300,11 @@ public abstract class TelecomUtil {
return instance.isDefaultDialer(context);
}
+ public static boolean isRttEnabled(Context context) {
+ return Settings.System.getInt(context.getContentResolver(), Settings.System.RTT_CALLING_MODE, 0)
+ != 0;
+ }
+
/** @return the other SIM based PhoneAccountHandle that is not {@code currentAccount} */
@Nullable
@RequiresPermission(permission.READ_PHONE_STATE)
diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java
index 385464252..38c8da898 100644
--- a/java/com/android/incallui/CallButtonPresenter.java
+++ b/java/com/android/incallui/CallButtonPresenter.java
@@ -304,6 +304,12 @@ public class CallButtonPresenter
}
@Override
+ public void changeToRttClicked() {
+ LogUtil.enterBlock("CallButtonPresenter.changeToRttClicked");
+ call.sendRttUpgradeRequest();
+ }
+
+ @Override
public void onEndCallClicked() {
LogUtil.i("CallButtonPresenter.onEndCallClicked", "call: " + call);
if (call != null) {
@@ -473,6 +479,8 @@ public class CallButtonPresenter
// Most devices cannot make calls on 2 SIMs at the same time.
&& InCallPresenter.getInstance().getCallList().getAllCalls().size() == 1;
+ boolean showUpgradeToRtt = TelecomUtil.isRttEnabled(context) && call.canUpgradeToRttCall();
+
inCallButtonUi.showButton(InCallButtonIds.BUTTON_AUDIO, true);
inCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP, showSwap);
inCallButtonUi.showButton(InCallButtonIds.BUTTON_HOLD, showHold);
@@ -482,6 +490,7 @@ public class CallButtonPresenter
inCallButtonUi.showButton(InCallButtonIds.BUTTON_ADD_CALL, true);
inCallButtonUi.enableButton(InCallButtonIds.BUTTON_ADD_CALL, showAddCall);
inCallButtonUi.showButton(InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
+ inCallButtonUi.showButton(InCallButtonIds.BUTTON_UPGRADE_TO_RTT, showUpgradeToRtt);
inCallButtonUi.showButton(InCallButtonIds.BUTTON_DOWNGRADE_TO_AUDIO, showDowngradeToAudio);
inCallButtonUi.showButton(
InCallButtonIds.BUTTON_SWITCH_CAMERA,
diff --git a/java/com/android/incallui/CallCardPresenter.java b/java/com/android/incallui/CallCardPresenter.java
index 9c5e0623e..2f88c8836 100644
--- a/java/com/android/incallui/CallCardPresenter.java
+++ b/java/com/android/incallui/CallCardPresenter.java
@@ -271,10 +271,10 @@ public class CallCardPresenter
// getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
// highest priority call to display as the secondary call.
- secondary = getCallToDisplay(callList, null, true);
+ secondary = InCallPresenter.getCallToDisplay(callList, null, true);
} else if (newState == InCallState.INCALL) {
- primary = getCallToDisplay(callList, null, false);
- secondary = getCallToDisplay(callList, primary, true);
+ primary = InCallPresenter.getCallToDisplay(callList, null, false);
+ secondary = InCallPresenter.getCallToDisplay(callList, primary, true);
}
LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
@@ -302,7 +302,6 @@ public class CallCardPresenter
this.primaryNumber = primaryNumber;
if (this.primary != null) {
- InCallPresenter.getInstance().onForegroundCallChanged(this.primary);
inCallScreen.updateInCallScreenColors();
}
@@ -636,54 +635,6 @@ public class CallCardPresenter
}
}
- /**
- * Get the highest priority call to display. Goes through the calls and chooses which to return
- * based on priority of which type of call to display to the user. Callers can use the "ignore"
- * feature to get the second best call by passing a previously found primary call as ignore.
- *
- * @param ignore A call to ignore if found.
- */
- private DialerCall getCallToDisplay(
- CallList callList, DialerCall ignore, boolean skipDisconnected) {
- // Active calls come second. An active call always gets precedent.
- DialerCall retval = callList.getActiveCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
-
- // Sometimes there is intemediate state that two calls are in active even one is about
- // to be on hold.
- retval = callList.getSecondActiveCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
-
- // Disconnected calls get primary position if there are no active calls
- // to let user know quickly what call has disconnected. Disconnected
- // calls are very short lived.
- if (!skipDisconnected) {
- retval = callList.getDisconnectingCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
- retval = callList.getDisconnectedCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
- }
-
- // Then we go to background call (calls on hold)
- retval = callList.getBackgroundCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
-
- // Lastly, we go to a second background call.
- retval = callList.getSecondBackgroundCall();
-
- return retval;
- }
-
private void updatePrimaryDisplayInfo() {
if (inCallScreen == null) {
// TODO: May also occur if search result comes back after ui is destroyed. Look into
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 9d08dc4b6..1ba3683f0 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -1468,7 +1468,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity
return new ShouldShowUiResult(false, null);
}
- if (call.isRttCall()) {
+ if (call.isActiveRttCall()) {
LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call");
return new ShouldShowUiResult(true, call);
}
@@ -1520,7 +1520,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity
AnswerScreen answerScreen =
AnswerBindings.createAnswerScreen(
call.getId(),
- call.isRttCall(),
+ call.isActiveRttCall(),
call.isVideoCall(),
isVideoUpgradeRequest,
call.getVideoTech().isSelfManagedCamera(),
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index 2e98a969d..e11b376c1 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -771,6 +771,22 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud
"Phone switching state: " + oldState + " -> " + newState);
inCallState = newState;
+ // Foreground call changed
+ DialerCall primary = null;
+ if (newState == InCallState.INCOMING) {
+ primary = callList.getIncomingCall();
+ } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
+ primary = callList.getOutgoingCall();
+ if (primary == null) {
+ primary = callList.getPendingOutgoingCall();
+ }
+ } else if (newState == InCallState.INCALL) {
+ primary = getCallToDisplay(callList, null, false);
+ }
+ if (primary != null) {
+ onForegroundCallChanged(primary);
+ }
+
// notify listeners of new state
for (InCallStateListener listener : listeners) {
LogUtil.d(
@@ -787,6 +803,54 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud
Trace.endSection();
}
+ /**
+ * Get the highest priority call to display. Goes through the calls and chooses which to return
+ * based on priority of which type of call to display to the user. Callers can use the "ignore"
+ * feature to get the second best call by passing a previously found primary call as ignore.
+ *
+ * @param ignore A call to ignore if found.
+ */
+ static DialerCall getCallToDisplay(
+ CallList callList, DialerCall ignore, boolean skipDisconnected) {
+ // Active calls come second. An active call always gets precedent.
+ DialerCall retval = callList.getActiveCall();
+ if (retval != null && retval != ignore) {
+ return retval;
+ }
+
+ // Sometimes there is intemediate state that two calls are in active even one is about
+ // to be on hold.
+ retval = callList.getSecondActiveCall();
+ if (retval != null && retval != ignore) {
+ return retval;
+ }
+
+ // Disconnected calls get primary position if there are no active calls
+ // to let user know quickly what call has disconnected. Disconnected
+ // calls are very short lived.
+ if (!skipDisconnected) {
+ retval = callList.getDisconnectingCall();
+ if (retval != null && retval != ignore) {
+ return retval;
+ }
+ retval = callList.getDisconnectedCall();
+ if (retval != null && retval != ignore) {
+ return retval;
+ }
+ }
+
+ // Then we go to background call (calls on hold)
+ retval = callList.getBackgroundCall();
+ if (retval != null && retval != ignore) {
+ return retval;
+ }
+
+ // Lastly, we go to a second background call.
+ retval = callList.getSecondBackgroundCall();
+
+ return retval;
+ }
+
/** Called when there is a new incoming call. */
@Override
public void onIncomingCall(DialerCall call) {
diff --git a/java/com/android/incallui/ProximitySensor.java b/java/com/android/incallui/ProximitySensor.java
index 4b033441d..9719e5d3d 100644
--- a/java/com/android/incallui/ProximitySensor.java
+++ b/java/com/android/incallui/ProximitySensor.java
@@ -113,7 +113,7 @@ public class ProximitySensor
DialerCall activeCall = callList.getActiveCall();
boolean isVideoCall = activeCall != null && activeCall.isVideoCall();
- boolean isRttCall = activeCall != null && activeCall.isRttCall();
+ boolean isRttCall = activeCall != null && activeCall.isActiveRttCall();
if (isOffhook != isPhoneOffhook
|| this.isVideoCall != isVideoCall
diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java
index d5e6a1001..0850e913a 100644
--- a/java/com/android/incallui/ReturnToCallController.java
+++ b/java/com/android/incallui/ReturnToCallController.java
@@ -92,10 +92,10 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio
endCall = createActionIntent(ReturnToCallActionReceiver.ACTION_END_CALL);
fullScreen = createActionIntent(ReturnToCallActionReceiver.ACTION_RETURN_TO_CALL);
- InCallPresenter.getInstance().addInCallUiListener(this);
- CallList.getInstance().addListener(this);
AudioModeProvider.getInstance().addListener(this);
audioState = AudioModeProvider.getInstance().getAudioState();
+ InCallPresenter.getInstance().addInCallUiListener(this);
+ CallList.getInstance().addListener(this);
}
public void tearDown() {
@@ -186,7 +186,7 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio
return;
}
- if ((bubble == null || !bubble.isVisible())
+ if ((bubble == null || !(bubble.isVisible() || bubble.isDismissed()))
&& 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 f639e5bdb..01f3b9d29 100644
--- a/java/com/android/incallui/call/CallList.java
+++ b/java/com/android/incallui/call/CallList.java
@@ -514,6 +514,15 @@ public class CallList implements DialerCallDelegate {
return call != null && call != getDisconnectingCall() && call != getDisconnectedCall();
}
+ boolean hasActiveRttCall() {
+ for (DialerCall call : getAllCalls()) {
+ if (call.isActiveRttCall()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns the first call found in the call map with the upgrade to video modification state.
*
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 4815a6e41..e08c926d8 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -988,7 +988,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
}
@TargetApi(28)
- public boolean isRttCall() {
+ public boolean isActiveRttCall() {
if (BuildCompat.isAtLeastP()) {
return getTelecomCall().isRttActive();
} else {
@@ -998,12 +998,41 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
@TargetApi(28)
public RttCall getRttCall() {
- if (!isRttCall()) {
+ if (!isActiveRttCall()) {
return null;
}
return getTelecomCall().getRttCall();
}
+ @TargetApi(28)
+ public boolean canUpgradeToRttCall() {
+ PhoneAccount phoneAccount = getPhoneAccount();
+ if (phoneAccount == null) {
+ return false;
+ }
+ if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
+ return false;
+ }
+ if (isActiveRttCall()) {
+ return false;
+ }
+ if (isVideoCall()) {
+ return false;
+ }
+ if (isConferenceCall()) {
+ return false;
+ }
+ if (CallList.getInstance().hasActiveRttCall()) {
+ return false;
+ }
+ return true;
+ }
+
+ @TargetApi(28)
+ public void sendRttUpgradeRequest() {
+ getTelecomCall().sendRttRequest();
+ }
+
public boolean hasReceivedVideoUpgradeRequest() {
return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
}
diff --git a/java/com/android/incallui/callpending/CallPendingActivity.java b/java/com/android/incallui/callpending/CallPendingActivity.java
index c7ce2b108..831ebbd52 100644
--- a/java/com/android/incallui/callpending/CallPendingActivity.java
+++ b/java/com/android/incallui/callpending/CallPendingActivity.java
@@ -255,6 +255,9 @@ public class CallPendingActivity extends FragmentActivity
public void changeToVideoClicked() {}
@Override
+ public void changeToRttClicked() {}
+
+ @Override
public void switchCameraClicked(boolean useFrontFacingCamera) {}
@Override
diff --git a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
index 2a0894047..757d81352 100644
--- a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
+++ b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
@@ -57,6 +57,9 @@ class ButtonChooserFactory {
mapping.put(
InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE,
MappingInfo.builder(4).setSlotOrder(0).build());
+ // RTT call is only supported on IMS and WiFi.
+ mapping.put(
+ InCallButtonIds.BUTTON_UPGRADE_TO_RTT, MappingInfo.builder(3).setSlotOrder(0).build());
mapping.put(
InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, MappingInfo.builder(4).setSlotOrder(10).build());
mapping.put(
@@ -114,7 +117,7 @@ class ButtonChooserFactory {
mapping.put(InCallButtonIds.BUTTON_MUTE, MappingInfo.builder(0).build());
mapping.put(InCallButtonIds.BUTTON_DIALPAD, MappingInfo.builder(1).build());
mapping.put(InCallButtonIds.BUTTON_AUDIO, MappingInfo.builder(2).build());
- mapping.put(InCallButtonIds.BUTTON_MERGE, MappingInfo.builder(3).setSlotOrder(0).build());
+ mapping.put(InCallButtonIds.BUTTON_MERGE, MappingInfo.builder(3).setSlotOrder(5).build());
mapping.put(InCallButtonIds.BUTTON_ADD_CALL, MappingInfo.builder(3).build());
mapping.put(InCallButtonIds.BUTTON_SWAP_SIM, MappingInfo.builder(4).build());
return mapping;
diff --git a/java/com/android/incallui/incall/impl/ButtonController.java b/java/com/android/incallui/incall/impl/ButtonController.java
index 98460c704..9106dab9d 100644
--- a/java/com/android/incallui/incall/impl/ButtonController.java
+++ b/java/com/android/incallui/incall/impl/ButtonController.java
@@ -519,6 +519,24 @@ interface ButtonController {
}
}
+ class UpgradeToRttButtonController extends SimpleNonCheckableButtonController {
+
+ public UpgradeToRttButtonController(@NonNull InCallButtonUiDelegate delegate) {
+ super(
+ delegate,
+ InCallButtonIds.BUTTON_UPGRADE_TO_RTT,
+ 0,
+ R.string.incall_label_rttcall,
+ R.drawable.quantum_ic_rtt_vd_theme_24);
+ Assert.isNotNull(delegate);
+ }
+
+ @Override
+ public void onClick(View view) {
+ delegate.changeToRttClicked();
+ }
+ }
+
class ManageConferenceButtonController extends SimpleNonCheckableButtonController {
private final InCallScreenDelegate inCallScreenDelegate;
diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java
index fb8c2c403..6f0ba60b8 100644
--- a/java/com/android/incallui/incall/impl/InCallFragment.java
+++ b/java/com/android/incallui/incall/impl/InCallFragment.java
@@ -54,6 +54,7 @@ import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRou
import com.android.incallui.contactgrid.ContactGridManager;
import com.android.incallui.hold.OnHoldFragment;
import com.android.incallui.incall.impl.ButtonController.SpeakerButtonController;
+import com.android.incallui.incall.impl.ButtonController.UpgradeToRttButtonController;
import com.android.incallui.incall.impl.InCallButtonGridFragment.OnButtonGridCreatedListener;
import com.android.incallui.incall.protocol.InCallButtonIds;
import com.android.incallui.incall.protocol.InCallButtonIdsExtension;
@@ -114,7 +115,8 @@ public class InCallFragment extends Fragment
|| id == InCallButtonIds.BUTTON_ADD_CALL
|| id == InCallButtonIds.BUTTON_MERGE
|| id == InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE
- || id == InCallButtonIds.BUTTON_SWAP_SIM;
+ || id == InCallButtonIds.BUTTON_SWAP_SIM
+ || id == InCallButtonIds.BUTTON_UPGRADE_TO_RTT;
}
@Override
@@ -226,6 +228,7 @@ public class InCallFragment extends Fragment
buttonControllers.add(new ButtonController.SwapSimButtonController(inCallButtonUiDelegate));
buttonControllers.add(
new ButtonController.UpgradeToVideoButtonController(inCallButtonUiDelegate));
+ buttonControllers.add(new UpgradeToRttButtonController(inCallButtonUiDelegate));
buttonControllers.add(
new ButtonController.ManageConferenceButtonController(inCallScreenDelegate));
buttonControllers.add(
diff --git a/java/com/android/incallui/incall/impl/res/values/strings.xml b/java/com/android/incallui/incall/impl/res/values/strings.xml
index d0217566a..c4c40a15d 100644
--- a/java/com/android/incallui/incall/impl/res/values/strings.xml
+++ b/java/com/android/incallui/incall/impl/res/values/strings.xml
@@ -20,6 +20,10 @@
[CHAR LIMIT=12] -->
<string name="incall_label_videocall">Video call</string>
+ <!-- Button shown during a phone call to upgrade to Real-time Text.
+ [CHAR LIMIT=12] -->
+ <string name="incall_label_rttcall">RTT</string>
+
<!-- Button shown during a phone call to put the call on hold.
[CHAR LIMIT=12] -->
<string name="incall_label_hold">Hold</string>
diff --git a/java/com/android/incallui/incall/protocol/InCallButtonIds.java b/java/com/android/incallui/incall/protocol/InCallButtonIds.java
index 3de533519..80ea75e99 100644
--- a/java/com/android/incallui/incall/protocol/InCallButtonIds.java
+++ b/java/com/android/incallui/incall/protocol/InCallButtonIds.java
@@ -39,6 +39,7 @@ import java.lang.annotation.RetentionPolicy;
InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY,
InCallButtonIds.BUTTON_SWAP_SIM,
InCallButtonIds.BUTTON_COUNT,
+ InCallButtonIds.BUTTON_UPGRADE_TO_RTT
})
public @interface InCallButtonIds {
@@ -58,4 +59,5 @@ public @interface InCallButtonIds {
int BUTTON_SWITCH_TO_SECONDARY = 13;
int BUTTON_SWAP_SIM = 14;
int BUTTON_COUNT = 15;
+ int BUTTON_UPGRADE_TO_RTT = 16;
}
diff --git a/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java b/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java
index db6e9009c..5239d9d34 100644
--- a/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java
+++ b/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java
@@ -56,6 +56,8 @@ public class InCallButtonIdsExtension {
return "SWITCH_TO_SECONDARY";
} else if (id == InCallButtonIds.BUTTON_SWAP_SIM) {
return "SWAP_SIM";
+ } else if (id == InCallButtonIds.BUTTON_UPGRADE_TO_RTT) {
+ return "UPGRADE_TO_RTT";
} else {
return "INVALID_BUTTON: " + id;
}
diff --git a/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java b/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java
index 9f9c5fb03..b0e23efcd 100644
--- a/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java
+++ b/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java
@@ -47,6 +47,8 @@ public interface InCallButtonUiDelegate {
void changeToVideoClicked();
+ void changeToRttClicked();
+
void switchCameraClicked(boolean useFrontFacingCamera);
void toggleCameraClicked();
diff --git a/java/com/android/incallui/rtt/impl/AudioSelectMenu.java b/java/com/android/incallui/rtt/impl/AudioSelectMenu.java
index 2d4ab3989..01c3950e9 100644
--- a/java/com/android/incallui/rtt/impl/AudioSelectMenu.java
+++ b/java/com/android/incallui/rtt/impl/AudioSelectMenu.java
@@ -39,7 +39,7 @@ public class AudioSelectMenu extends PopupWindow {
Context context,
InCallButtonUiDelegate inCallButtonUiDelegate,
OnButtonClickListener onButtonClickListener) {
- super(context);
+ super(context, null, 0, R.style.OverflowMenu);
this.context = context;
this.inCallButtonUiDelegate = inCallButtonUiDelegate;
this.onButtonClickListener = onButtonClickListener;
@@ -76,7 +76,6 @@ public class AudioSelectMenu extends PopupWindow {
}
item.setOnClickListener(
(v) -> {
- dismiss();
inCallButtonUiDelegate.setAudioRoute(itemRoute);
});
}
diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
index 8d924c9f8..955fc9fec 100644
--- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java
+++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
@@ -33,7 +33,9 @@ import java.util.List;
public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolder> {
interface MessageListener {
- void newMessageAdded();
+ void onUpdateRemoteMessage(int position);
+
+ void onUpdateLocalMessage(int position);
}
private static final String KEY_MESSAGE_DATA = "key_message_data";
@@ -114,7 +116,7 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde
void addLocalMessage(String message) {
updateCurrentLocalMessage(message);
if (messageListener != null) {
- messageListener.newMessageAdded();
+ messageListener.onUpdateLocalMessage(lastIndexOfLocalMessage);
}
}
@@ -143,7 +145,7 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde
}
updateCurrentRemoteMessage(message);
if (messageListener != null) {
- messageListener.newMessageAdded();
+ messageListener.onUpdateRemoteMessage(RttChatMessage.getLastIndexRemoteMessage(rttMessages));
}
}
diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java
index 56ac2429c..a181f88f0 100644
--- a/java/com/android/incallui/rtt/impl/RttChatFragment.java
+++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java
@@ -85,15 +85,6 @@ public class RttChatFragment extends Fragment
private ImageButton submitButton;
private boolean isClearingInput;
- private final OnScrollListener onScrollListener =
- new OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- if (dy < 0) {
- UiUtil.hideKeyboardFrom(getContext(), editText);
- }
- }
- };
private InCallScreenDelegate inCallScreenDelegate;
private RttCallScreenDelegate rttCallScreenDelegate;
private InCallButtonUiDelegate inCallButtonUiDelegate;
@@ -105,6 +96,8 @@ public class RttChatFragment extends Fragment
private SecondaryInfo savedSecondaryInfo;
private TextView statusBanner;
private PrimaryInfo primaryInfo;
+ private boolean isUserScrolling;
+ private boolean shouldAutoScrolling;
/**
* Create a new instance of RttChatFragment.
@@ -193,7 +186,27 @@ public class RttChatFragment extends Fragment
recyclerView.setHasFixedSize(false);
adapter = new RttChatAdapter(getContext(), this, savedInstanceState);
recyclerView.setAdapter(adapter);
- recyclerView.addOnScrollListener(onScrollListener);
+ recyclerView.addOnScrollListener(
+ new OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int i) {
+ if (i == RecyclerView.SCROLL_STATE_DRAGGING) {
+ isUserScrolling = true;
+ } else if (i == RecyclerView.SCROLL_STATE_IDLE) {
+ isUserScrolling = false;
+ // Auto scrolling for new messages should be resumed if it's scrolled to bottom.
+ shouldAutoScrolling = !recyclerView.canScrollVertically(1);
+ }
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (dy < 0 && isUserScrolling) {
+ UiUtil.hideKeyboardFrom(getContext(), editText);
+ }
+ }
+ });
+
submitButton = view.findViewById(R.id.rtt_chat_submit_button);
submitButton.setOnClickListener(
v -> {
@@ -202,6 +215,9 @@ public class RttChatFragment extends Fragment
editText.setText("");
isClearingInput = false;
rttCallScreenDelegate.onLocalMessage(Constants.BUBBLE_BREAKER);
+ // Auto scrolling for new messages should be resumed since user has submit current
+ // message.
+ shouldAutoScrolling = true;
});
submitButton.setEnabled(false);
endCallButton = view.findViewById(R.id.rtt_end_call_button);
@@ -276,8 +292,21 @@ public class RttChatFragment extends Fragment
}
@Override
- public void newMessageAdded() {
- recyclerView.smoothScrollToPosition(adapter.getItemCount());
+ public void onUpdateLocalMessage(int position) {
+ if (position < 0) {
+ return;
+ }
+ recyclerView.smoothScrollToPosition(position);
+ }
+
+ @Override
+ public void onUpdateRemoteMessage(int position) {
+ if (position < 0) {
+ return;
+ }
+ if (shouldAutoScrolling) {
+ recyclerView.smoothScrollToPosition(position);
+ }
}
@Override
diff --git a/java/com/android/incallui/rtt/impl/RttChatMessage.java b/java/com/android/incallui/rtt/impl/RttChatMessage.java
index cbc53ef15..0060b1bd1 100644
--- a/java/com/android/incallui/rtt/impl/RttChatMessage.java
+++ b/java/com/android/incallui/rtt/impl/RttChatMessage.java
@@ -162,7 +162,7 @@ final class RttChatMessage implements Parcelable {
return i;
}
- private static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) {
+ static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) {
int i = messageList.size() - 1;
while (i >= 0 && !messageList.get(i).isRemote) {
i--;
diff --git a/java/com/android/incallui/rtt/impl/RttOverflowMenu.java b/java/com/android/incallui/rtt/impl/RttOverflowMenu.java
index 6a7aeba96..deee8ee15 100644
--- a/java/com/android/incallui/rtt/impl/RttOverflowMenu.java
+++ b/java/com/android/incallui/rtt/impl/RttOverflowMenu.java
@@ -42,7 +42,7 @@ public class RttOverflowMenu extends PopupWindow implements OnCheckedChangeListe
Context context,
InCallButtonUiDelegate inCallButtonUiDelegate,
InCallScreenDelegate inCallScreenDelegate) {
- super(context);
+ super(context, null, 0, R.style.OverflowMenu);
this.inCallButtonUiDelegate = inCallButtonUiDelegate;
this.inCallScreenDelegate = inCallScreenDelegate;
View view = View.inflate(context, R.layout.overflow_menu, null);
@@ -67,7 +67,6 @@ public class RttOverflowMenu extends PopupWindow implements OnCheckedChangeListe
if (isSwitchToSecondaryButtonEnabled) {
this.inCallScreenDelegate.onSecondaryInfoClicked();
}
- dismiss();
});
}
@@ -80,7 +79,6 @@ public class RttOverflowMenu extends PopupWindow implements OnCheckedChangeListe
} else if (button == dialpadButton) {
inCallButtonUiDelegate.showDialpadClicked(isChecked);
}
- dismiss();
}
void setMuteButtonChecked(boolean isChecked) {
diff --git a/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml b/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml
index 614298679..2af14fd8e 100644
--- a/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml
+++ b/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml
@@ -18,4 +18,4 @@
android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners android:radius="2dp"/>
-</shape> \ No newline at end of file
+</shape>
diff --git a/java/com/android/incallui/rtt/impl/res/layout/audio_route.xml b/java/com/android/incallui/rtt/impl/res/layout/audio_route.xml
index 89b5c76f0..f098316a1 100644
--- a/java/com/android/incallui/rtt/impl/res/layout/audio_route.xml
+++ b/java/com/android/incallui/rtt/impl/res/layout/audio_route.xml
@@ -20,7 +20,6 @@
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
- android:background="@drawable/overflow_menu_background"
android:orientation="vertical">
<com.android.incallui.rtt.impl.RttCheckableButton
android:id="@+id/audioroute_back"
diff --git a/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml b/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml
index 34a99544a..ea7ff1095 100644
--- a/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml
+++ b/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml
@@ -23,6 +23,7 @@
android:id="@+id/rtt_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingTop="70dp"
android:paddingBottom="70dp"
android:clipToPadding="false"/>
diff --git a/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml b/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml
index eb7e38691..0ec36f33e 100644
--- a/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml
+++ b/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml
@@ -20,7 +20,6 @@
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
- android:background="@drawable/overflow_menu_background"
android:orientation="vertical">
<com.android.incallui.rtt.impl.RttCheckableButton
android:id="@+id/menu_mute"
diff --git a/java/com/android/incallui/rtt/impl/res/values/dimens.xml b/java/com/android/incallui/rtt/impl/res/values/dimens.xml
index 4c3fe02d2..a6418d70e 100644
--- a/java/com/android/incallui/rtt/impl/res/values/dimens.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/dimens.xml
@@ -18,4 +18,5 @@
<dimen name="rtt_message_margin_top">16dp</dimen>
<dimen name="rtt_same_group_message_margin_top">2dp</dimen>
<dimen name="rtt_overflow_menu_width">180dp</dimen>
+ <dimen name="rtt_overflow_menu_elevation">8dp</dimen>
</resources> \ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/values/styles.xml b/java/com/android/incallui/rtt/impl/res/values/styles.xml
index bbacde813..667cd1241 100644
--- a/java/com/android/incallui/rtt/impl/res/values/styles.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/styles.xml
@@ -40,4 +40,10 @@
<item name="android:theme">@style/ButtonTheme</item>
<item name="android:background">?attr/selectableItemBackground</item>
</style>
+
+ <style name="OverflowMenu">
+ <item name="android:popupAnimationStyle">@android:style/Animation.Dialog</item>
+ <item name="android:popupBackground">@drawable/overflow_menu_background</item>
+ <item name="android:popupElevation">@dimen/rtt_overflow_menu_elevation</item>
+ </style>
</resources> \ No newline at end of file
diff --git a/java/com/android/incallui/videotech/ims/ImsVideoTech.java b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
index d9660e192..c4c177f39 100644
--- a/java/com/android/incallui/videotech/ims/ImsVideoTech.java
+++ b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
@@ -203,6 +203,7 @@ public class ImsVideoTech implements VideoTech {
LogUtil.enterBlock("ImsVideoTech.declineUpgradeRequest");
call.getVideoCall()
.sendSessionModifyResponse(new VideoProfile(call.getDetails().getVideoState()));
+ setSessionModificationState(SessionModificationState.NO_REQUEST);
logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_DECLINED);
}
diff --git a/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
index cbf165753..5c5bae547 100644
--- a/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
+++ b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
@@ -295,7 +295,7 @@ public class GetTranscriptReceiver extends BroadcastReceiver {
transcriptionClientFactoryForTesting = factory;
}
- private static TranscriptionClientFactory getTranscriptionClientFactory(Context context) {
+ static TranscriptionClientFactory getTranscriptionClientFactory(Context context) {
if (transcriptionClientFactoryForTesting != null) {
return transcriptionClientFactoryForTesting;
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
index f6035fd2c..034af6bfc 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
@@ -83,6 +83,16 @@ public class TranscriptionTaskAsync extends TranscriptionTask {
} else if (uploadResponse == null) {
VvmLog.i(TAG, "getTranscription, failed to upload voicemail.");
return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+ } else if (uploadResponse.isStatusAlreadyExists()) {
+ VvmLog.i(TAG, "getTranscription, transcription already exists.");
+ GetTranscriptReceiver.beginPolling(
+ context,
+ voicemailUri,
+ uploadRequest.getTranscriptionId(),
+ 0,
+ configProvider,
+ phoneAccountHandle);
+ return new Pair<>(null, null);
} else if (uploadResponse.getTranscriptionId() == null) {
VvmLog.i(TAG, "getTranscription, upload error: " + uploadResponse.status);
return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
@@ -116,7 +126,9 @@ public class TranscriptionTaskAsync extends TranscriptionTask {
// Generate the transcript id locally if configured to do so, or if voicemail donation is
// available (because rating donating voicemails requires locally generated voicemail ids).
if (configProvider.useClientGeneratedVoicemailIds()
- || configProvider.isVoicemailDonationAvailable()) {
+ || VoicemailComponent.get(context)
+ .getVoicemailClient()
+ .isVoicemailDonationAvailable(context, phoneAccountHandle)) {
// The server currently can't handle repeated transcription id's so if we add the Uri to the
// fingerprint (which contains the voicemail id) which is different each time a voicemail is
// downloaded. If this becomes a problem then it should be possible to change the server
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
index ae4796dea..bd65abe84 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
@@ -18,6 +18,7 @@ package com.android.voicemail.impl.transcribe.grpc;
import android.support.annotation.Nullable;
import com.android.dialer.common.Assert;
import io.grpc.Status;
+import io.grpc.Status.Code;
/**
* Base class for encapulating a voicemail transcription server response. This handles the Grpc
@@ -43,6 +44,14 @@ public abstract class TranscriptionResponse {
return false;
}
+ public boolean isStatusAlreadyExists() {
+ if (status != null) {
+ return status.getCode() == Code.ALREADY_EXISTS;
+ }
+
+ return false;
+ }
+
public boolean hasFatalError() {
if (status != null) {
return status.getCode() != Status.Code.OK && status.getCode() != Status.Code.UNAVAILABLE;