diff options
28 files changed, 693 insertions, 71 deletions
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java index 680424a78..bca4265b6 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java @@ -279,7 +279,7 @@ public class PhoneCallDetailsHelper return; } - if (PhoneNumberUtils.isEmergencyNumber(details.number.toString())) { + if (PhoneNumberUtils.isEmergencyNumber(details.displayNumber)) { views.nameView.setText(R.string.emergency_number); views.nameView.setTextDirection(View.TEXT_DIRECTION_INHERIT); return; diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java index ed09eea68..6b1a9e1f5 100644 --- a/java/com/android/dialer/calllog/database/Coalescer.java +++ b/java/com/android/dialer/calllog/database/Coalescer.java @@ -18,6 +18,7 @@ package com.android.dialer.calllog.database; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; +import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; import android.telecom.PhoneAccountHandle; @@ -151,7 +152,6 @@ public class Coalescer { TelecomUtil.composePhoneAccountHandle( row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME), row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_ID)); - if (!Objects.equals(phoneAccount1, phoneAccount2)) { return false; } @@ -161,7 +161,7 @@ public class Coalescer { return false; } - if (!meetsAssistedDialingCriteria(row1, row2)) { + if (!meetsCallFeatureCriteria(row1, row2)) { return false; } @@ -185,20 +185,25 @@ public class Coalescer { } /** - * Returns a boolean indicating whether or not FEATURES_ASSISTED_DIALING is mutually exclusive - * between two rows. + * Returns true if column {@link AnnotatedCallLog#FEATURES} of the two given rows indicate that + * they can be coalesced. */ - private static boolean meetsAssistedDialingCriteria(ContentValues row1, ContentValues row2) { - int row1Assisted = - row1.getAsInteger(AnnotatedCallLog.FEATURES) - & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING; - int row2Assisted = - row2.getAsInteger(AnnotatedCallLog.FEATURES) - & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING; - - // FEATURES_ASSISTED_DIALING should not be combined with calls that are - // !FEATURES_ASSISTED_DIALING - return row1Assisted == row2Assisted; + private static boolean meetsCallFeatureCriteria(ContentValues row1, ContentValues row2) { + int row1Features = row1.getAsInteger(AnnotatedCallLog.FEATURES); + int row2Features = row2.getAsInteger(AnnotatedCallLog.FEATURES); + + // A row with FEATURES_ASSISTED_DIALING should not be combined with one without it. + if ((row1Features & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING) + != (row2Features & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING)) { + return false; + } + + // A video call should not be combined with one that is not a video call. + if ((row1Features & Calls.FEATURES_VIDEO) != (row2Features & Calls.FEATURES_VIDEO)) { + return false; + } + + return true; } /** diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java index aeb69a7a8..e316f66b5 100644 --- a/java/com/android/dialer/calllog/ui/menu/Modules.java +++ b/java/com/android/dialer/calllog/ui/menu/Modules.java @@ -27,8 +27,10 @@ import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.calllog.model.CoalescedRow; import com.android.dialer.calllogutils.CallLogEntryText; import com.android.dialer.calllogutils.NumberAttributesConverter; +import com.android.dialer.duo.DuoConstants; import com.android.dialer.glidephotomanager.PhotoInfo; import com.android.dialer.historyitemactions.DividerModule; +import com.android.dialer.historyitemactions.DuoCallModule; import com.android.dialer.historyitemactions.HistoryItemActionModule; import com.android.dialer.historyitemactions.IntentModule; import com.android.dialer.historyitemactions.SharedModules; @@ -134,9 +136,15 @@ final class Modules { // Add a video item if (1) the call log entry is for a video call, and (2) the call is not spam. if ((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO && !row.getNumberAttributes().getIsSpam()) { + boolean isDuoCall = + DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME + .flattenToString() + .equals(row.getPhoneAccountComponentName()); modules.add( - IntentModule.newVideoCallModule( - context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); + isDuoCall + ? new DuoCallModule(context, normalizedNumber, CallInitiationType.Type.CALL_LOG) + : IntentModule.newCarrierVideoCallModule( + context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); } // TODO(zachh): Also show video option if the call log entry is for an audio call but video diff --git a/java/com/android/dialer/calllogutils/res/values/strings.xml b/java/com/android/dialer/calllogutils/res/values/strings.xml index bc19ce22a..1c1f31446 100644 --- a/java/com/android/dialer/calllogutils/res/values/strings.xml +++ b/java/com/android/dialer/calllogutils/res/values/strings.xml @@ -131,7 +131,7 @@ <!-- 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 carrier video call. [CHAR LIMIT=30] --> + <!-- Text to show in call log for a carrier video call. [CHAR LIMIT=31] --> <string name="new_call_log_carrier_video">Carrier video</string> <!-- Text to show in call log for a duo video call. [CHAR LIMIT=30] --> @@ -145,4 +145,4 @@ <!-- String used to display calls from spam numbers in the call log. [CHAR LIMIT=30] --> <string name="new_call_log_secondary_spam">Spam</string> -</resources>
\ No newline at end of file +</resources> diff --git a/java/com/android/dialer/constants/ActivityRequestCodes.java b/java/com/android/dialer/constants/ActivityRequestCodes.java index 66c38fa64..7fd619ba9 100644 --- a/java/com/android/dialer/constants/ActivityRequestCodes.java +++ b/java/com/android/dialer/constants/ActivityRequestCodes.java @@ -16,8 +16,6 @@ package com.android.dialer.constants; -import com.android.dialer.duo.Duo; - /** * Class containing {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)} * request codes. @@ -32,7 +30,9 @@ public final class ActivityRequestCodes { /** Request code for {@link com.android.dialer.callcomposer.CallComposerActivity} intent. */ public static final int DIALTACTS_CALL_COMPOSER = 2; - /** Request code for {@link Duo#getIntent(android.content.Context, String)}. */ + /** + * Request code for {@link com.android.dialer.duo.Duo#getIntent(android.content.Context, String)}. + */ public static final int DIALTACTS_DUO = 3; /** Request code for {@link com.android.dialer.calldetails.OldCallDetailsActivity} intent. */ diff --git a/java/com/android/dialer/database/DialerDatabaseHelper.java b/java/com/android/dialer/database/DialerDatabaseHelper.java index efff11ecc..b172d7039 100644 --- a/java/com/android/dialer/database/DialerDatabaseHelper.java +++ b/java/com/android/dialer/database/DialerDatabaseHelper.java @@ -32,21 +32,23 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Directory; -import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.text.TextUtils; import com.android.contacts.common.R; import com.android.contacts.common.util.StopWatch; import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.common.concurrent.DialerFutureSerializer; import com.android.dialer.common.database.Selection; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; import com.android.dialer.smartdial.util.SmartDialNameMatcher; import com.android.dialer.smartdial.util.SmartDialPrefix; import com.android.dialer.util.PermissionsUtil; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; @@ -85,6 +87,8 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { private static final int MAX_ENTRIES = 20; private final Context context; + private final DialerFutureSerializer dialerFutureSerializer = new DialerFutureSerializer(); + private boolean isTestInstance = false; protected DialerDatabaseHelper(Context context, String databaseName, int dbVersion) { @@ -344,11 +348,19 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { */ public void startSmartDialUpdateThread(boolean forceUpdate) { if (PermissionsUtil.hasContactsReadPermissions(context)) { - DialerExecutorComponent.get(context) - .dialerExecutorFactory() - .createNonUiTaskBuilder(new UpdateSmartDialWorker()) - .build() - .executeParallel(forceUpdate); + Futures.addCallback( + // Serialize calls to updateSmartDialDatabase. Use FutureSerializer instead of + // synchronizing on the method to prevent deadlocking thread pool. FutureSerializer + // provides the guarantee that the next AsyncCallable won't even be submitted until the + // ListenableFuture returned by the previous one completes. See a bug. + dialerFutureSerializer.submit( + () -> { + updateSmartDialDatabase(forceUpdate); + return null; + }, + DialerExecutorComponent.get(context).backgroundExecutor()), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); } } @@ -657,7 +669,7 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { * @param forceUpdate If set to true, update the database by reloading all contacts. */ @WorkerThread - public synchronized void updateSmartDialDatabase(boolean forceUpdate) { + public void updateSmartDialDatabase(boolean forceUpdate) { LogUtil.enterBlock("DialerDatabaseHelper.updateSmartDialDatabase"); final SQLiteDatabase db = getWritableDatabase(); @@ -1296,14 +1308,4 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { return false; } } - - private class UpdateSmartDialWorker implements Worker<Boolean, Void> { - - @Nullable - @Override - public Void doInBackground(Boolean forceUpdate) throws Throwable { - updateSmartDialDatabase(forceUpdate); - return null; - } - } } diff --git a/java/com/android/dialer/duo/PlaceDuoCallNotifier.java b/java/com/android/dialer/duo/PlaceDuoCallNotifier.java new file mode 100644 index 000000000..8fde981a0 --- /dev/null +++ b/java/com/android/dialer/duo/PlaceDuoCallNotifier.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.duo; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; +import com.android.dialer.common.LogUtil; + +/** Notifies that a Duo video call should be started. */ +public final class PlaceDuoCallNotifier { + + private PlaceDuoCallNotifier() {} + + /** + * Broadcasts an intent notifying that a Duo call should be started. + * + * <p>See {@link PlaceDuoCallReceiver} for how the intent is handled. + * + * @param phoneNumber The number to start a Duo call. It can be of any format. + */ + public static void notify(Context context, String phoneNumber) { + LogUtil.enterBlock("PlaceDuoCallNotifier.notify"); + + Intent intent = new Intent(); + intent.setAction(PlaceDuoCallReceiver.ACTION_START_DUO_CALL); + intent.putExtra(PlaceDuoCallReceiver.EXTRA_PHONE_NUMBER, phoneNumber); + + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } +} diff --git a/java/com/android/dialer/duo/PlaceDuoCallReceiver.java b/java/com/android/dialer/duo/PlaceDuoCallReceiver.java new file mode 100644 index 000000000..913132c88 --- /dev/null +++ b/java/com/android/dialer/duo/PlaceDuoCallReceiver.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.duo; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.constants.ActivityRequestCodes; + +/** A {@link BroadcastReceiver} that starts a Duo video call. */ +public final class PlaceDuoCallReceiver extends BroadcastReceiver { + + static final String ACTION_START_DUO_CALL = "start_duo_call"; + static final String EXTRA_PHONE_NUMBER = "phone_number"; + + /** + * {@link Activity} needed to launch Duo. + * + * <p>A Duo call can only be placed via {@link Activity#startActivityForResult(Intent, int)}. + */ + private final Activity activity; + + /** Returns an {@link IntentFilter} containing all actions accepted by this broadcast receiver. */ + public static IntentFilter getIntentFilter() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_START_DUO_CALL); + return intentFilter; + } + + public PlaceDuoCallReceiver(Activity activity) { + this.activity = activity; + } + + @Override + public void onReceive(Context context, Intent intent) { + LogUtil.enterBlock("PlaceDuoCallReceiver.onReceive"); + + String action = intent.getAction(); + + switch (Assert.isNotNull(action)) { + case ACTION_START_DUO_CALL: + startDuoCall(context, intent); + break; + default: + throw new IllegalStateException("Unsupported action: " + action); + } + } + + private void startDuoCall(Context context, Intent intent) { + LogUtil.enterBlock("PlaceDuoCallReceiver.startDuoCall"); + + Assert.checkArgument(intent.hasExtra(EXTRA_PHONE_NUMBER)); + String phoneNumber = intent.getStringExtra(EXTRA_PHONE_NUMBER); + + Duo duo = DuoComponent.get(context).getDuo(); + activity.startActivityForResult( + duo.getIntent(context, phoneNumber), ActivityRequestCodes.DIALTACTS_DUO); + } +} diff --git a/java/com/android/dialer/historyitemactions/DuoCallModule.java b/java/com/android/dialer/historyitemactions/DuoCallModule.java new file mode 100644 index 000000000..b0d6a11fc --- /dev/null +++ b/java/com/android/dialer/historyitemactions/DuoCallModule.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.historyitemactions; + +import android.Manifest.permission; +import android.content.Context; +import android.support.annotation.RequiresPermission; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.duo.Duo; +import com.android.dialer.duo.DuoComponent; +import com.android.dialer.duo.PlaceDuoCallNotifier; +import com.android.dialer.precall.PreCall; + +/** {@link HistoryItemActionModule} for making a Duo call. */ +public class DuoCallModule implements HistoryItemActionModule { + + private final Context context; + private final String phoneNumber; + private final CallInitiationType.Type callInitiationType; + + /** + * Creates a module for making a Duo call. + * + * @param phoneNumber The number to start a Duo call. It can be of any format. + */ + public DuoCallModule( + Context context, String phoneNumber, CallInitiationType.Type callInitiationType) { + this.context = context; + this.phoneNumber = phoneNumber; + this.callInitiationType = callInitiationType; + } + + @Override + public int getStringId() { + return R.string.video_call; + } + + @Override + public int getDrawableId() { + return R.drawable.quantum_ic_videocam_vd_white_24; + } + + @Override + @RequiresPermission(permission.READ_PHONE_STATE) + public boolean onClick() { + if (canPlaceDuoCall(context, phoneNumber)) { + PlaceDuoCallNotifier.notify(context, phoneNumber); + } else { + // If a Duo call can't be placed, fall back to an IMS video call. + PreCall.start( + context, new CallIntentBuilder(phoneNumber, callInitiationType).setIsVideoCall(true)); + } + + return true; // Close the bottom sheet. + } + + private boolean canPlaceDuoCall(Context context, String phoneNumber) { + Duo duo = DuoComponent.get(context).getDuo(); + + return duo.isInstalled(context) + && duo.isEnabled(context) + && duo.isActivated(context) + && duo.isReachable(context, phoneNumber); + } +} diff --git a/java/com/android/dialer/historyitemactions/IntentModule.java b/java/com/android/dialer/historyitemactions/IntentModule.java index efb10e8bb..a5236c57a 100644 --- a/java/com/android/dialer/historyitemactions/IntentModule.java +++ b/java/com/android/dialer/historyitemactions/IntentModule.java @@ -77,7 +77,7 @@ public class IntentModule implements HistoryItemActionModule { R.drawable.quantum_ic_call_white_24); } - public static IntentModule newVideoCallModule( + public static IntentModule newCarrierVideoCallModule( Context context, String number, @Nullable PhoneAccountHandle phoneAccountHandle, diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index 2046b048f..3f660f56c 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -24,6 +24,7 @@ import com.android.dialer.blockreportspam.ShowBlockReportSpamDialogReceiver; import com.android.dialer.calllog.config.CallLogConfigComponent; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.duo.PlaceDuoCallReceiver; import com.android.dialer.interactions.PhoneNumberInteraction.DisambigDialogDismissedListener; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorListener; @@ -47,6 +48,9 @@ public class MainActivity extends TransactionSafeActivity */ private ShowBlockReportSpamDialogReceiver showBlockReportSpamDialogReceiver; + /** {@link android.content.BroadcastReceiver} that starts a Duo call. */ + private PlaceDuoCallReceiver placeDuoCallReceiver; + public static Intent getShowCallLogIntent(Context context) { return getShowTabIntent(context, TabIndex.CALL_LOG); } @@ -79,6 +83,7 @@ public class MainActivity extends TransactionSafeActivity activePeer.onActivityCreate(savedInstanceState); showBlockReportSpamDialogReceiver = new ShowBlockReportSpamDialogReceiver(getFragmentManager()); + placeDuoCallReceiver = new PlaceDuoCallReceiver(/* activity = */ this); } protected MainActivityPeer getNewPeer() { @@ -104,6 +109,8 @@ public class MainActivity extends TransactionSafeActivity LocalBroadcastManager.getInstance(this) .registerReceiver( showBlockReportSpamDialogReceiver, ShowBlockReportSpamDialogReceiver.getIntentFilter()); + LocalBroadcastManager.getInstance(this) + .registerReceiver(placeDuoCallReceiver, PlaceDuoCallReceiver.getIntentFilter()); } @Override @@ -118,6 +125,7 @@ public class MainActivity extends TransactionSafeActivity activePeer.onActivityPause(); LocalBroadcastManager.getInstance(this).unregisterReceiver(showBlockReportSpamDialogReceiver); + LocalBroadcastManager.getInstance(this).unregisterReceiver(placeDuoCallReceiver); } @Override diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 6d78a5171..c15d7c1a8 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -68,10 +68,12 @@ import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.calldetails.OldCallDetailsActivity; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.CallSpecificAppData; +import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.config.CallLogConfigComponent; import com.android.dialer.calllog.ui.NewCallLogFragment; import com.android.dialer.common.FragmentUtils.FragmentUtilListener; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.common.concurrent.UiListener; @@ -96,6 +98,7 @@ import com.android.dialer.main.MainActivityPeer; import com.android.dialer.main.impl.bottomnav.BottomNavBar; import com.android.dialer.main.impl.bottomnav.BottomNavBar.OnBottomNavTabSelectedListener; import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; +import com.android.dialer.main.impl.bottomnav.MissedCallCountObserver; import com.android.dialer.main.impl.toolbar.MainToolbar; import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; @@ -113,7 +116,9 @@ import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandle import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker; import com.android.dialer.voicemailstatus.VoicemailStatusHelper; import com.android.voicemail.VoicemailComponent; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -184,7 +189,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private BottomNavBar bottomNav; private MainBottomNavBarBottomNavTabListener bottomNavTabListener; private View snackbarContainer; + private MissedCallCountObserver missedCallCountObserver; private UiListener<String> getLastOutgoingCallListener; + private UiListener<Integer> missedCallObserverUiListener; public static Intent getShowTabIntent(Context context, @TabIndex int tabIndex) { Intent intent = new Intent(context, MainActivity.class); @@ -219,6 +226,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen getLastOutgoingCallListener = DialerExecutorComponent.get(activity) .createUiListener(activity.getFragmentManager(), "Query last phone number"); + missedCallObserverUiListener = + DialerExecutorComponent.get(activity) + .createUiListener(activity.getFragmentManager(), "Missed call observer"); } private void initLayout(Bundle savedInstanceState) { @@ -247,9 +257,13 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen boolean showVoicemailTab = canVoicemailTabBeShown(activity); bottomNav.showVoicemail(showVoicemailTab); + missedCallCountObserver = + new MissedCallCountObserver( + activity.getApplicationContext(), bottomNav, missedCallObserverUiListener); + callLogFragmentListener = new MainCallLogFragmentListener( - activity, activity.getContentResolver(), bottomNav, toolbar); + activity, activity.getContentResolver(), bottomNav, toolbar, bottomNavTabListener); bottomNav.addOnTabSelectedListener(callLogFragmentListener); searchController = @@ -471,6 +485,13 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen */ bottomNavTabListener.ensureCorrectCallLogShown(); + if (bottomNavTabListener.newCallLogFragmentActive()) { + missedCallCountObserver.onChange(false); // Set the initial value for the badge + activity + .getContentResolver() + .registerContentObserver(Calls.CONTENT_URI, true, missedCallCountObserver); + } + // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. ThreadUtil.postDelayedOnUiThread( () -> @@ -489,6 +510,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen public void onActivityPause() { searchController.onActivityPause(); LocalBroadcastManager.getInstance(activity).unregisterReceiver(disableNewCallLogReceiver); + activity.getContentResolver().unregisterContentObserver(missedCallCountObserver); } @Override @@ -819,6 +841,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private final Context context; private final BottomNavBar bottomNavBar; private final Toolbar toolbar; + private final MainBottomNavBarBottomNavTabListener bottomNavTabListener; private @TabIndex int currentTab = TabIndex.SPEED_DIAL; private long timeSelected = -1; @@ -837,11 +860,13 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar, - Toolbar toolbar) { + Toolbar toolbar, + MainBottomNavBarBottomNavTabListener bottomNavTabListener) { callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this); this.context = context; this.bottomNavBar = bottomNavBar; this.toolbar = toolbar; + this.bottomNavTabListener = bottomNavTabListener; } private void registerVoicemailStatusContentObserver(Context context) { @@ -953,8 +978,15 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } private void markMissedCallsAsReadAndRemoveNotification() { - callLogQueryHandler.markMissedCallsAsRead(); - CallLogNotificationsService.cancelAllMissedCalls(context); + if (bottomNavTabListener.newCallLogFragmentActive()) { + Futures.addCallback( + CallLogComponent.get(context).getClearMissedCalls().clearAll(), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); + } else { + callLogQueryHandler.markMissedCallsAsRead(); + CallLogNotificationsService.cancelAllMissedCalls(context); + } } private void setCurrentTab(@TabIndex int tabIndex) { @@ -969,7 +1001,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen activityIsAlive = true; registerVoicemailStatusContentObserver(context); callLogQueryHandler.fetchVoicemailStatus(); - callLogQueryHandler.fetchMissedCallsUnreadCount(); + if (!bottomNavTabListener.newCallLogFragmentActive()) { + callLogQueryHandler.fetchMissedCallsUnreadCount(); + } // Reset the tab on resume to restart the timer setCurrentTab(bottomNavBar.getSelectedTab()); } @@ -978,7 +1012,11 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen public void onActivityStop(boolean changingConfigurations, boolean keyguardLocked) { context.getContentResolver().unregisterContentObserver(voicemailStatusObserver); activityIsAlive = false; - if (viewedCallLogTabPastTimeThreshold() && !changingConfigurations && !keyguardLocked) { + // The new call log fragment handles this on its own. + if (!bottomNavTabListener.newCallLogFragmentActive() + && viewedCallLogTabPastTimeThreshold() + && !changingConfigurations + && !keyguardLocked) { markMissedCallsAsReadAndRemoveNotification(); } } @@ -1239,6 +1277,14 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } } + boolean newCallLogFragmentActive() { + return supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null + || (fragmentManager.findFragmentByTag(CALL_LOG_TAG) == null + && CallLogConfigComponent.get(activity) + .callLogConfig() + .isNewCallLogFragmentEnabled()); + } + @Override public void onContactsSelected() { LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onContactsSelected"); diff --git a/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java b/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java new file mode 100644 index 000000000..276063474 --- /dev/null +++ b/java/com/android/dialer/main/impl/bottomnav/MissedCallCountObserver.java @@ -0,0 +1,79 @@ +/* + * 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.main.impl.bottomnav; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.provider.CallLog.Calls; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.common.concurrent.UiListener; +import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; +import com.android.dialer.util.PermissionsUtil; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Observes the call log and updates the badge count to show the number of unread missed calls. + * + * <p>Used only when the new call log fragment is enabled. + */ +public final class MissedCallCountObserver extends ContentObserver { + private final Context appContext; + private final BottomNavBar bottomNavBar; + private final UiListener<Integer> uiListener; + + public MissedCallCountObserver( + Context appContext, BottomNavBar bottomNavBar, UiListener<Integer> uiListener) { + super(null); + this.appContext = appContext; + this.bottomNavBar = bottomNavBar; + this.uiListener = uiListener; + } + + @SuppressLint("MissingPermission") + @Override + public void onChange(boolean selfChange) { + ListenableFuture<Integer> countFuture = + DialerExecutorComponent.get(appContext) + .backgroundExecutor() + .submit( + () -> { + if (!PermissionsUtil.hasCallLogReadPermissions(appContext)) { + return 0; + } + try (Cursor cursor = + appContext + .getContentResolver() + .query( + Calls.CONTENT_URI, + new String[] {Calls._ID}, + Calls.IS_READ + " = ? AND " + Calls.TYPE + " = ?", + new String[] {"0", Integer.toString(Calls.MISSED_TYPE)}, + /* sortOrder= */ null)) { + return cursor == null ? 0 : cursor.getCount(); + } + }); + uiListener.listen( + appContext, + countFuture, + count -> bottomNavBar.setNotificationCount(TabIndex.CALL_LOG, count == null ? 0 : count), + throwable -> { + throw new RuntimeException(throwable); + }); + } +} diff --git a/java/com/android/dialer/oem/AndroidManifest.xml b/java/com/android/dialer/oem/AndroidManifest.xml index a781521c6..94cd4fcba 100644 --- a/java/com/android/dialer/oem/AndroidManifest.xml +++ b/java/com/android/dialer/oem/AndroidManifest.xml @@ -19,5 +19,5 @@ <uses-permission android:name="com.cequint.ecid.CALLER_ID_LOOKUP"/> <!-- This is used by MotorolaInCallUiNotifier to send broadcasts. --> - <uses-permission android:name="com.motorola.incallui.action.INCOMING_CALL_VISIBILITY_CHANGED"/> -</manifest>
\ No newline at end of file + <uses-permission android:name="com.motorola.incallui.permission.INCOMING_CALL_VISIBILITY_CHANGED"/> +</manifest> diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java index 70ad1b2e7..b7d31c0e9 100644 --- a/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java +++ b/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java @@ -18,7 +18,6 @@ package com.android.dialer.rootcomponentgenerator.processor; import static javax.tools.Diagnostic.Kind.ERROR; -import com.android.dialer.rootcomponentgenerator.annotation.DialerComponent; import com.android.dialer.rootcomponentgenerator.annotation.InstallIn; import com.android.dialer.rootcomponentgenerator.annotation.RootComponentGeneratorMetadata; import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; @@ -26,6 +25,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.TypeSpec; +import dagger.Subcomponent; import java.lang.annotation.Annotation; import java.util.Collections; import java.util.Set; @@ -33,7 +33,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; /** - * Genereates metadata for every type annotated by {@link InstallIn} and {@link DialerComponent}. + * Genereates metadata for every type annotated by {@link InstallIn} and {@link Subcomponent}. * * <p>The metadata has the information where the annotated types are and it is used by annotation * processor when the processor tries to generate root component. @@ -48,15 +48,15 @@ final class MetadataGeneratingStep implements ProcessingStep { @Override public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(DialerComponent.class, InstallIn.class); + return ImmutableSet.of(Subcomponent.class, InstallIn.class); } @Override public Set<? extends Element> process( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { - for (Element element : elementsByAnnotation.get(DialerComponent.class)) { - generateMetadataFor(DialerComponent.class, element); + for (Element element : elementsByAnnotation.get(Subcomponent.class)) { + generateMetadataFor(Subcomponent.class, element); } for (Element element : elementsByAnnotation.get(InstallIn.class)) { if (element.getAnnotation(InstallIn.class).variants().length == 0) { diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java index 86a030856..9b97adafd 100644 --- a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java +++ b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java @@ -20,7 +20,6 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.getAnnotationMirror; import static com.google.auto.common.MoreElements.isAnnotationPresent; -import com.android.dialer.rootcomponentgenerator.annotation.DialerComponent; import com.android.dialer.rootcomponentgenerator.annotation.DialerRootComponent; import com.android.dialer.rootcomponentgenerator.annotation.DialerVariant; import com.android.dialer.rootcomponentgenerator.annotation.InstallIn; @@ -36,6 +35,7 @@ import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeSpec; import dagger.Component; +import dagger.Subcomponent; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; @@ -90,11 +90,7 @@ final class RootComponentGeneratingStep implements ProcessingStep { .addAnnotation(Singleton.class); for (TypeElement componentWithSuperInterface : componentList) { rootComponentClassBuilder.addSuperinterface( - ClassName.get(componentWithSuperInterface) - .peerClass( - RootComponentUtils.GENERATED_COMPONENT_PREFIX - + componentWithSuperInterface.getSimpleName()) - .nestedClass("HasComponent")); + ClassName.get(componentWithSuperInterface).nestedClass("HasComponent")); } AnnotationSpec.Builder componentAnnotation = AnnotationSpec.builder(Component.class); for (TypeElement annotatedElement : componentModuleMap.get(dialerVariant)) { @@ -108,7 +104,7 @@ final class RootComponentGeneratingStep implements ProcessingStep { private List<TypeElement> generateComponentList() { List<TypeElement> list = new ArrayList<>(); - extractInfoFromMetadata(DialerComponent.class, list::add); + extractInfoFromMetadata(Subcomponent.class, list::add); return list; } diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java index 5e083d29d..56caa9ea4 100644 --- a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java +++ b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java @@ -29,7 +29,6 @@ public class RootComponentProcessor extends BasicAnnotationProcessor { @Override protected Iterable<? extends ProcessingStep> initSteps() { return ImmutableList.of( - new ComponentGeneratingStep(processingEnv), new MetadataGeneratingStep(processingEnv), new RootComponentGeneratingStep(processingEnv)); } diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java index f05ec202d..2608cb2aa 100644 --- a/java/com/android/dialer/telecom/TelecomUtil.java +++ b/java/com/android/dialer/telecom/TelecomUtil.java @@ -26,7 +26,6 @@ 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; @@ -300,11 +299,6 @@ 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 38c8da898..833460398 100644 --- a/java/com/android/incallui/CallButtonPresenter.java +++ b/java/com/android/incallui/CallButtonPresenter.java @@ -479,7 +479,7 @@ 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(); + boolean showUpgradeToRtt = call.canUpgradeToRttCall(); inCallButtonUi.showButton(InCallButtonIds.BUTTON_AUDIO, true); inCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP, showSwap); diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 1ba3683f0..65ef323fe 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -35,6 +35,7 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; @@ -1221,6 +1222,12 @@ public class InCallActivity extends TransactionSafeFragmentActivity fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI); } + public void showDialogForRttRequest(DialerCall call, int rttRequestId) { + LogUtil.enterBlock("InCallActivity.showDialogForRttRequest"); + DialogFragment fragment = RttRequestDialogFragment.newInstance(call.getId(), rttRequestId); + fragment.show(getSupportFragmentManager(), Tags.RTT_REQUEST_DIALOG); + } + @Override public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { super.onMultiWindowModeChanged(isInMultiWindowMode); @@ -1763,6 +1770,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity static final String RTT_CALL_SCREEN = "tag_rtt_call_screen"; static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment"; static final String SPEAK_EASY_SCREEN = "tag_speak_easy_screen"; + static final String RTT_REQUEST_DIALOG = "tag_rtt_request_dialog"; } private static final class ConfigNames { diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index e11b376c1..6300dac75 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -894,6 +894,13 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud } @Override + public void onUpgradeToRtt(DialerCall call, int rttRequestId) { + if (inCallActivity != null) { + inCallActivity.showDialogForRttRequest(call, rttRequestId); + } + } + + @Override public void onSessionModificationStateChange(DialerCall call) { int newState = call.getVideoTech().getSessionModificationState(); LogUtil.i("InCallPresenter.onSessionModificationStateChange", "state: %d", newState); diff --git a/java/com/android/incallui/RttRequestDialogFragment.java b/java/com/android/incallui/RttRequestDialogFragment.java new file mode 100644 index 000000000..fa9b0e5db --- /dev/null +++ b/java/com/android/incallui/RttRequestDialogFragment.java @@ -0,0 +1,149 @@ +/* + * 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.incallui; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.telephony.PhoneNumberUtils; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; +import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; +import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; +import java.lang.ref.WeakReference; + +/** Dialog that shown to user when receiving RTT request mid call. */ +public class RttRequestDialogFragment extends DialogFragment { + + /** + * Returns a new instance of {@link RttRequestDialogFragment} with the given callback. + * + * <p>Prefer this method over the default constructor. + */ + public static RttRequestDialogFragment newInstance(String callId, int rttRequestId) { + RttRequestDialogFragment fragment = new RttRequestDialogFragment(); + Bundle args = new Bundle(); + args.putString(ARG_CALL_ID, Assert.isNotNull(callId)); + args.putInt(ARG_RTT_REQUEST_ID, rttRequestId); + fragment.setArguments(args); + return fragment; + } + + /** Key in the arguments bundle for call id. */ + private static final String ARG_CALL_ID = "call_id"; + + private static final String ARG_RTT_REQUEST_ID = "rtt_request_id"; + + private TextView detailsTextView; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle bundle) { + super.onCreateDialog(bundle); + LogUtil.enterBlock("RttRequestDialogFragment.onCreateDialog"); + + View dialogView = View.inflate(getActivity(), R.layout.frag_rtt_request_dialog, null); + detailsTextView = dialogView.findViewById(R.id.details); + + ContactInfoCache cache = ContactInfoCache.getInstance(getContext()); + DialerCall dialerCall = + CallList.getInstance().getCallById(getArguments().getString(ARG_CALL_ID)); + cache.findInfo(dialerCall, false, new ContactLookupCallback(this)); + + dialogView + .findViewById(R.id.rtt_button_decline_request) + .setOnClickListener(v -> onNegativeButtonClick()); + dialogView + .findViewById(R.id.rtt_button_accept_request) + .setOnClickListener(v -> onPositiveButtonClick()); + + AlertDialog alertDialog = + new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme) + .setCancelable(false) + .setView(dialogView) + .setTitle(R.string.rtt_request_dialog_title) + .create(); + + alertDialog.setCanceledOnTouchOutside(false); + return alertDialog; + } + + private void onPositiveButtonClick() { + LogUtil.enterBlock("RttRequestDialogFragment.onPositiveButtonClick"); + + DialerCall call = CallList.getInstance().getCallById(getArguments().getString(ARG_CALL_ID)); + call.respondToRttRequest(true, getArguments().getInt(ARG_RTT_REQUEST_ID)); + dismiss(); + } + + private void onNegativeButtonClick() { + LogUtil.enterBlock("RttRequestDialogFragment.onNegativeButtonClick"); + + DialerCall call = CallList.getInstance().getCallById(getArguments().getString(ARG_CALL_ID)); + call.respondToRttRequest(false, getArguments().getInt(ARG_RTT_REQUEST_ID)); + dismiss(); + } + + private void setNameOrNumber(CharSequence nameOrNumber) { + detailsTextView.setText(getString(R.string.rtt_request_dialog_details, nameOrNumber)); + } + + private static class ContactLookupCallback implements ContactInfoCacheCallback { + private final WeakReference<RttRequestDialogFragment> rttRequestDialogFragmentWeakReference; + + private ContactLookupCallback(RttRequestDialogFragment rttRequestDialogFragment) { + rttRequestDialogFragmentWeakReference = new WeakReference<>(rttRequestDialogFragment); + } + + @Override + public void onContactInfoComplete(String callId, ContactCacheEntry entry) { + RttRequestDialogFragment fragment = rttRequestDialogFragmentWeakReference.get(); + if (fragment != null) { + fragment.setNameOrNumber(getNameOrNumber(entry, fragment.getContext())); + } + } + + private CharSequence getNameOrNumber(ContactCacheEntry entry, Context context) { + String preferredName = + ContactDisplayUtils.getPreferredDisplayName( + entry.namePrimary, + entry.nameAlternative, + ContactsPreferencesFactory.newContactsPreferences(context)); + if (TextUtils.isEmpty(preferredName)) { + return TextUtils.isEmpty(entry.number) + ? null + : PhoneNumberUtils.createTtsSpannable( + BidiFormatter.getInstance().unicodeWrap(entry.number, TextDirectionHeuristics.LTR)); + } + return preferredName; + } + + @Override + public void onImageLoadComplete(String callId, ContactCacheEntry entry) {} + } +} diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java index 01f3b9d29..eccdceeca 100644 --- a/java/com/android/incallui/call/CallList.java +++ b/java/com/android/incallui/call/CallList.java @@ -784,6 +784,12 @@ public class CallList implements DialerCallDelegate { */ void onUpgradeToVideo(DialerCall call); + /** + * Called when a new RTT call request comes in This is the only method that gets called for RTT + * requests. + */ + default void onUpgradeToRtt(DialerCall call, int rttRequestId) {} + /** Called when the session modification state of a call changes. */ void onSessionModificationStateChange(DialerCall call); @@ -855,6 +861,13 @@ public class CallList implements DialerCallDelegate { public void onDialerCallLastForwardedNumberChange() {} @Override + public void onDialerCallUpgradeToRtt(int rttRequestId) { + for (Listener listener : listeners) { + listener.onUpgradeToRtt(call, rttRequestId); + } + } + + @Override public void onDialerCallUpgradeToVideo() { for (Listener listener : listeners) { listener.onUpgradeToVideo(call); diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index e08c926d8..35f9481c5 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -282,6 +282,9 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa @Override public void onRttRequest(Call call, int id) { LogUtil.v("TelecomCallCallback.onRttRequest", "id=%d", id); + for (DialerCallListener listener : listeners) { + listener.onDialerCallUpgradeToRtt(id); + } } @Override @@ -1033,6 +1036,11 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa getTelecomCall().sendRttRequest(); } + @TargetApi(28) + public void respondToRttRequest(boolean accept, int rttRequestId) { + getTelecomCall().respondToRttRequest(rttRequestId, accept); + } + public boolean hasReceivedVideoUpgradeRequest() { return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState()); } diff --git a/java/com/android/incallui/call/DialerCallListener.java b/java/com/android/incallui/call/DialerCallListener.java index 5d24a4d4b..37c30d30c 100644 --- a/java/com/android/incallui/call/DialerCallListener.java +++ b/java/com/android/incallui/call/DialerCallListener.java @@ -29,6 +29,8 @@ public interface DialerCallListener { void onDialerCallUpgradeToVideo(); + default void onDialerCallUpgradeToRtt(int rttRequestId) {} + void onDialerCallSessionModificationStateChange(); void onWiFiToLteHandover(); diff --git a/java/com/android/incallui/res/layout/frag_rtt_request_dialog.xml b/java/com/android/incallui/res/layout/frag_rtt_request_dialog.xml new file mode 100644 index 000000000..ab743eb89 --- /dev/null +++ b/java/com/android/incallui/res/layout/frag_rtt_request_dialog.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/details" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="20dp" + android:paddingBottom="12dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" + android:textColor="#DE000000" + android:textSize="16sp"/> + + <TextView + android:id="@+id/more_information" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="16dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" + android:text="@string/rtt_request_dialog_more_information" + android:textColor="#8A000000" + android:textSize="14sp"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:orientation="horizontal"> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + <Button + android:id="@+id/rtt_button_decline_request" + style="@style/Widget.AppCompat.Button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:backgroundTint="@android:color/white" + android:fontFamily="sans-serif-medium" + android:stateListAnimator="@null" + android:text="@string/rtt_button_decline_request" + android:textColor="#757575"/> + <Button + android:id="@+id/rtt_button_accept_request" + style="@style/Widget.AppCompat.Button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:backgroundTint="@color/dialer_theme_color" + android:fontFamily="sans-serif-medium" + android:stateListAnimator="@null" + android:text="@string/rtt_button_accept_request" + android:textColor="@android:color/white"/> + </LinearLayout> + +</LinearLayout> + diff --git a/java/com/android/incallui/res/values/strings.xml b/java/com/android/incallui/res/values/strings.xml index 15cd2e02d..c7e5677c7 100644 --- a/java/com/android/incallui/res/values/strings.xml +++ b/java/com/android/incallui/res/values/strings.xml @@ -202,4 +202,19 @@ <!-- Text for bubble return-to-call button --> <string name="bubble_return_to_call">Back to call</string> + <!-- Title for RTT request dialog. [CHAR LIMIT=60] --> + <string name="rtt_request_dialog_title">Join RTT call?</string> + + <!-- Details for RTT request dialog. [CHAR LIMIT=NONE] --> + <string name="rtt_request_dialog_details"><xliff:g id="caller">%1$s</xliff:g> wants to use messaging within your voice call.</string> + + <!-- More information for RTT request dialog. [CHAR LIMIT=NONE] --> + <string name="rtt_request_dialog_more_information">RTT assists callers who are deaf, hard of hearing, have a speech disability, or need more than voice alone.</string> + + <!-- Text for button to decline RTT request. [CHAR LIMIT=20] --> + <string name="rtt_button_decline_request">No thanks</string> + + <!-- Text for button to accept RTT request. [CHAR LIMIT=20] --> + <string name="rtt_button_accept_request">Join RTT</string> + </resources> diff --git a/java/com/android/incallui/rtt/impl/res/values/strings.xml b/java/com/android/incallui/rtt/impl/res/values/strings.xml index b0ac2057e..1d09f5446 100644 --- a/java/com/android/incallui/rtt/impl/res/values/strings.xml +++ b/java/com/android/incallui/rtt/impl/res/values/strings.xml @@ -27,7 +27,7 @@ <!-- Text for back button. [CHAR LIMIT=20] --> <string name="rtt_button_back">Back</string> - <!-- Text for status banner. [CHAT LIMIT=100] --> + <!-- Text for status banner. [CHAR LIMIT=100] --> <string name="rtt_status_banner_text">Waiting for <xliff:g id="name">%s</xliff:g> to join RTT call…</string> </resources>
\ No newline at end of file |