diff options
15 files changed, 303 insertions, 59 deletions
diff --git a/java/com/android/dialer/speeddial/SpeedDialAdapter.java b/java/com/android/dialer/speeddial/SpeedDialAdapter.java index 8a37e97dd..a382b1a6b 100644 --- a/java/com/android/dialer/speeddial/SpeedDialAdapter.java +++ b/java/com/android/dialer/speeddial/SpeedDialAdapter.java @@ -34,10 +34,10 @@ import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener; import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener; import com.android.dialer.speeddial.draghelper.SpeedDialItemTouchHelperCallback.ItemTouchHelperAdapter; import com.android.dialer.speeddial.loader.SpeedDialUiItem; +import com.google.common.collect.ImmutableList; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -146,7 +146,13 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi public void setSpeedDialUiItems(List<SpeedDialUiItem> immutableSpeedDialUiItems) { speedDialUiItems = new ArrayList<>(); speedDialUiItems.addAll(immutableSpeedDialUiItems); - speedDialUiItems.sort((o1, o2) -> Boolean.compare(o2.isStarred(), o1.isStarred())); + speedDialUiItems.sort( + (o1, o2) -> { + if (o1.isStarred() && o2.isStarred()) { + return Integer.compare(o1.pinnedPosition().or(-1), o2.pinnedPosition().or(-1)); + } + return Boolean.compare(o2.isStarred(), o1.isStarred()); + }); positionToRowTypeMap.clear(); if (speedDialUiItems.isEmpty()) { return; @@ -168,6 +174,13 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi } } + public ImmutableList<SpeedDialUiItem> getSpeedDialUiItems() { + if (speedDialUiItems == null || speedDialUiItems.isEmpty()) { + return ImmutableList.of(); + } + return ImmutableList.copyOf(speedDialUiItems); + } + public SpanSizeLookup getSpanSizeLookup() { return new SpanSizeLookup() { @Override @@ -189,16 +202,9 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi @Override public void onItemMove(int fromPosition, int toPosition) { - if (fromPosition < toPosition) { - for (int i = fromPosition; i < toPosition && i < speedDialUiItems.size() - 1; i++) { - Collections.swap(speedDialUiItems, i, i + 1); - } - } else { - for (int i = fromPosition - 1; i > toPosition; i--) { - Collections.swap(speedDialUiItems, i, i - 1); - } - } - // TODO(calderwoodra): store pinned positions + // fromPosition/toPosition correspond to adapter position, which is off by 1 from the list + // position b/c of the favorites header. So subtract 1 here. + speedDialUiItems.add(toPosition - 1, speedDialUiItems.remove(fromPosition - 1)); notifyItemMoved(fromPosition, toPosition); } diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index b74c06239..b76db1cf3 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -36,6 +36,7 @@ import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.FragmentUtils; 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.SupportUiListener; import com.android.dialer.constants.ActivityRequestCodes; @@ -54,6 +55,7 @@ import com.android.dialer.speeddial.loader.SpeedDialUiItem; import com.android.dialer.speeddial.loader.UiItemLoaderComponent; import com.android.dialer.util.IntentUtil; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; /** * Fragment for displaying: @@ -143,7 +145,7 @@ public class SpeedDialFragment extends Fragment { speedDialLoaderListener.listen( getContext(), - UiItemLoaderComponent.get(getContext()).speedDialUiItemLoader().loadSpeedDialUiItems(), + UiItemLoaderComponent.get(getContext()).speedDialUiItemMutator().loadSpeedDialUiItems(), this::onSpeedDialUiItemListLoaded, throwable -> { throw new RuntimeException(throwable); @@ -158,7 +160,7 @@ public class SpeedDialFragment extends Fragment { speedDialLoaderListener.listen( getContext(), UiItemLoaderComponent.get(getContext()) - .speedDialUiItemLoader() + .speedDialUiItemMutator() .starContact(data.getData()), this::onSpeedDialUiItemListLoaded, throwable -> { @@ -173,7 +175,7 @@ public class SpeedDialFragment extends Fragment { // TODO(calderwoodra): Use DiffUtil to properly update and animate the change adapter.setSpeedDialUiItems( UiItemLoaderComponent.get(getContext()) - .speedDialUiItemLoader() + .speedDialUiItemMutator() .insertDuoChannels(getContext(), speedDialUiItems)); adapter.notifyDataSetChanged(); if (getActivity() != null) { @@ -187,6 +189,18 @@ public class SpeedDialFragment extends Fragment { super.onPause(); contextMenu.hideMenu(); contextMenuBackground.setVisibility(View.GONE); + Futures.addCallback( + DialerExecutorComponent.get(getContext()) + .backgroundExecutor() + .submit( + () -> { + UiItemLoaderComponent.get(getContext()) + .speedDialUiItemMutator() + .updatePinnedPosition(adapter.getSpeedDialUiItems()); + return null; + }), + new DefaultFutureCallback<>(), + DialerExecutorComponent.get(getContext()).backgroundExecutor()); } @Override diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntry.java b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java index 89aed8f37..181f9eca7 100644 --- a/java/com/android/dialer/speeddial/database/SpeedDialEntry.java +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java @@ -20,6 +20,7 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +36,9 @@ public abstract class SpeedDialEntry { @Nullable public abstract Long id(); + /** Position the contact is pinned to in the UI. Will be absent if it hasn't be set yet. */ + public abstract Optional<Integer> pinnedPosition(); + /** @see {@link Contacts#_ID} */ public abstract long contactId(); @@ -53,7 +57,7 @@ public abstract class SpeedDialEntry { public abstract Builder toBuilder(); public static Builder builder() { - return new AutoValue_SpeedDialEntry.Builder(); + return new AutoValue_SpeedDialEntry.Builder().setPinnedPosition(Optional.absent()); } /** Builder class for speed dial entry. */ @@ -62,6 +66,8 @@ public abstract class SpeedDialEntry { public abstract Builder setId(Long id); + public abstract Builder setPinnedPosition(Optional<Integer> pinnedPosition); + public abstract Builder setContactId(long contactId); public abstract Builder setLookupKey(String lookupKey); diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java index 544bb3613..1416a203d 100644 --- a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java @@ -25,6 +25,7 @@ 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 com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; @@ -38,12 +39,20 @@ import java.util.List; public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper implements SpeedDialEntryDao { + /** + * If the pinned position is absent, then we need to write an impossible value in the table like + * -1 so that it doesn't default to 0. When we read this value from the table, we'll translate it + * to Optional.absent() in the resulting {@link SpeedDialEntry}. + */ + private static final int PINNED_POSITION_ABSENT = -1; + private static final int DATABASE_VERSION = 2; 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 PINNED_POSITION = "pinned_position"; private static final String CONTACT_ID = "contact_id"; private static final String LOOKUP_KEY = "lookup_key"; private static final String PHONE_NUMBER = "phone_number"; @@ -53,12 +62,13 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper // 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_TYPE = 4; - private static final int POSITION_PHONE_LABEL = 5; - private static final int POSITION_PHONE_TECHNOLOGY = 6; + private static final int POSITION_PINNED_POSITION = 1; + private static final int POSITION_CONTACT_ID = 2; + private static final int POSITION_LOOKUP_KEY = 3; + private static final int POSITION_PHONE_NUMBER = 4; + private static final int POSITION_PHONE_TYPE = 5; + private static final int POSITION_PHONE_LABEL = 6; + private static final int POSITION_PHONE_TECHNOLOGY = 7; // Create Table Query private static final String CREATE_TABLE_SQL = @@ -66,6 +76,7 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper + TABLE_NAME + " (" + (ID + " integer primary key, ") + + (PINNED_POSITION + " integer, ") + (CONTACT_ID + " integer, ") + (LOOKUP_KEY + " text, ") + (PHONE_NUMBER + " text, ") @@ -119,11 +130,17 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper .build(); } + Optional<Integer> pinnedPosition = Optional.of(cursor.getInt(POSITION_PINNED_POSITION)); + if (pinnedPosition.or(PINNED_POSITION_ABSENT) == PINNED_POSITION_ABSENT) { + pinnedPosition = Optional.absent(); + } + SpeedDialEntry entry = SpeedDialEntry.builder() .setDefaultChannel(channel) .setContactId(cursor.getLong(POSITION_CONTACT_ID)) .setLookupKey(cursor.getString(POSITION_LOOKUP_KEY)) + .setPinnedPosition(pinnedPosition) .setId(cursor.getLong(POSITION_ID)) .build(); entries.add(entry); @@ -226,6 +243,7 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper if (includeId) { values.put(ID, entry.id()); } + values.put(PINNED_POSITION, entry.pinnedPosition().or(PINNED_POSITION_ABSENT)); values.put(CONTACT_ID, entry.contactId()); values.put(LOOKUP_KEY, entry.lookupKey()); if (entry.defaultChannel() != null) { diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java index 9bda3fb31..a2bdfb89a 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java @@ -25,6 +25,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.speeddial.database.SpeedDialEntry; import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; @@ -83,7 +84,9 @@ public abstract class SpeedDialUiItem { } public static Builder builder() { - return new AutoValue_SpeedDialUiItem.Builder().setChannels(ImmutableList.of()); + return new AutoValue_SpeedDialUiItem.Builder() + .setChannels(ImmutableList.of()) + .setPinnedPosition(Optional.absent()); } /** @@ -139,6 +142,7 @@ public abstract class SpeedDialUiItem { public SpeedDialEntry buildSpeedDialEntry() { return SpeedDialEntry.builder() .setId(speedDialEntryId()) + .setPinnedPosition(pinnedPosition()) .setLookupKey(lookupKey()) .setContactId(contactId()) .setDefaultChannel(defaultChannel()) @@ -212,6 +216,9 @@ public abstract class SpeedDialUiItem { @Nullable public abstract Long speedDialEntryId(); + /** @see SpeedDialEntry#pinnedPosition() */ + public abstract Optional<Integer> pinnedPosition(); + /** @see android.provider.ContactsContract.Contacts#DISPLAY_NAME */ public abstract String name(); @@ -255,6 +262,8 @@ public abstract class SpeedDialUiItem { /** Set to null if {@link #isStarred()} is false. */ public abstract Builder setSpeedDialEntryId(@Nullable Long id); + public abstract Builder setPinnedPosition(Optional<Integer> pinnedPosition); + public abstract Builder setName(String name); public abstract Builder setContactId(long contactId); diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java index 921468773..5dae2efab 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java @@ -17,11 +17,14 @@ package com.android.dialer.speeddial.loader; import android.annotation.TargetApi; +import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Context; +import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.Build.VERSION_CODES; +import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; @@ -43,6 +46,7 @@ import com.android.dialer.speeddial.database.SpeedDialEntry; import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; import com.android.dialer.speeddial.database.SpeedDialEntryDao; import com.android.dialer.speeddial.database.SpeedDialEntryDatabaseHelper; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ListenableFuture; @@ -76,7 +80,7 @@ import javax.inject.Singleton; @SuppressWarnings("AndroidApiChecker") @TargetApi(VERSION_CODES.N) @Singleton -public final class SpeedDialUiItemLoader { +public final class SpeedDialUiItemMutator { private static final int MAX_DUO_SUGGESTIONS = 3; @@ -87,7 +91,7 @@ public final class SpeedDialUiItemLoader { private final ContactsPreferences contactsPreferences; @Inject - public SpeedDialUiItemLoader( + public SpeedDialUiItemMutator( @ApplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutor) { this.appContext = appContext; @@ -127,7 +131,7 @@ public final class SpeedDialUiItemLoader { null, null)) { if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.insertNewContactEntry", "Cursor was null"); + LogUtil.e("SpeedDialUiItemMutator.insertNewContactEntry", "Cursor was null"); return loadSpeedDialUiItemsInternal(); } Assert.checkArgument(cursor.moveToFirst(), "Cursor should never be empty"); @@ -285,7 +289,7 @@ public final class SpeedDialUiItemLoader { null, null)) { if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.updateContactIdsAndLookupKeys", "null cursor"); + LogUtil.e("SpeedDialUiItemMutator.updateContactIdsAndLookupKeys", "null cursor"); return new ArrayList<>(); } if (cursor.getCount() == 0) { @@ -339,9 +343,11 @@ public final class SpeedDialUiItemLoader { SpeedDialUiItem item = SpeedDialUiItem.fromCursor(cursor); for (SpeedDialEntry entry : entries) { if (entry.contactId() == item.contactId()) { - // Update the id to match it's corresponding SpeedDialEntry. + // Update the id and pinned position to match it's corresponding SpeedDialEntry. SpeedDialUiItem.Builder entrySpeedDialItem = - item.toBuilder().setSpeedDialEntryId(entry.id()); + item.toBuilder() + .setSpeedDialEntryId(entry.id()) + .setPinnedPosition(entry.pinnedPosition()); // Preserve the default channel if it didn't change/still exists Channel defaultChannel = entry.defaultChannel(); @@ -405,7 +411,7 @@ public final class SpeedDialUiItemLoader { .getContentResolver() .query(strequentUri, new String[] {Phone.CONTACT_ID}, null, null, null)) { if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.getStrequentContacts", "null cursor"); + LogUtil.e("SpeedDialUiItemMutator.getStrequentContacts", "null cursor"); return new ArrayList<>(); } if (cursor.getCount() == 0) { @@ -430,7 +436,7 @@ public final class SpeedDialUiItemLoader { null)) { List<SpeedDialUiItem> contacts = new ArrayList<>(); if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.getStrequentContacts", "null cursor"); + LogUtil.e("SpeedDialUiItemMutator.getStrequentContacts", "null cursor"); return new ArrayList<>(); } if (cursor.getCount() == 0) { @@ -444,6 +450,75 @@ public final class SpeedDialUiItemLoader { } /** + * Persists the position of the {@link SpeedDialUiItem items} as the pinned position according to + * the order they were passed in. + */ + @WorkerThread + public void updatePinnedPosition(List<SpeedDialUiItem> speedDialUiItems) { + Assert.isWorkerThread(); + if (speedDialUiItems == null || speedDialUiItems.isEmpty()) { + return; + } + + // Update the positions in the SpeedDialEntry database + ImmutableList.Builder<SpeedDialEntry> entriesToUpdate = ImmutableList.builder(); + for (int i = 0; i < speedDialUiItems.size(); i++) { + SpeedDialUiItem item = speedDialUiItems.get(i); + if (item.isStarred()) { + entriesToUpdate.add( + item.buildSpeedDialEntry().toBuilder().setPinnedPosition(Optional.of(i)).build()); + } + } + getSpeedDialEntryDao().update(entriesToUpdate.build()); + + // Update the positions in CP2 + // Build a list of SpeedDialUiItems where each contact is only represented once but the order + // is maintained. For example, assume you have a list of contacts with contact ids: + // > { 1, 1, 2, 1, 2, 3 } + // This list will be reduced to: + // > { 1, 2, 3 } + // and their positions in the resulting list will be written to the CP2 Contacts.PINNED column. + List<SpeedDialUiItem> cp2SpeedDialUiItems = new ArrayList<>(); + Set<Long> contactIds = new ArraySet<>(); + for (SpeedDialUiItem item : speedDialUiItems) { + if (contactIds.add(item.contactId())) { + cp2SpeedDialUiItems.add(item); + } + } + + // Code copied from PhoneFavoritesTileAdapter#handleDrop + ArrayList<ContentProviderOperation> operations = new ArrayList<>(); + for (int i = 0; i < cp2SpeedDialUiItems.size(); i++) { + SpeedDialUiItem item = cp2SpeedDialUiItems.get(i); + // Pinned positions in the database start from 1 instead of being zero-indexed like + // arrays, so offset by 1. + int databasePinnedPosition = i + 1; + if (item.pinnedPosition().isPresent() + && item.pinnedPosition().get() == databasePinnedPosition) { + continue; + } + + Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(item.contactId())); + ContentValues values = new ContentValues(); + values.put(Contacts.PINNED, databasePinnedPosition); + operations.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); + } + if (operations.isEmpty()) { + // Nothing to update + return; + } + try { + appContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); + // TODO(calderwoodra): log + } catch (RemoteException | OperationApplicationException e) { + LogUtil.e( + "SpeedDialUiItemMutator.updatePinnedPosition", + "Exception thrown when pinning contacts", + e); + } + } + + /** * Returns a new list with duo reachable channels inserted. Duo channels won't replace ViLTE * channels. */ diff --git a/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java b/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java index 7d01b4380..852908409 100644 --- a/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java +++ b/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java @@ -24,7 +24,7 @@ import dagger.Subcomponent; @Subcomponent public abstract class UiItemLoaderComponent { - public abstract SpeedDialUiItemLoader speedDialUiItemLoader(); + public abstract SpeedDialUiItemMutator speedDialUiItemMutator(); public static UiItemLoaderComponent get(Context context) { return ((UiItemLoaderComponent.HasComponent) diff --git a/java/com/android/incallui/AndroidManifest.xml b/java/com/android/incallui/AndroidManifest.xml index 832a5e874..7286b0db7 100644 --- a/java/com/android/incallui/AndroidManifest.xml +++ b/java/com/android/incallui/AndroidManifest.xml @@ -106,6 +106,13 @@ android:theme="@style/Theme.Incall.DialogHolder" /> + <activity + android:excludeFromRecents="true" + android:exported="false" + android:name="com.android.incallui.PostCharDialogActivity" + android:noHistory="true" + android:theme="@style/Theme.Incall.DialogHolder"/> + <!-- BroadcastReceiver for receiving Intents from Notification mechanism. --> <receiver android:directBootAware="true" diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 2b6c2b49d..02335b6e4 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -145,8 +145,6 @@ public class InCallActivity extends TransactionSafeFragmentActivity private View pseudoBlackScreenOverlay; private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment; private String dtmfTextToPrepopulate; - private String showPostCharWaitDialogCallId; - private String showPostCharWaitDialogChars; private boolean allowOrientationChange; private boolean animateDialpadOnShow; private boolean didShowAnswerScreen; @@ -160,7 +158,6 @@ public class InCallActivity extends TransactionSafeFragmentActivity private boolean isRecreating; // whether the activity is going to be recreated private boolean isVisible; private boolean needDismissPendingDialogs; - private boolean showPostCharWaitDialogOnResume; private boolean touchDownWhenPseudoScreenOff; private int[] backgroundDrawableColors; @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE; @@ -531,10 +528,6 @@ public class InCallActivity extends TransactionSafeFragmentActivity } updateNavigationBar(isDialpadVisible()); - if (showPostCharWaitDialogOnResume) { - showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars); - } - CallList.getInstance() .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false)); @@ -1016,18 +1009,8 @@ public class InCallActivity extends TransactionSafeFragmentActivity } public void showDialogForPostCharWait(String callId, String chars) { - if (isVisible) { - PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); - fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT); - - showPostCharWaitDialogOnResume = false; - showPostCharWaitDialogCallId = null; - showPostCharWaitDialogChars = null; - } else { - showPostCharWaitDialogOnResume = true; - showPostCharWaitDialogCallId = callId; - showPostCharWaitDialogChars = chars; - } + PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); + fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT); } public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) { diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index 526cc64d3..6e7daf551 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -1271,8 +1271,22 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud } public void onPostDialCharWait(String callId, String chars) { - if (isActivityStarted()) { + // If not visible, inCallActivity is stopped. Starting from P, calling recreate() will destroy + // the old activity instance and create a new instance immediately. Previously, the old activity + // went through its lifecycle from create to destroy before creating a new instance. + // So this case doesn't work now: make a call with char WAIT, leave in call UI, call gets + // connected, and go back to in call UI to see the dialog. + // So we should show dialog in an empty activity if inCallActivity is not visible. And it also + // helps with background calling. + if (isActivityStarted() && inCallActivity.isVisible()) { inCallActivity.showDialogForPostCharWait(callId, chars); + } else { + Intent intent = new Intent(context, PostCharDialogActivity.class); + // Prevent showing MainActivity with PostCharDialogActivity on above + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.putExtra(PostCharDialogActivity.EXTRA_CALL_ID, callId); + intent.putExtra(PostCharDialogActivity.EXTRA_POST_DIAL_STRING, chars); + context.startActivity(intent); } } diff --git a/java/com/android/incallui/PostCharDialogActivity.java b/java/com/android/incallui/PostCharDialogActivity.java new file mode 100644 index 000000000..dcdc9d663 --- /dev/null +++ b/java/com/android/incallui/PostCharDialogActivity.java @@ -0,0 +1,100 @@ +/* + * 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.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; + +/** + * Activity that contains an alert dialog with OK and Cancel buttons to allow user to Accept or + * Reject the WAIT inserted as part of the Dial string. + */ +public class PostCharDialogActivity extends AppCompatActivity implements CallList.Listener { + + public static final String EXTRA_CALL_ID = "extra_call_id"; + public static final String EXTRA_POST_DIAL_STRING = "extra_post_dial_string"; + private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi"; + + private String callId; + + @Override + protected void onCreate(@Nullable Bundle bundle) { + super.onCreate(bundle); + + callId = getIntent().getStringExtra(EXTRA_CALL_ID); + String postDialString = getIntent().getStringExtra(EXTRA_POST_DIAL_STRING); + if (callId == null || postDialString == null) { + finish(); + return; + } + + PostCharDialogFragment fragment = new PostCharDialogFragment(callId, postDialString); + fragment.show(getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI); + + CallList.getInstance().addListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + CallList.getInstance().removeListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + // We don't expect the activity to resume, except for orientation change. + if (!isChangingConfigurations()) { + finish(); + } + } + + @Override + public void onDisconnect(DialerCall call) { + if (callId.equals(call.getId())) { + finish(); + } + } + + @Override + public void onIncomingCall(DialerCall call) {} + + @Override + public void onUpgradeToVideo(DialerCall call) {} + + @Override + public void onUpgradeToRtt(DialerCall call, int rttRequestId) {} + + @Override + public void onSessionModificationStateChange(DialerCall call) {} + + @Override + public void onCallListChange(CallList callList) {} + + @Override + public void onWiFiToLteHandover(DialerCall call) {} + + @Override + public void onHandoverToWifiFailed(DialerCall call) {} + + @Override + public void onInternationalCallOnWifi(@NonNull DialerCall call) {} +} diff --git a/java/com/android/incallui/PostCharDialogFragment.java b/java/com/android/incallui/PostCharDialogFragment.java index 4bcc68e33..1d06fd487 100644 --- a/java/com/android/incallui/PostCharDialogFragment.java +++ b/java/com/android/incallui/PostCharDialogFragment.java @@ -55,7 +55,8 @@ public class PostCharDialogFragment extends DialogFragment { buf.append(getResources().getText(R.string.wait_prompt_str)); buf.append(postDialStr); - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final AlertDialog.Builder builder = + new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme); builder.setMessage(buf.toString()); builder.setPositiveButton( diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 9dfe7abcb..da05b9d2a 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -192,6 +192,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa private volatile boolean feedbackRequested = false; @Nullable private PreferredAccountRecorder preferredAccountRecorder; + private boolean isCallRemoved; public static String getNumberFromHandle(Uri handle) { return handle == null ? "" : handle.getSchemeSpecificPart(); @@ -1608,15 +1609,17 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa void onRemovedFromCallList() { LogUtil.enterBlock("DialerCall.onRemovedFromCallList"); // Ensure we clean up when this call is removed. - videoTechManager.dispatchRemovedFromCallList(); - if (rttTranscript != null) { + if (videoTechManager != null) { + videoTechManager.dispatchRemovedFromCallList(); + } + // TODO(a bug): Add tests for it to make sure no crash on subsequent call to this method. + if (rttTranscript != null && !isCallRemoved) { Futures.addCallback( RttTranscriptUtil.saveRttTranscript(context, rttTranscript), new DefaultFutureCallback<>(), MoreExecutors.directExecutor()); - // Sets to null so it won't be saved again when called multiple times. - rttTranscript = null; } + isCallRemoved = true; } public com.android.dialer.logging.VideoTech.Type getSelectedAvailableVideoTechType() { diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index 7bfa100ea..47036cd43 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -461,6 +461,12 @@ public class RttChatFragment extends Fragment + SystemClock.elapsedRealtime()); chronometer.start(); isTimerStarted = true; + editText.setVisibility(View.VISIBLE); + submitButton.setVisibility(View.VISIBLE); + editText.setFocusableInTouchMode(true); + if (editText.requestFocus()) { + UiUtil.openKeyboardFrom(getContext(), editText); + } } if (primaryCallState.state() == State.DIALING) { showWaitingForJoinBanner(); 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 ea7ff1095..f995185b1 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 @@ -54,7 +54,8 @@ android:minHeight="53dp" android:textColor="#DD000000" android:textColorHint="#757575" - android:textSize="16sp"/> + android:textSize="16sp" + android:visibility="gone"/> <ImageButton android:id="@+id/rtt_chat_submit_button" android:layout_width="55dp" @@ -65,7 +66,8 @@ android:backgroundTintMode="multiply" android:contentDescription="@string/content_description_rtt_check_button" android:src="@drawable/quantum_ic_done_vd_theme_24" - android:tint="@color/submit_button_color"/> + android:tint="@color/submit_button_color" + android:visibility="gone"/> </LinearLayout> <FrameLayout |