diff options
author | Eric Erfanian <erfanian@google.com> | 2018-04-25 19:00:11 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-04-25 19:00:11 +0000 |
commit | 43ed0dc0666e9d2e2837a2019e2765f10f43dff8 (patch) | |
tree | ac6491831c0d331f8582a65eda60143cb0798fd0 /java/com/android | |
parent | 34ed7da073bbac5b8a4283c495c588ef3b0ea32b (diff) | |
parent | 79a407ee27e6c8f6447f3a8c71ae2c7f6b33f591 (diff) |
Merge changes I5b8ad5ca,I0011019c,I8ee43ce8,I35e0748a,I9e9947ad, ...
* changes:
Show clear frequents option in the toolbar if there are suggested contacts.
Use UI listener for preferred account worker in in call UI
Make SIM Selection hint multi-line
Convert CequintCallerIdContact into an @AutoValue
Newly starred SpeedDialUiItems now have SpeedDialEntry ids set.
Disable entries in CallingAccountSelector that are not selectable
Add "enabled" to SelectPhoneAccountDialogFragment.
Add skeleton for CequintPhoneLookup
Log send button impressions for RTT call.
Show international call on wifi dialog without InCallActivity.
Expose active calls from in call UI
Diffstat (limited to 'java/com/android')
47 files changed, 1341 insertions, 415 deletions
diff --git a/java/com/android/contacts/common/res/layout/select_account_list_item.xml b/java/com/android/contacts/common/res/layout/select_account_list_item.xml index 84cb1fd66..98e7c5454 100644 --- a/java/com/android/contacts/common/res/layout/select_account_list_item.xml +++ b/java/com/android/contacts/common/res/layout/select_account_list_item.xml @@ -54,14 +54,15 @@ android:includeFontPadding="false" android:maxLines="1" android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="@color/dialer_secondary_text_color" android:visibility="gone"/> <TextView android:id="@+id/hint" android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" - android:maxLines="1" android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="@color/dialer_secondary_text_color" android:visibility="gone"/> </LinearLayout> diff --git a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java index 295aa963a..3ee21ccea 100644 --- a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java +++ b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java @@ -47,10 +47,9 @@ import com.android.contacts.common.R; import com.android.contacts.common.compat.PhoneAccountCompat; import com.android.dialer.location.GeoUtil; import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import com.android.dialer.protos.ProtoParsers; import com.android.dialer.telecom.TelecomUtil; import com.google.common.base.Optional; -import java.util.ArrayList; -import java.util.List; /** * Dialog that allows the user to select a phone accounts for a given action. Optionally provides @@ -58,128 +57,74 @@ import java.util.List; */ public class SelectPhoneAccountDialogFragment extends DialogFragment { - private static final String ARG_TITLE_RES_ID = "title_res_id"; - private static final String ARG_CAN_SET_DEFAULT = "can_set_default"; - private static final String ARG_SET_DEFAULT_RES_ID = "set_default_res_id"; - private static final String ARG_ACCOUNT_HANDLES = "account_handles"; + @VisibleForTesting public static final String ARG_OPTIONS = "options"; + private static final String ARG_IS_DEFAULT_CHECKED = "is_default_checked"; - private static final String ARG_LISTENER = "listener"; - private static final String ARG_CALL_ID = "call_id"; - private static final String ARG_HINTS = "hints"; - - private List<PhoneAccountHandle> mAccountHandles; - private List<String> mHints; - private boolean mIsSelected; - private boolean mIsDefaultChecked; - private SelectPhoneAccountListener mListener; - - public SelectPhoneAccountDialogFragment() {} - - /** - * Create new fragment instance with default title and no option to set as default. - * - * @param accountHandles The {@code PhoneAccountHandle}s available to select from. - * @param listener The listener for the results of the account selection. - */ - public static SelectPhoneAccountDialogFragment newInstance( - List<PhoneAccountHandle> accountHandles, - SelectPhoneAccountListener listener, - @Nullable String callId) { - return newInstance( - R.string.select_account_dialog_title, false, 0, accountHandles, listener, callId, null); - } - /** - * Create new fragment instance. This method also allows specifying a custom title and "set - * default" checkbox. - * - * @param titleResId The resource ID for the string to use in the title of the dialog. - * @param canSetDefault {@code true} if the dialog should include an option to set the selection - * as the default. False otherwise. - * @param setDefaultResId The resource ID for the string to use in the "set as default" checkbox - * @param accountHandles The {@code PhoneAccountHandle}s available to select from. - * @param listener The listener for the results of the account selection. - * @param callId The callId to be passed back to the listener in {@link - * SelectPhoneAccountListener#EXTRA_CALL_ID} - * @param hints Additional information to be shown underneath the phone account to help user - * choose. Index must match {@code accountHandles} - */ + private SelectPhoneAccountDialogOptions options = + SelectPhoneAccountDialogOptions.getDefaultInstance(); + private SelectPhoneAccountListener listener; + + private boolean isDefaultChecked; + private boolean isSelected; + + /** Create new fragment instance. */ public static SelectPhoneAccountDialogFragment newInstance( - int titleResId, - boolean canSetDefault, - int setDefaultResId, - List<PhoneAccountHandle> accountHandles, - SelectPhoneAccountListener listener, - @Nullable String callId, - @Nullable List<String> hints) { - ArrayList<PhoneAccountHandle> accountHandlesCopy = new ArrayList<>(); - if (accountHandles != null) { - accountHandlesCopy.addAll(accountHandles); - } + SelectPhoneAccountDialogOptions options, SelectPhoneAccountListener listener) { SelectPhoneAccountDialogFragment fragment = new SelectPhoneAccountDialogFragment(); - final Bundle args = new Bundle(); - args.putInt(ARG_TITLE_RES_ID, titleResId); - args.putBoolean(ARG_CAN_SET_DEFAULT, canSetDefault); - if (setDefaultResId != 0) { - args.putInt(ARG_SET_DEFAULT_RES_ID, setDefaultResId); - } - args.putParcelableArrayList(ARG_ACCOUNT_HANDLES, accountHandlesCopy); - args.putParcelable(ARG_LISTENER, listener); - args.putString(ARG_CALL_ID, callId); - if (hints != null) { - args.putStringArrayList(ARG_HINTS, new ArrayList<>(hints)); - } - fragment.setArguments(args); fragment.setListener(listener); + Bundle arguments = new Bundle(); + ProtoParsers.put(arguments, ARG_OPTIONS, options); + fragment.setArguments(arguments); return fragment; } public void setListener(SelectPhoneAccountListener listener) { - mListener = listener; + this.listener = listener; } @Nullable @VisibleForTesting public SelectPhoneAccountListener getListener() { - return mListener; + return listener; } @VisibleForTesting public boolean canSetDefault() { - return getArguments().getBoolean(ARG_CAN_SET_DEFAULT); + return options.getCanSetDefault(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean(ARG_IS_DEFAULT_CHECKED, mIsDefaultChecked); + outState.putBoolean(ARG_IS_DEFAULT_CHECKED, isDefaultChecked); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - int titleResId = getArguments().getInt(ARG_TITLE_RES_ID); - boolean canSetDefault = getArguments().getBoolean(ARG_CAN_SET_DEFAULT); - mAccountHandles = getArguments().getParcelableArrayList(ARG_ACCOUNT_HANDLES); - mListener = getArguments().getParcelable(ARG_LISTENER); - mHints = getArguments().getStringArrayList(ARG_HINTS); + options = + ProtoParsers.getTrusted( + getArguments(), ARG_OPTIONS, SelectPhoneAccountDialogOptions.getDefaultInstance()); if (savedInstanceState != null) { - mIsDefaultChecked = savedInstanceState.getBoolean(ARG_IS_DEFAULT_CHECKED); + isDefaultChecked = savedInstanceState.getBoolean(ARG_IS_DEFAULT_CHECKED); } - mIsSelected = false; + isSelected = false; final DialogInterface.OnClickListener selectionListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mIsSelected = true; - PhoneAccountHandle selectedAccountHandle = mAccountHandles.get(which); + isSelected = true; + PhoneAccountHandle selectedAccountHandle = + SelectPhoneAccountDialogOptionsUtil.getPhoneAccountHandle( + options.getEntriesList().get(which)); Bundle result = new Bundle(); result.putParcelable( SelectPhoneAccountListener.EXTRA_SELECTED_ACCOUNT_HANDLE, selectedAccountHandle); - result.putBoolean(SelectPhoneAccountListener.EXTRA_SET_DEFAULT, mIsDefaultChecked); + result.putBoolean(SelectPhoneAccountListener.EXTRA_SET_DEFAULT, isDefaultChecked); result.putString(SelectPhoneAccountListener.EXTRA_CALL_ID, getCallId()); - if (mListener != null) { - mListener.onReceiveResult(SelectPhoneAccountListener.RESULT_SELECTED, result); + if (listener != null) { + listener.onReceiveResult(SelectPhoneAccountListener.RESULT_SELECTED, result); } } }; @@ -188,22 +133,23 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton check, boolean isChecked) { - mIsDefaultChecked = isChecked; + isDefaultChecked = isChecked; } }; AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); ListAdapter selectAccountListAdapter = new SelectAccountListAdapter( - builder.getContext(), R.layout.select_account_list_item, mAccountHandles, mHints); + builder.getContext(), R.layout.select_account_list_item, options); AlertDialog dialog = builder - .setTitle(titleResId) + .setTitle( + options.hasTitle() ? options.getTitle() : R.string.select_account_dialog_title) .setAdapter(selectAccountListAdapter, selectionListener) .create(); - if (canSetDefault) { + if (options.getCanSetDefault()) { // Generate custom checkbox view, lint suppressed since no appropriate parent (is dialog) @SuppressLint("InflateParams") LinearLayout checkboxLayout = @@ -213,11 +159,13 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { CheckBox checkBox = checkboxLayout.findViewById(R.id.default_account_checkbox_view); checkBox.setOnCheckedChangeListener(checkListener); - checkBox.setChecked(mIsDefaultChecked); + checkBox.setChecked(isDefaultChecked); TextView textView = checkboxLayout.findViewById(R.id.default_account_checkbox_text); int setDefaultResId = - getArguments().getInt(ARG_SET_DEFAULT_RES_ID, R.string.set_default_account); + options.hasSetDefaultLabel() + ? options.getSetDefaultLabel() + : R.string.set_default_account; textView.setText(setDefaultResId); textView.setOnClickListener((view) -> checkBox.performClick()); checkboxLayout.setOnClickListener((view) -> checkBox.performClick()); @@ -230,17 +178,17 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { @Override public void onCancel(DialogInterface dialog) { - if (!mIsSelected && mListener != null) { + if (!isSelected && listener != null) { Bundle result = new Bundle(); result.putString(SelectPhoneAccountListener.EXTRA_CALL_ID, getCallId()); - mListener.onReceiveResult(SelectPhoneAccountListener.RESULT_DISMISSED, result); + listener.onReceiveResult(SelectPhoneAccountListener.RESULT_DISMISSED, result); } super.onCancel(dialog); } @Nullable private String getCallId() { - return getArguments().getString(ARG_CALL_ID); + return options.getCallId(); } public static class SelectPhoneAccountListener extends ResultReceiver { @@ -274,22 +222,30 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { public void onDialogDismissed(@Nullable String callId) {} } - private static class SelectAccountListAdapter extends ArrayAdapter<PhoneAccountHandle> { + static class SelectAccountListAdapter + extends ArrayAdapter<SelectPhoneAccountDialogOptions.Entry> { private int mResId; - private final List<String> mHints; + private final SelectPhoneAccountDialogOptions options; SelectAccountListAdapter( - Context context, - int resource, - List<PhoneAccountHandle> accountHandles, - @Nullable List<String> hints) { - super(context, resource, accountHandles); - mHints = hints; + Context context, int resource, SelectPhoneAccountDialogOptions options) { + super(context, resource, options.getEntriesList()); + this.options = options; mResId = resource; } @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return options.getEntries(position).getEnabled(); + } + + @Override public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -311,7 +267,9 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { holder = (ViewHolder) rowView.getTag(); } - PhoneAccountHandle accountHandle = getItem(position); + SelectPhoneAccountDialogOptions.Entry entry = getItem(position); + PhoneAccountHandle accountHandle = + SelectPhoneAccountDialogOptionsUtil.getPhoneAccountHandle(entry); PhoneAccount account = getContext().getSystemService(TelecomManager.class).getPhoneAccount(accountHandle); if (account == null) { @@ -331,18 +289,17 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { } holder.imageView.setImageDrawable( PhoneAccountCompat.createIconDrawable(account, getContext())); - if (mHints != null && position < mHints.size()) { - String hint = mHints.get(position); - if (TextUtils.isEmpty(hint)) { - holder.hintTextView.setVisibility(View.GONE); - } else { - holder.hintTextView.setVisibility(View.VISIBLE); - holder.hintTextView.setText(hint); - } - } else { + + if (TextUtils.isEmpty(entry.getHint())) { holder.hintTextView.setVisibility(View.GONE); + } else { + holder.hintTextView.setVisibility(View.VISIBLE); + holder.hintTextView.setText(entry.getHint()); } - + holder.labelTextView.setEnabled(entry.getEnabled()); + holder.numberTextView.setEnabled(entry.getEnabled()); + holder.hintTextView.setEnabled(entry.getEnabled()); + holder.imageView.setImageAlpha(entry.getEnabled() ? 255 : 97 /* 38%*/); return rowView; } @@ -356,7 +313,7 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { return info.get().getCountryIso().toUpperCase(); } - private static final class ViewHolder { + static final class ViewHolder { TextView labelTextView; TextView numberTextView; diff --git a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogOptionsUtil.java b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogOptionsUtil.java new file mode 100644 index 000000000..5a44ae7f8 --- /dev/null +++ b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogOptionsUtil.java @@ -0,0 +1,55 @@ +/* + * 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.contacts.common.widget; + +import android.telecom.PhoneAccountHandle; +import com.android.dialer.common.Assert; +import com.android.dialer.telecom.TelecomUtil; +import java.util.Collection; + +/** Provides common operation on a {@link SelectPhoneAccountDialogOptions} */ +public final class SelectPhoneAccountDialogOptionsUtil { + private SelectPhoneAccountDialogOptionsUtil() {} + + public static PhoneAccountHandle getPhoneAccountHandle( + SelectPhoneAccountDialogOptions.Entry entry) { + return Assert.isNotNull( + TelecomUtil.composePhoneAccountHandle( + entry.getPhoneAccountHandleComponentName(), entry.getPhoneAccountHandleId())); + } + + public static SelectPhoneAccountDialogOptions.Entry.Builder setPhoneAccountHandle( + SelectPhoneAccountDialogOptions.Entry.Builder entryBuilder, + PhoneAccountHandle phoneAccountHandle) { + entryBuilder.setPhoneAccountHandleComponentName( + phoneAccountHandle.getComponentName().flattenToString()); + entryBuilder.setPhoneAccountHandleId(phoneAccountHandle.getId()); + return entryBuilder; + } + + public static SelectPhoneAccountDialogOptions.Builder builderWithAccounts( + Collection<PhoneAccountHandle> phoneAccountHandles) { + SelectPhoneAccountDialogOptions.Builder optionsBuilder = + SelectPhoneAccountDialogOptions.newBuilder(); + for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) { + optionsBuilder.addEntries( + SelectPhoneAccountDialogOptionsUtil.setPhoneAccountHandle( + SelectPhoneAccountDialogOptions.Entry.newBuilder(), phoneAccountHandle)); + } + return optionsBuilder; + } +} diff --git a/java/com/android/contacts/common/widget/select_phone_account_dialog_options.proto b/java/com/android/contacts/common/widget/select_phone_account_dialog_options.proto new file mode 100644 index 000000000..cc40c64b4 --- /dev/null +++ b/java/com/android/contacts/common/widget/select_phone_account_dialog_options.proto @@ -0,0 +1,54 @@ +// 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 + +syntax = "proto2"; + +option java_package = "com.android.contacts.common.widget"; +option java_multiple_files = true; +option optimize_for = LITE_RUNTIME; + + +package com.android.contacts.common.widget; + +// Parameters for SelectPhoneAccountDialogFragment +message SelectPhoneAccountDialogOptions { + // The resource ID for the title. Defaults to + // R.string.select_account_dialog_title + optional int32 title = 1; + // Whether the dialog should include a "set as default" checkbox. Defaults to + // false + optional bool can_set_default = 2; + // The label on the "set as default" checkbox. Defaults + // R.string.set_default_account + optional int32 set_default_label = 3; + // The call ID to pass back to the callback + optional string call_id = 4; + // Phone accounts to show in the dialog + repeated Entry entries = 5; + + message Entry { + // PhoneAccountHandle.getComponentName().flattenToString() + optional string phone_account_handle_component_name = 1; + // PhoneAccountHandle.getId() + optional string phone_account_handle_id = 2; + // The hint to show under the phone account, for example showing the user + // the account was selected frequently before. + optional string hint = 3; + // Whether the account is actually selectable. Defaults to true. Sometimes + // an account will be temporarily unusable, for example the user is already + // in a call so the other SIM cannot be used. Hint should also be set to + // inform the user why the account is unavailable. + optional bool enabled = 4 [default = true]; + } +}
\ No newline at end of file diff --git a/java/com/android/dialer/activecalls/ActiveCallInfo.java b/java/com/android/dialer/activecalls/ActiveCallInfo.java new file mode 100644 index 000000000..d4f76b393 --- /dev/null +++ b/java/com/android/dialer/activecalls/ActiveCallInfo.java @@ -0,0 +1,48 @@ +/* + * 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.activecalls; + +import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; + +/** Info of an active call */ +@AutoValue +@SuppressWarnings("Guava") +public abstract class ActiveCallInfo { + + /** The {@link PhoneAccountHandle} the call is made with */ + public abstract Optional<PhoneAccountHandle> phoneAccountHandle(); + + public static Builder builder() { + return new AutoValue_ActiveCallInfo.Builder(); + } + + /** Builder for {@link ActiveCallInfo}. Only In Call UI should create ActiveCallInfo */ + @AutoValue.Builder + public abstract static class Builder { + + public Builder setPhoneAccountHandle(@Nullable PhoneAccountHandle phoneAccountHandle) { + return setPhoneAccountHandle(Optional.fromNullable(phoneAccountHandle)); + } + + public abstract Builder setPhoneAccountHandle(Optional<PhoneAccountHandle> phoneAccountHandle); + + public abstract ActiveCallInfo build(); + } +} diff --git a/java/com/android/dialer/activecalls/ActiveCalls.java b/java/com/android/dialer/activecalls/ActiveCalls.java new file mode 100644 index 000000000..600839c73 --- /dev/null +++ b/java/com/android/dialer/activecalls/ActiveCalls.java @@ -0,0 +1,34 @@ +/* + * 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.activecalls; + +import android.support.annotation.MainThread; +import com.google.common.collect.ImmutableList; + +/** Exposes information about current active calls to the whole dialer. */ +public interface ActiveCalls { + + /** + * Return a list of current active calls. Any call that is not disconnected is regarded as active. + * Ordering of elements are not guaranteed. + */ + ImmutableList<ActiveCallInfo> getActiveCalls(); + + /** Should only be called by in call UI. */ + @MainThread + void setActiveCalls(ImmutableList<ActiveCallInfo> activeCalls); +} diff --git a/java/com/android/dialer/activecalls/ActiveCallsComponent.java b/java/com/android/dialer/activecalls/ActiveCallsComponent.java new file mode 100644 index 000000000..99e0e9493 --- /dev/null +++ b/java/com/android/dialer/activecalls/ActiveCallsComponent.java @@ -0,0 +1,40 @@ +/* + * 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.activecalls; + +import android.content.Context; +import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; +import dagger.Subcomponent; + +/** Component for {@link ActiveCalls} */ +@Subcomponent +public abstract class ActiveCallsComponent { + + public abstract ActiveCalls activeCalls(); + + public static ActiveCallsComponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .activeCallsComponent(); + } + + /** Used to refer to the root application component. */ + @IncludeInDialerRoot + public interface HasComponent { + ActiveCallsComponent activeCallsComponent(); + } +} diff --git a/java/com/android/dialer/activecalls/ActiveCallsModule.java b/java/com/android/dialer/activecalls/ActiveCallsModule.java new file mode 100644 index 000000000..4d7f44858 --- /dev/null +++ b/java/com/android/dialer/activecalls/ActiveCallsModule.java @@ -0,0 +1,34 @@ +/* + * 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.activecalls; + +import com.android.dialer.activecalls.impl.ActiveCallsImpl; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; +import dagger.Binds; +import dagger.Module; +import javax.inject.Singleton; + +/** Module for {@link ActiveCallsComponent} */ +@Module +@InstallIn(variants = DialerVariant.DIALER_TEST) // TODO(weijiaxu): put all variants. +public abstract class ActiveCallsModule { + + @Singleton + @Binds + public abstract ActiveCalls to(ActiveCallsImpl impl); +} diff --git a/java/com/android/dialer/activecalls/impl/ActiveCallsImpl.java b/java/com/android/dialer/activecalls/impl/ActiveCallsImpl.java new file mode 100644 index 000000000..3449cc8b0 --- /dev/null +++ b/java/com/android/dialer/activecalls/impl/ActiveCallsImpl.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.activecalls.impl; + +import android.support.annotation.MainThread; +import com.android.dialer.activecalls.ActiveCallInfo; +import com.android.dialer.activecalls.ActiveCalls; +import com.android.dialer.common.Assert; +import com.google.common.collect.ImmutableList; +import javax.inject.Inject; + +/** Implementation of {@link ActiveCalls} */ +public class ActiveCallsImpl implements ActiveCalls { + + ImmutableList<ActiveCallInfo> activeCalls = ImmutableList.of(); + + @Inject + ActiveCallsImpl() {} + + @Override + public ImmutableList<ActiveCallInfo> getActiveCalls() { + return activeCalls; + } + + @Override + @MainThread + public void setActiveCalls(ImmutableList<ActiveCallInfo> activeCalls) { + Assert.isMainThread(); + this.activeCalls = Assert.isNotNull(activeCalls); + } +} diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java index 21a282ded..e1021894f 100644 --- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java +++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java @@ -17,6 +17,7 @@ package com.android.dialer.binary.aosp; import com.android.bubble.stub.StubBubbleModule; +import com.android.dialer.activecalls.ActiveCallsModule; import com.android.dialer.binary.basecomponent.BaseDialerRootComponent; import com.android.dialer.calllog.CallLogModule; import com.android.dialer.calllog.config.CallLogConfigModule; @@ -49,6 +50,7 @@ import javax.inject.Singleton; @Singleton @Component( modules = { + ActiveCallsModule.class, CallLogModule.class, CallLogConfigModule.class, CommandLineModule.class, diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java index 11e952cbc..75ddaf7f0 100644 --- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java +++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java @@ -17,6 +17,7 @@ package com.android.dialer.binary.basecomponent; import com.android.bubble.BubbleComponent; +import com.android.dialer.activecalls.ActiveCallsComponent; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.config.CallLogConfigComponent; import com.android.dialer.calllog.database.CallLogDatabaseComponent; @@ -50,7 +51,8 @@ import com.android.voicemail.VoicemailComponent; * from this component. */ public interface BaseDialerRootComponent - extends BluetoothDeviceProviderComponent.HasComponent, + extends ActiveCallsComponent.HasComponent, + BluetoothDeviceProviderComponent.HasComponent, BubbleComponent.HasComponent, CallLocationComponent.HasComponent, CallLogComponent.HasComponent, diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java index 0da2f9577..bdbdeb9dd 100644 --- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java +++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java @@ -17,6 +17,7 @@ package com.android.dialer.binary.google; import com.android.bubble.stub.StubBubbleModule; +import com.android.dialer.activecalls.ActiveCallsModule; import com.android.dialer.binary.basecomponent.BaseDialerRootComponent; import com.android.dialer.calllog.CallLogModule; import com.android.dialer.calllog.config.CallLogConfigModule; @@ -52,6 +53,7 @@ import javax.inject.Singleton; @Singleton @Component( modules = { + ActiveCallsModule.class, CallLocationModule.class, CallLogModule.class, CallLogConfigModule.class, diff --git a/java/com/android/dialer/commandline/CommandLineModule.java b/java/com/android/dialer/commandline/CommandLineModule.java index 915578722..c78de21e5 100644 --- a/java/com/android/dialer/commandline/CommandLineModule.java +++ b/java/com/android/dialer/commandline/CommandLineModule.java @@ -16,6 +16,7 @@ package com.android.dialer.commandline; +import com.android.dialer.commandline.impl.ActiveCallsCommand; import com.android.dialer.commandline.impl.BlockingCommand; import com.android.dialer.commandline.impl.CallCommand; import com.android.dialer.commandline.impl.Echo; @@ -45,6 +46,7 @@ public abstract class CommandLineModule { private final Echo echo; private final BlockingCommand blockingCommand; private final CallCommand callCommand; + private final ActiveCallsCommand activeCallsCommand; @Inject AospCommandInjector( @@ -52,12 +54,14 @@ public abstract class CommandLineModule { Version version, Echo echo, BlockingCommand blockingCommand, - CallCommand callCommand) { + CallCommand callCommand, + ActiveCallsCommand activeCallsCommand) { this.help = help; this.version = version; this.echo = echo; this.blockingCommand = blockingCommand; this.callCommand = callCommand; + this.activeCallsCommand = activeCallsCommand; } public CommandSupplier.Builder inject(CommandSupplier.Builder builder) { @@ -66,6 +70,7 @@ public abstract class CommandLineModule { builder.addCommand("echo", echo); builder.addCommand("blocking", blockingCommand); builder.addCommand("call", callCommand); + builder.addCommand("activecalls", activeCallsCommand); return builder; } } diff --git a/java/com/android/dialer/commandline/impl/ActiveCallsCommand.java b/java/com/android/dialer/commandline/impl/ActiveCallsCommand.java new file mode 100644 index 000000000..81641ed50 --- /dev/null +++ b/java/com/android/dialer/commandline/impl/ActiveCallsCommand.java @@ -0,0 +1,67 @@ +/* + * 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.commandline.impl; + +import android.content.Context; +import android.support.annotation.NonNull; +import com.android.dialer.activecalls.ActiveCallsComponent; +import com.android.dialer.commandline.Arguments; +import com.android.dialer.commandline.Command; +import com.android.dialer.inject.ApplicationContext; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import javax.inject.Inject; + +/** Manipulates {@link com.android.dialer.activecalls.ActiveCalls} */ +public class ActiveCallsCommand implements Command { + + private final Context appContext; + + @Inject + ActiveCallsCommand(@ApplicationContext Context appContext) { + this.appContext = appContext; + } + + @NonNull + @Override + public String getShortDescription() { + return "manipulate active calls"; + } + + @NonNull + @Override + public String getUsage() { + return "activecalls list"; + } + + @Override + public ListenableFuture<String> run(Arguments args) throws IllegalCommandLineArgumentException { + if (args.getPositionals().isEmpty()) { + return Futures.immediateFuture(getUsage()); + } + + String command = args.getPositionals().get(0); + + switch (command) { + case "list": + return Futures.immediateFuture( + ActiveCallsComponent.get(appContext).activeCalls().getActiveCalls().toString()); + default: + throw new IllegalCommandLineArgumentException("unknown command " + command); + } + } +} diff --git a/java/com/android/dialer/common/FragmentUtils.java b/java/com/android/dialer/common/FragmentUtils.java index c07d9a799..aa4441eeb 100644 --- a/java/com/android/dialer/common/FragmentUtils.java +++ b/java/com/android/dialer/common/FragmentUtils.java @@ -59,6 +59,11 @@ public class FragmentUtils { @SuppressWarnings("unchecked") // Casts are checked using runtime methods T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface); return parent; + } else if (fragment.getActivity() instanceof MainActivityPeer.PeerSupplier) { + MainActivityPeer peer = ((MainActivityPeer.PeerSupplier) fragment.getActivity()).getPeer(); + if (peer instanceof FragmentUtilListener) { + return ((FragmentUtilListener) peer).getImpl(callbackInterface); + } } return null; } diff --git a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java index 9929ddd3b..d2652ee66 100644 --- a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java +++ b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java @@ -56,6 +56,7 @@ import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; +import com.android.contacts.common.widget.SelectPhoneAccountDialogOptionsUtil; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.telephony.TelephonyManagerCompat; @@ -234,10 +235,12 @@ public class SpecialCharSequenceMgr { } else { SelectPhoneAccountListener callback = new HandleAdnEntryAccountSelectedCallback(applicationContext, handler, sc); - DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance( - subscriptionAccountHandles, callback, null); + SelectPhoneAccountDialogOptionsUtil.builderWithAccounts( + subscriptionAccountHandles) + .build(), + callback); dialogFragment.show(((Activity) context).getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT); } @@ -292,7 +295,9 @@ public class SpecialCharSequenceMgr { DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance( - subscriptionAccountHandles, listener, null); + SelectPhoneAccountDialogOptionsUtil.builderWithAccounts(subscriptionAccountHandles) + .build(), + listener); dialogFragment.show(((Activity) context).getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT); } return true; diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index 5d40bb1ea..7cd22079c 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: 1387 + // Next Tag: 1392 enum Type { UNKNOWN_AOSP_EVENT_TYPE = 1000; @@ -626,7 +626,11 @@ message DialerImpression { DUAL_SIM_SELECTION_NON_SUGGESTED_SIM_SELECTED = 1304; DUAL_SIM_SELECTION_PREFERRED_SET = 1305; DUAL_SIM_SELECTION_PREFERRED_USED = 1306; + DUAL_SIM_SELECTION_PREFERRED_NOT_SELECTABLE = 1389; DUAL_SIM_SELECTION_GLOBAL_USED = 1307; + DUAL_SIM_SELECTION_GLOBAL_NOT_SELECTABLE = 1390; + DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED = 1322; + DUAL_SIM_SELECTION_SUGGESTION_AUTO_NOT_SELECTABLE = 1391; DUO_CALL_LOG_SET_UP_INSTALL = 1308; DUO_CALL_LOG_SET_UP_ACTIVATE = 1309; @@ -655,8 +659,6 @@ message DialerImpression { // Drag bubble to bottom and end call BUBBLE_V2_BOTTOM_ACTION_END_CALL = 1321; - DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED = 1322; - // Bubble appears BUBBLE_V2_SHOW = 1323; @@ -764,5 +766,9 @@ message DialerImpression { RTT_MID_CALL_ACCEPTED = 1385; // Mid call RTT request rejected. RTT_MID_CALL_REJECTED = 1386; + + // Send button clicked in RTT call, this includes send button on keyboard. + RTT_SEND_BUTTON_CLICKED = 1387; + RTT_KEYBOARD_SEND_BUTTON_CLICKED = 1388; } } diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java index 0ab69a443..f2d6fa6d3 100644 --- a/java/com/android/dialer/main/impl/NewMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java @@ -29,7 +29,6 @@ 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.speeddial.SpeedDialFragment; import com.android.dialer.voicemail.listui.NewVoicemailFragment; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; @@ -106,16 +105,18 @@ public class NewMainActivityPeer implements MainActivityPeer { @Override public void onSpeedDialSelected() { hideAllFragments(); - SpeedDialFragment fragment = - (SpeedDialFragment) supportFragmentManager.findFragmentByTag(SPEED_DIAL_TAG); - if (fragment == null) { - supportFragmentManager - .beginTransaction() - .add(R.id.fragment_container, SpeedDialFragment.newInstance(), SPEED_DIAL_TAG) - .commit(); - } else { - supportFragmentManager.beginTransaction().show(fragment).commit(); - } + // TODO(calderwoodra): Since we aren't using fragment utils in this peer, let's disable + // speed dial until we figure out a solution. + // SpeedDialFragment fragment = + // (SpeedDialFragment) supportFragmentManager.findFragmentByTag(SPEED_DIAL_TAG); + // if (fragment == null) { + // supportFragmentManager + // .beginTransaction() + // .add(R.id.fragment_container, SpeedDialFragment.newInstance(), SPEED_DIAL_TAG) + // .commit(); + // } else { + // supportFragmentManager.beginTransaction().show(fragment).commit(); + // } } @Override diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 1c0cad0b0..9ac351094 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -183,6 +183,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen // Speed Dial private MainOnPhoneNumberPickerActionListener onPhoneNumberPickerActionListener; private MainOldSpeedDialFragmentHost oldSpeedDialFragmentHost; + private MainSpeedDialFragmentHost speedDialFragmentHost; /** Language the device was in last time {@link #onSaveInstanceState(Bundle)} was called. */ private String savedLanguageCode; @@ -293,6 +294,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen activity.findViewById(R.id.remove_view), activity.findViewById(R.id.search_view_container), toolbar); + speedDialFragmentHost = new MainSpeedDialFragmentHost(toolbar); lastTabController = new LastTabController(activity, bottomNav, showVoicemailTab); @@ -639,6 +641,8 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen return (T) oldSpeedDialFragmentHost; } else if (callbackInterface.isInstance(searchController)) { return (T) searchController; + } else if (callbackInterface.isInstance(speedDialFragmentHost)) { + return (T) speedDialFragmentHost; } else { return null; } @@ -1191,6 +1195,25 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } /** + * Handles the callbacks for {@link SpeedDialFragment}. + * + * @see SpeedDialFragment.HostInterface + */ + private static final class MainSpeedDialFragmentHost implements SpeedDialFragment.HostInterface { + + private final MainToolbar toolbar; + + MainSpeedDialFragmentHost(MainToolbar toolbar) { + this.toolbar = toolbar; + } + + @Override + public void setHasFrequents(boolean hasFrequents) { + toolbar.showClearFrequents(hasFrequents); + } + } + + /** * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of * the main tabs and FAB. * diff --git a/java/com/android/dialer/oem/CequintCallerIdManager.java b/java/com/android/dialer/oem/CequintCallerIdManager.java index ee865bc14..55cafc15e 100644 --- a/java/com/android/dialer/oem/CequintCallerIdManager.java +++ b/java/com/android/dialer/oem/CequintCallerIdManager.java @@ -30,6 +30,7 @@ import android.text.TextUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; +import com.google.auto.value.AutoValue; import java.util.concurrent.ConcurrentHashMap; /** @@ -70,20 +71,37 @@ public class CequintCallerIdManager { // TODO(wangqi): Revisit it and maybe remove it if it's not necessary. private final ConcurrentHashMap<String, CequintCallerIdContact> callLogCache; - /** Cequint caller id contact information. */ - public static class CequintCallerIdContact { - public final String name; - public final String geoDescription; - public final String imageUrl; + /** Cequint caller ID contact information. */ + @AutoValue + public abstract static class CequintCallerIdContact { - private CequintCallerIdContact(String name, String geoDescription, String imageUrl) { - this.name = name; - this.geoDescription = geoDescription; - this.imageUrl = imageUrl; + public abstract String name(); + + /** + * Description of the geolocation (e.g., "Mountain View, CA"), which is for display purpose + * only. + */ + public abstract String geolocation(); + + public abstract String photoUri(); + + static Builder builder() { + return new AutoValue_CequintCallerIdManager_CequintCallerIdContact.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setName(String name); + + abstract Builder setGeolocation(String geolocation); + + abstract Builder setPhotoUri(String photoUri); + + abstract CequintCallerIdContact build(); } } - /** Check whether Cequint Caller Id provider package is available and enabled. */ + /** Check whether Cequint Caller ID provider package is available and enabled. */ @AnyThread public static synchronized boolean isCequintCallerIdEnabled(@NonNull Context context) { if (!ConfigProviderBindings.get(context).getBoolean(CONFIG_CALLER_ID_ENABLED, true)) { @@ -175,22 +193,26 @@ public class CequintCallerIdManager { String name = getString(cursor, cursor.getColumnIndex(NAME)); String firstName = getString(cursor, cursor.getColumnIndex(FIRST_NAME)); String lastName = getString(cursor, cursor.getColumnIndex(LAST_NAME)); - String imageUrl = getString(cursor, cursor.getColumnIndex(IMAGE)); + String photoUri = getString(cursor, cursor.getColumnIndex(IMAGE)); String displayName = getString(cursor, cursor.getColumnIndex(DISPLAY_NAME)); String contactName = TextUtils.isEmpty(displayName) ? generateDisplayName(firstName, lastName, company, name) : displayName; - String geoDescription = getGeoDescription(city, state, stateAbbr, country); + String geolocation = getGeolocation(city, state, stateAbbr, country); LogUtil.d( "CequintCallerIdManager.lookup", "number: %s, contact name: %s, geo: %s, photo url: %s", LogUtil.sanitizePhoneNumber(number), LogUtil.sanitizePii(contactName), - LogUtil.sanitizePii(geoDescription), - imageUrl); - return new CequintCallerIdContact(contactName, geoDescription, imageUrl); + LogUtil.sanitizePii(geolocation), + photoUri); + return CequintCallerIdContact.builder() + .setName(contactName) + .setGeolocation(geolocation) + .setPhotoUri(photoUri) + .build(); } else { LogUtil.d("CequintCallerIdManager.lookup", "No CequintCallerIdContact found"); return null; @@ -249,8 +271,8 @@ public class CequintCallerIdManager { return null; } - /** Returns geo location information. e.g. Mountain View, CA. */ - private static String getGeoDescription( + /** Returns geolocation information (e.g., "Mountain View, CA"). */ + private static String getGeolocation( String city, String state, String stateAbbr, String country) { String geoDescription = null; diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java index 86e6991c1..c6e91c4a4 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java +++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java @@ -17,6 +17,7 @@ package com.android.dialer.phonelookup; import com.android.dialer.phonelookup.blockednumber.SystemBlockedNumberPhoneLookup; +import com.android.dialer.phonelookup.cequint.CequintPhoneLookup; import com.android.dialer.phonelookup.cnap.CnapPhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2DefaultDirectoryPhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2ExtendedDirectoryPhoneLookup; @@ -32,12 +33,14 @@ public abstract class PhoneLookupModule { @Provides @SuppressWarnings({"unchecked", "rawtype"}) static ImmutableList<PhoneLookup> providePhoneLookupList( + CequintPhoneLookup cequintPhoneLookup, CnapPhoneLookup cnapPhoneLookup, Cp2DefaultDirectoryPhoneLookup cp2DefaultDirectoryPhoneLookup, Cp2ExtendedDirectoryPhoneLookup cp2ExtendedDirectoryPhoneLookup, SystemBlockedNumberPhoneLookup systemBlockedNumberPhoneLookup, SpamPhoneLookup spamPhoneLookup) { return ImmutableList.of( + cequintPhoneLookup, cnapPhoneLookup, cp2DefaultDirectoryPhoneLookup, cp2ExtendedDirectoryPhoneLookup, diff --git a/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java b/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java new file mode 100644 index 000000000..ce2cd18ad --- /dev/null +++ b/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.phonelookup.cequint; + +import android.content.Context; +import android.telecom.Call; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.phonelookup.PhoneLookup; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.CequintInfo; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import javax.inject.Inject; + +/** PhoneLookup implementation for Cequint. */ +public class CequintPhoneLookup implements PhoneLookup<CequintInfo> { + + @Inject + CequintPhoneLookup() {} + + @Override + public ListenableFuture<CequintInfo> lookup(Context appContext, Call call) { + // TODO(a bug): Override the default implementation in the PhoneLookup interface + // as a Cequint lookup requires info in the provided call. + return Futures.immediateFuture(CequintInfo.getDefaultInstance()); + } + + @Override + public ListenableFuture<CequintInfo> lookup(DialerPhoneNumber dialerPhoneNumber) { + // TODO(a bug): Implement this method. + return Futures.immediateFuture(CequintInfo.getDefaultInstance()); + } + + @Override + public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) { + return Futures.immediateFuture(false); + } + + @Override + public ListenableFuture<ImmutableMap<DialerPhoneNumber, CequintInfo>> getMostRecentInfo( + ImmutableMap<DialerPhoneNumber, CequintInfo> existingInfoMap) { + return Futures.immediateFuture(existingInfoMap); + } + + @Override + public void setSubMessage(PhoneLookupInfo.Builder destination, CequintInfo subMessage) { + destination.setCequintInfo(subMessage); + } + + @Override + public CequintInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) { + return phoneLookupInfo.getCequintInfo(); + } + + @Override + public ListenableFuture<Void> onSuccessfulBulkUpdate() { + return Futures.immediateFuture(null); + } + + @Override + public void registerContentObservers() { + // No content observers for Cequint info. + } + + @Override + public void unregisterContentObservers() { + // No content observers for Cequint info. + } + + @Override + public ListenableFuture<Void> clearData() { + return Futures.immediateFuture(null); + } +} diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto index 1beff6ca9..50817c4e3 100644 --- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto +++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto @@ -14,7 +14,7 @@ package com.android.dialer.phonelookup; // "cp2_info_in_default_directory" corresponds to class // Cp2DefaultDirectoryPhoneLookup, and class Cp2DefaultDirectoryPhoneLookup // alone is responsible for populating it. -// Next ID: 8 +// Next ID: 9 message PhoneLookupInfo { // Information about a PhoneNumber retrieved from CP2. message Cp2Info { @@ -163,4 +163,16 @@ message PhoneLookupInfo { optional string name = 1; } optional CnapInfo cnap_info = 7; + + // Information obtained via Cequint + // Next ID: 4 + message CequintInfo { + optional string name = 1; + + // Description of the geolocation (e.g., "Mountain View, CA") + optional string geolocation = 2; + + optional string photo_uri = 3; + } + optional CequintInfo cequint_info = 8; }
\ No newline at end of file diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java index 777175e00..4302436a7 100644 --- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java +++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java @@ -625,16 +625,16 @@ public class ContactInfoHelper { if (cequintCallerIdContact == null) { return; } - if (TextUtils.isEmpty(info.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) { - info.name = cequintCallerIdContact.name; + if (TextUtils.isEmpty(info.name) && !TextUtils.isEmpty(cequintCallerIdContact.name())) { + info.name = cequintCallerIdContact.name(); } - if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) { - info.geoDescription = cequintCallerIdContact.geoDescription; + if (!TextUtils.isEmpty(cequintCallerIdContact.geolocation())) { + info.geoDescription = cequintCallerIdContact.geolocation(); info.sourceType = ContactSource.Type.SOURCE_TYPE_CEQUINT_CALLER_ID; } // Only update photo if local lookup has no result. - if (!info.contactExists && info.photoUri == null && cequintCallerIdContact.imageUrl != null) { - info.photoUri = UriUtils.parseUriOrNull(cequintCallerIdContact.imageUrl); + if (!info.contactExists && info.photoUri == null && cequintCallerIdContact.photoUri() != null) { + info.photoUri = UriUtils.parseUriOrNull(cequintCallerIdContact.photoUri()); } } } diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java index 16e7641ba..8f63fa0db 100644 --- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java +++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java @@ -28,12 +28,15 @@ import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; +import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions; +import com.android.contacts.common.widget.SelectPhoneAccountDialogOptionsUtil; +import com.android.dialer.activecalls.ActiveCallInfo; +import com.android.dialer.activecalls.ActiveCallsComponent; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.DialerImpression.Type; import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCallAction; @@ -41,10 +44,13 @@ import com.android.dialer.precall.PreCallCoordinator; import com.android.dialer.precall.PreCallCoordinator.PendingAction; import com.android.dialer.preferredsim.PreferredAccountRecorder; import com.android.dialer.preferredsim.PreferredAccountWorker; +import com.android.dialer.preferredsim.PreferredAccountWorker.Result; import com.android.dialer.preferredsim.suggestion.SuggestionProvider; import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion; -import com.android.dialer.telecom.TelecomUtil; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; import java.util.List; +import java.util.Objects; /** PreCallAction to select which phone account to call with. Ignored if there's only one account */ @SuppressWarnings("MissingPermission") @@ -75,17 +81,6 @@ public class CallingAccountSelector implements PreCallAction { if (accounts.size() <= 1) { return false; } - - if (TelecomUtil.isInManagedCall(context)) { - // Most devices are DSDS (dual SIM dual standby) which only one SIM can have active calls at - // a time. Telecom will ignore the phone account handle and use the current active SIM, thus - // there's no point of selecting SIMs - // TODO(a bug): let the user know selections are not available and preferred SIM is not - // used - // TODO(twyen): support other dual SIM modes when the API is exposed. - return false; - } - return true; } @@ -134,12 +129,7 @@ public class CallingAccountSelector implements PreCallAction { return; } if (result.getPhoneAccountHandle().isPresent()) { - Logger.get(coordinator.getActivity()) - .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_PREFERRED_USED); - coordinator - .getBuilder() - .setPhoneAccountHandle(result.getPhoneAccountHandle().get()); - pendingAction.finish(); + usePreferredAccount(coordinator, pendingAction, result); return; } PhoneAccountHandle defaultPhoneAccount = @@ -147,10 +137,7 @@ public class CallingAccountSelector implements PreCallAction { .getSystemService(TelecomManager.class) .getDefaultOutgoingPhoneAccount(builder.getUri().getScheme()); if (defaultPhoneAccount != null) { - Logger.get(coordinator.getActivity()) - .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_GLOBAL_USED); - builder.setPhoneAccountHandle(defaultPhoneAccount); - pendingAction.finish(); + useDefaultAccount(coordinator, pendingAction, result, defaultPhoneAccount); return; } if (result.getSuggestion().isPresent()) { @@ -158,18 +145,7 @@ public class CallingAccountSelector implements PreCallAction { "CallingAccountSelector.processPreferredAccount", "SIM suggested: " + result.getSuggestion().get().reason); if (result.getSuggestion().get().shouldAutoSelect) { - Logger.get(coordinator.getActivity()) - .logImpression( - DialerImpression.Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED); - LogUtil.i( - "CallingAccountSelector.processPreferredAccount", "Auto selected suggestion"); - builder.setPhoneAccountHandle(result.getSuggestion().get().phoneAccountHandle); - builder - .getInCallUiIntentExtras() - .putString( - SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON, - result.getSuggestion().get().reason.name()); - pendingAction.finish(); + useSuggestedAccount(coordinator, pendingAction, result); return; } } @@ -184,6 +160,120 @@ public class CallingAccountSelector implements PreCallAction { .executeParallel(activity); } + private void usePreferredAccount( + PreCallCoordinator coordinator, PendingAction pendingAction, Result result) { + String phoneNumber = coordinator.getBuilder().getUri().getSchemeSpecificPart(); + if (isSelectable(coordinator.getActivity(), result.getPhoneAccountHandle().get())) { + Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED); + coordinator.getBuilder().setPhoneAccountHandle(result.getPhoneAccountHandle().get()); + pendingAction.finish(); + } else { + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_NOT_SELECTABLE); + LogUtil.i("CallingAccountSelector.usePreferredAccount", "preferred account not selectable"); + showDialog( + coordinator, + pendingAction, + result.getDataId().orNull(), + phoneNumber, + result.getSuggestion().orNull()); + } + } + + private void useDefaultAccount( + PreCallCoordinator coordinator, + PendingAction pendingAction, + Result result, + PhoneAccountHandle defaultPhoneAccount) { + CallIntentBuilder builder = coordinator.getBuilder(); + String phoneNumber = builder.getUri().getSchemeSpecificPart(); + if (isSelectable(coordinator.getActivity(), defaultPhoneAccount)) { + Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_GLOBAL_USED); + builder.setPhoneAccountHandle(defaultPhoneAccount); + pendingAction.finish(); + } else { + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_GLOBAL_NOT_SELECTABLE); + LogUtil.i("CallingAccountSelector.useDefaultAccount", "default account not selectable"); + showDialog( + coordinator, + pendingAction, + result.getDataId().orNull(), + phoneNumber, + result.getSuggestion().orNull()); + } + } + + private void useSuggestedAccount( + PreCallCoordinator coordinator, PendingAction pendingAction, Result result) { + CallIntentBuilder builder = coordinator.getBuilder(); + String phoneNumber = builder.getUri().getSchemeSpecificPart(); + if (isSelectable(coordinator.getActivity(), result.getSuggestion().get().phoneAccountHandle)) { + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED); + LogUtil.i("CallingAccountSelector.processPreferredAccount", "Auto selected suggestion"); + builder.setPhoneAccountHandle(result.getSuggestion().get().phoneAccountHandle); + builder + .getInCallUiIntentExtras() + .putString( + SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON, + result.getSuggestion().get().reason.name()); + pendingAction.finish(); + } else { + LogUtil.i("CallingAccountSelector.useSuggestedAccount", "suggested account not selectable"); + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_NOT_SELECTABLE); + showDialog( + coordinator, + pendingAction, + result.getDataId().orNull(), + phoneNumber, + result.getSuggestion().orNull()); + } + } + + /** + * Most devices are DSDS (dual SIM dual standby) which only one SIM can have active calls at a + * time. TODO(twyen): support other dual SIM modes when the API is exposed. + */ + private static boolean isSelectable(Context context, PhoneAccountHandle phoneAccountHandle) { + ImmutableList<ActiveCallInfo> activeCalls = + ActiveCallsComponent.get(context).activeCalls().getActiveCalls(); + if (activeCalls.isEmpty()) { + return true; + } + for (ActiveCallInfo activeCall : activeCalls) { + if (Objects.equals(phoneAccountHandle, activeCall.phoneAccountHandle().orNull())) { + return true; + } + } + return false; + } + + private static Optional<String> getActiveCallLabel(Context context) { + ImmutableList<ActiveCallInfo> activeCalls = + ActiveCallsComponent.get(context).activeCalls().getActiveCalls(); + + if (activeCalls.isEmpty()) { + LogUtil.e("CallingAccountSelector.getActiveCallLabel", "active calls no longer exist"); + return Optional.absent(); + } + ActiveCallInfo activeCall = activeCalls.get(0); + if (!activeCall.phoneAccountHandle().isPresent()) { + LogUtil.e("CallingAccountSelector.getActiveCallLabel", "active call has no phone account"); + return Optional.absent(); + } + PhoneAccount phoneAccount = + context + .getSystemService(TelecomManager.class) + .getPhoneAccount(activeCall.phoneAccountHandle().get()); + if (phoneAccount == null) { + LogUtil.e("CallingAccountSelector.getActiveCallLabel", "phone account not found"); + return Optional.absent(); + } + return Optional.of(phoneAccount.getLabel().toString()); + } + @MainThread private void showDialog( PreCallCoordinator coordinator, @@ -211,24 +301,47 @@ public class CallingAccountSelector implements PreCallAction { default: } } - List<PhoneAccountHandle> phoneAccountHandles = + SelectPhoneAccountDialogOptions.Builder optionsBuilder = + SelectPhoneAccountDialogOptions.newBuilder() + .setTitle(R.string.pre_call_select_phone_account) + .setCanSetDefault(dataId != null) + .setSetDefaultLabel(R.string.pre_call_select_phone_account_remember); + + for (PhoneAccountHandle phoneAccountHandle : coordinator .getActivity() .getSystemService(TelecomManager.class) - .getCallCapablePhoneAccounts(); + .getCallCapablePhoneAccounts()) { + SelectPhoneAccountDialogOptions.Entry.Builder entryBuilder = + SelectPhoneAccountDialogOptions.Entry.newBuilder(); + SelectPhoneAccountDialogOptionsUtil.setPhoneAccountHandle(entryBuilder, phoneAccountHandle); + if (isSelectable(coordinator.getActivity(), phoneAccountHandle)) { + Optional<String> hint = + SuggestionProvider.getHint(coordinator.getActivity(), phoneAccountHandle, suggestion); + if (hint.isPresent()) { + entryBuilder.setHint(hint.get()); + } + } else { + entryBuilder.setEnabled(false); + Optional<String> activeCallLabel = getActiveCallLabel(coordinator.getActivity()); + if (activeCallLabel.isPresent()) { + entryBuilder.setHint( + coordinator + .getActivity() + .getString( + R.string.pre_call_select_phone_account_hint_other_sim_in_use, + activeCallLabel.get())); + } + } + optionsBuilder.addEntries(entryBuilder); + } selectPhoneAccountDialogFragment = SelectPhoneAccountDialogFragment.newInstance( - R.string.pre_call_select_phone_account, - dataId != null /* canSetDefault */, - R.string.pre_call_select_phone_account_remember, - phoneAccountHandles, + optionsBuilder.build(), new SelectedListener( coordinator, pendingAction, - new PreferredAccountRecorder(number, suggestion, dataId)), - null /* call ID */, - SuggestionProvider.buildHint( - coordinator.getActivity(), phoneAccountHandles, suggestion)); + new PreferredAccountRecorder(number, suggestion, dataId))); selectPhoneAccountDialogFragment.show( coordinator.getActivity().getFragmentManager(), TAG_CALLING_ACCOUNT_SELECTOR); } diff --git a/java/com/android/dialer/precall/impl/res/values/strings.xml b/java/com/android/dialer/precall/impl/res/values/strings.xml index 4bd5d36f2..5e7ddd36c 100644 --- a/java/com/android/dialer/precall/impl/res/values/strings.xml +++ b/java/com/android/dialer/precall/impl/res/values/strings.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<resources> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Toast when the user tried to place a call but has revoked phone permission [CHAR_LIMIT=none] --> <string name="pre_call_permission_check_no_phone_permission">Cannot make call without phone permission</string> @@ -26,4 +26,8 @@ same contact and never ask again [CHAR LIMIT=40]--> <string name="pre_call_select_phone_account_remember">Remember this choice</string> + <!-- Hint to show under a SIM entry when selecting SIM for call on a multi-SIM device, when the + call cannot be placed with the SIM because there is already a call on the other SIM, + [CHAR LIMIT=NONE]--> + <string name="pre_call_select_phone_account_hint_other_sim_in_use">Not available while using <xliff:g example="SIM 1" id="other_sim">%1$s</xliff:g></string> </resources>
\ No newline at end of file diff --git a/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java b/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java index f710f734c..50ae01fd0 100644 --- a/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java +++ b/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java @@ -24,10 +24,9 @@ import android.telecom.PhoneAccountHandle; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.google.common.base.Optional; -import java.util.ArrayList; -import java.util.List; /** Provides hints to the user when selecting a SIM to make a call. */ +@SuppressWarnings("Guava") public interface SuggestionProvider { String EXTRA_SIM_SUGGESTION_REASON = "sim_suggestion_reason"; @@ -80,35 +79,25 @@ public interface SuggestionProvider { @NonNull Context context, @NonNull String number, @NonNull PhoneAccountHandle newAccount); /** - * Return a list of suggestion strings matching the list position of the {@code - * phoneAccountHandles}. The list will contain {@code null} if the PhoneAccountHandle does not - * have suggestions. + * Return the hint for {@code phoneAccountHandle}. Absent if no hint is available for the account. */ - @Nullable - static List<String> buildHint( - Context context, - List<PhoneAccountHandle> phoneAccountHandles, - @Nullable Suggestion suggestion) { + static Optional<String> getHint( + Context context, PhoneAccountHandle phoneAccountHandle, @Nullable Suggestion suggestion) { if (suggestion == null) { - return null; + return Optional.absent(); } - List<String> hints = new ArrayList<>(); - for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) { - if (!phoneAccountHandle.equals(suggestion.phoneAccountHandle)) { - hints.add(null); - continue; - } - switch (suggestion.reason) { - case INTRA_CARRIER: - hints.add(context.getString(R.string.pre_call_select_phone_account_hint_intra_carrier)); - break; - case FREQUENT: - hints.add(context.getString(R.string.pre_call_select_phone_account_hint_frequent)); - break; - default: - LogUtil.w("CallingAccountSelector.buildHint", "unhandled reason " + suggestion.reason); - } + if (!phoneAccountHandle.equals(suggestion.phoneAccountHandle)) { + return Optional.absent(); + } + switch (suggestion.reason) { + case INTRA_CARRIER: + return Optional.of( + context.getString(R.string.pre_call_select_phone_account_hint_intra_carrier)); + case FREQUENT: + return Optional.of(context.getString(R.string.pre_call_select_phone_account_hint_frequent)); + default: + LogUtil.w("CallingAccountSelector.getHint", "unhandled reason " + suggestion.reason); + return Optional.absent(); } - return hints; } } diff --git a/java/com/android/dialer/speeddial/SpeedDialAdapter.java b/java/com/android/dialer/speeddial/SpeedDialAdapter.java index 6f6ac5498..8a37e97dd 100644 --- a/java/com/android/dialer/speeddial/SpeedDialAdapter.java +++ b/java/com/android/dialer/speeddial/SpeedDialAdapter.java @@ -210,4 +210,9 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi public void setItemTouchHelper(ItemTouchHelper itemTouchHelper) { this.itemTouchHelper = itemTouchHelper; } + + /** Returns true if there are suggested contacts. */ + public boolean hasFrequents() { + return !speedDialUiItems.isEmpty() && getItemViewType(getItemCount() - 1) == RowType.SUGGESTION; + } } diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index 26893a8d3..b74c06239 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -34,6 +34,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; 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.DialerExecutorComponent; import com.android.dialer.common.concurrent.SupportUiListener; @@ -132,11 +133,6 @@ public class SpeedDialFragment extends Fragment { return rootLayout; } - public boolean hasFrequents() { - // TODO(calderwoodra) - return false; - } - @Override public void onResume() { super.onResume(); @@ -173,12 +169,17 @@ public class SpeedDialFragment extends Fragment { } private void onSpeedDialUiItemListLoaded(ImmutableList<SpeedDialUiItem> speedDialUiItems) { + LogUtil.enterBlock("SpeedDialFragment.onSpeedDialUiItemListLoaded"); // TODO(calderwoodra): Use DiffUtil to properly update and animate the change adapter.setSpeedDialUiItems( UiItemLoaderComponent.get(getContext()) .speedDialUiItemLoader() .insertDuoChannels(getContext(), speedDialUiItems)); adapter.notifyDataSetChanged(); + if (getActivity() != null) { + FragmentUtils.getParentUnsafe(this, HostInterface.class) + .setHasFrequents(adapter.hasFrequents()); + } } @Override @@ -359,4 +360,10 @@ public class SpeedDialFragment extends Fragment { Contacts.CONTENT_URI, String.valueOf(speedDialUiItem.contactId())))); } } + + /** Interface for {@link SpeedDialFragment} to communicate with its host/parent. */ + public interface HostInterface { + + void setHasFrequents(boolean hasFrequents); + } } diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java index ce771c3c8..4d6ac2d7f 100644 --- a/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java @@ -17,6 +17,7 @@ package com.android.dialer.speeddial.database; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; /** * Interface that databases support speed dial entries should implement. @@ -32,8 +33,10 @@ public interface SpeedDialEntryDao { * Insert new entries. * * <p>{@link SpeedDialEntry#id() ids} must be null. + * + * @return a map of the inserted entries to their new ids. */ - void insert(ImmutableList<SpeedDialEntry> entries); + ImmutableMap<SpeedDialEntry, Long> insert(ImmutableList<SpeedDialEntry> entries); /** * Insert a new entry. @@ -59,11 +62,12 @@ public interface SpeedDialEntryDao { /** * Inserts, updates and deletes rows all in on transaction. * + * @return a map of the inserted entries to their new ids. * @see #insert(ImmutableList) * @see #update(ImmutableList) * @see #delete(ImmutableList) */ - void insertUpdateAndDelete( + ImmutableMap<SpeedDialEntry, Long> insertUpdateAndDelete( ImmutableList<SpeedDialEntry> entriesToInsert, ImmutableList<SpeedDialEntry> entriesToUpdate, ImmutableList<Long> entriesToDelete); diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java index 137933fbe..544bb3613 100644 --- a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java @@ -26,6 +26,7 @@ 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.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.List; @@ -132,30 +133,39 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper } @Override - public void insert(ImmutableList<SpeedDialEntry> entries) { + public ImmutableMap<SpeedDialEntry, Long> insert(ImmutableList<SpeedDialEntry> entries) { if (entries.isEmpty()) { - return; + return ImmutableMap.of(); } SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { - insert(db, entries); + ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap = insert(db, entries); db.setTransactionSuccessful(); + return insertedEntriesToIdsMap; } finally { db.endTransaction(); db.close(); } } - private void insert(SQLiteDatabase writeableDatabase, ImmutableList<SpeedDialEntry> entries) { + private ImmutableMap<SpeedDialEntry, Long> insert( + SQLiteDatabase writeableDatabase, ImmutableList<SpeedDialEntry> entries) { + ImmutableMap.Builder<SpeedDialEntry, Long> insertedEntriesToIdsMap = ImmutableMap.builder(); for (SpeedDialEntry entry : entries) { Assert.checkArgument(entry.id() == null); - if (writeableDatabase.insert(TABLE_NAME, null, buildContentValuesWithoutId(entry)) == -1L) { + long id = writeableDatabase.insert(TABLE_NAME, null, buildContentValuesWithoutId(entry)); + if (id == -1L) { throw Assert.createUnsupportedOperationFailException( "Attempted to insert a row that already exists."); } + // It's impossible to insert two identical entries but this is an important assumption we need + // to verify because there's an assumption that each entry will correspond to exactly one id. + // ImmutableMap#put verifies this check for us. + insertedEntriesToIdsMap.put(entry, id); } + return insertedEntriesToIdsMap.build(); } @Override @@ -255,20 +265,21 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper } @Override - public void insertUpdateAndDelete( + public ImmutableMap<SpeedDialEntry, Long> insertUpdateAndDelete( ImmutableList<SpeedDialEntry> entriesToInsert, ImmutableList<SpeedDialEntry> entriesToUpdate, ImmutableList<Long> entriesToDelete) { if (entriesToInsert.isEmpty() && entriesToUpdate.isEmpty() && entriesToDelete.isEmpty()) { - return; + return ImmutableMap.of(); } SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { - insert(db, entriesToInsert); + ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap = insert(db, entriesToInsert); update(db, entriesToUpdate); delete(db, entriesToDelete); db.setTransactionSuccessful(); + return insertedEntriesToIdsMap; } finally { db.endTransaction(); db.close(); diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java index 24bc776e7..9bda3fb31 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java @@ -136,6 +136,15 @@ public abstract class SpeedDialUiItem { return builder.build(); } + public SpeedDialEntry buildSpeedDialEntry() { + return SpeedDialEntry.builder() + .setId(speedDialEntryId()) + .setLookupKey(lookupKey()) + .setContactId(contactId()) + .setDefaultChannel(defaultChannel()) + .build(); + } + /** * Returns a video channel if there is exactly one video channel or the default channel is a video * channel. diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java index 7107706fe..921468773 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java @@ -44,6 +44,7 @@ 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.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.ArrayList; @@ -146,13 +147,7 @@ public final class SpeedDialUiItemLoader { } // Insert a new entry into the SpeedDialEntry database - getSpeedDialEntryDao() - .insert( - SpeedDialEntry.builder() - .setLookupKey(item.lookupKey()) - .setContactId(item.contactId()) - .setDefaultChannel(item.defaultChannel()) - .build()); + getSpeedDialEntryDao().insert(item.buildSpeedDialEntry()); } return loadSpeedDialUiItemsInternal(); } @@ -216,23 +211,55 @@ public final class SpeedDialUiItemLoader { contact.toBuilder().setDefaultChannel(contact.channels().get(0)).build()); } else if (speedDialUiItems.stream().noneMatch(c -> c.contactId() == contact.contactId())) { - entriesToInsert.add( - SpeedDialEntry.builder() - .setLookupKey(contact.lookupKey()) - .setContactId(contact.contactId()) - .setDefaultChannel(contact.defaultChannel()) - .build()); + entriesToInsert.add(contact.buildSpeedDialEntry()); // These are our newly starred contacts speedDialUiItems.add(contact); } } - db.insertUpdateAndDelete( - ImmutableList.copyOf(entriesToInsert), - ImmutableList.copyOf(entriesToUpdate), - ImmutableList.copyOf(entriesToDelete)); - return ImmutableList.copyOf(speedDialUiItems); + ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap = + db.insertUpdateAndDelete( + ImmutableList.copyOf(entriesToInsert), + ImmutableList.copyOf(entriesToUpdate), + ImmutableList.copyOf(entriesToDelete)); + return speedDialUiItemsWithUpdatedIds(speedDialUiItems, insertedEntriesToIdsMap); + } + + /** + * Since newly starred contacts sometimes aren't in the SpeedDialEntry database, we couldn't set + * their ids when we created our initial list of {@link SpeedDialUiItem speedDialUiItems}. Now + * that we've inserted the entries into the database and we have their ids, build a new list of + * speedDialUiItems with the now known ids. + */ + private ImmutableList<SpeedDialUiItem> speedDialUiItemsWithUpdatedIds( + List<SpeedDialUiItem> speedDialUiItems, + ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap) { + if (insertedEntriesToIdsMap.isEmpty()) { + // There were no newly inserted entries, so all entries ids are set already. + return ImmutableList.copyOf(speedDialUiItems); + } + + ImmutableList.Builder<SpeedDialUiItem> updatedItems = ImmutableList.builder(); + for (SpeedDialUiItem speedDialUiItem : speedDialUiItems) { + SpeedDialEntry entry = speedDialUiItem.buildSpeedDialEntry(); + if (insertedEntriesToIdsMap.containsKey(entry)) { + // Get the id for newly inserted entry, update our SpeedDialUiItem and add it to our list + Long id = Assert.isNotNull(insertedEntriesToIdsMap.get(entry)); + updatedItems.add(speedDialUiItem.toBuilder().setSpeedDialEntryId(id).build()); + continue; + } + + // Starred contacts that aren't in the map, should already have speed dial entry ids. + // Non-starred contacts (suggestions) aren't in the speed dial entry database, so they + // shouldn't have speed dial entry ids. + Assert.checkArgument( + speedDialUiItem.isStarred() == (speedDialUiItem.speedDialEntryId() != null), + "Contact must be starred with a speed dial entry id, or not starred with no id " + + "(suggested contacts)"); + updatedItems.add(speedDialUiItem); + } + return updatedItems.build(); } /** diff --git a/java/com/android/dialer/theme/res/color/dialer_primary_text_color.xml b/java/com/android/dialer/theme/res/color/dialer_primary_text_color.xml new file mode 100644 index 000000000..347db41bc --- /dev/null +++ b/java/com/android/dialer/theme/res/color/dialer_primary_text_color.xml @@ -0,0 +1,21 @@ +<?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 + --> +<!-- Primary text color in the Phone app --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="@color/dialer_disabled_text_color"/> + <item android:color="#212121"/> +</selector>
\ No newline at end of file diff --git a/java/com/android/dialer/theme/res/color/dialer_secondary_text_color.xml b/java/com/android/dialer/theme/res/color/dialer_secondary_text_color.xml new file mode 100644 index 000000000..920b46792 --- /dev/null +++ b/java/com/android/dialer/theme/res/color/dialer_secondary_text_color.xml @@ -0,0 +1,21 @@ +<?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 + --> +<!-- Secondary text color in the Phone app --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="@color/dialer_disabled_text_color"/> + <item android:color="#636363"/> +</selector>
\ No newline at end of file diff --git a/java/com/android/dialer/theme/res/values/colors.xml b/java/com/android/dialer/theme/res/values/colors.xml index e80fc4b30..2185d861c 100644 --- a/java/com/android/dialer/theme/res/values/colors.xml +++ b/java/com/android/dialer/theme/res/values/colors.xml @@ -36,15 +36,11 @@ <color name="dialer_secondary_color">#F50057</color> - <!-- Primary text color in the Phone app --> - <color name="dialer_primary_text_color">#333333</color> <color name="dialer_primary_text_color_white">#ffffff</color> <color name="dialer_edit_text_hint_color">#DE78909C</color> - <!-- Secondary text color in the Phone app --> - <color name="dialer_secondary_text_color">#636363</color> <!-- 38% opacity --> - <color name="dialer_secondary_text_color_hiden">#61000000</color> + <color name="dialer_disabled_text_color">#9E9E9E</color> <color name="dialer_link_color">#2A56C6</color> diff --git a/java/com/android/incallui/ActiveCallsCallListListener.java b/java/com/android/incallui/ActiveCallsCallListListener.java new file mode 100644 index 000000000..ce9f9a36d --- /dev/null +++ b/java/com/android/incallui/ActiveCallsCallListListener.java @@ -0,0 +1,73 @@ +/* + * 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.content.Context; +import android.support.annotation.NonNull; +import com.android.dialer.activecalls.ActiveCallInfo; +import com.android.dialer.activecalls.ActiveCallsComponent; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; +import com.android.incallui.call.DialerCall.State; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; + +/** Updates {@link com.android.dialer.activecalls.ActiveCalls} */ +@SuppressWarnings("Guava") +public class ActiveCallsCallListListener implements CallList.Listener { + + private final Context appContext; + + ActiveCallsCallListListener(Context appContext) { + this.appContext = appContext; + } + + @Override + public void onIncomingCall(DialerCall call) {} + + @Override + public void onUpgradeToVideo(DialerCall call) {} + + @Override + public void onSessionModificationStateChange(DialerCall call) {} + + @Override + public void onCallListChange(CallList callList) { + ImmutableList.Builder<ActiveCallInfo> activeCalls = ImmutableList.builder(); + for (DialerCall call : callList.getAllCalls()) { + if (call.getState() != State.DISCONNECTED) { + activeCalls.add( + ActiveCallInfo.builder() + .setPhoneAccountHandle(Optional.fromNullable(call.getAccountHandle())) + .build()); + } + } + ActiveCallsComponent.get(appContext).activeCalls().setActiveCalls(activeCalls.build()); + } + + @Override + public void onDisconnect(DialerCall call) {} + + @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/ConferenceParticipantListAdapter.java b/java/com/android/incallui/ConferenceParticipantListAdapter.java index 4780974ef..dc793f7f8 100644 --- a/java/com/android/incallui/ConferenceParticipantListAdapter.java +++ b/java/com/android/incallui/ConferenceParticipantListAdapter.java @@ -356,7 +356,7 @@ public class ConferenceParticipantListAdapter extends BaseAdapter { statusTextView.setText(onHoldText); statusTextView.setVisibility(View.VISIBLE); - int onHoldColor = getContext().getColor(R.color.dialer_secondary_text_color_hiden); + int onHoldColor = getContext().getColor(R.color.dialer_disabled_text_color); nameTextView.setTextColor(onHoldColor); numberTextView.setTextColor(onHoldColor); diff --git a/java/com/android/incallui/ContactInfoCache.java b/java/com/android/incallui/ContactInfoCache.java index 165ec13bf..eefd4833c 100644 --- a/java/com/android/incallui/ContactInfoCache.java +++ b/java/com/android/incallui/ContactInfoCache.java @@ -522,20 +522,20 @@ public class ContactInfoCache implements OnImageLoadCompleteListener { } boolean hasUpdate = false; - if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name)) { - callerInfo.name = cequintCallerIdContact.name; + if (TextUtils.isEmpty(callerInfo.name) && !TextUtils.isEmpty(cequintCallerIdContact.name())) { + callerInfo.name = cequintCallerIdContact.name(); hasUpdate = true; } - if (!TextUtils.isEmpty(cequintCallerIdContact.geoDescription)) { - callerInfo.geoDescription = cequintCallerIdContact.geoDescription; + if (!TextUtils.isEmpty(cequintCallerIdContact.geolocation())) { + callerInfo.geoDescription = cequintCallerIdContact.geolocation(); callerInfo.shouldShowGeoDescription = true; hasUpdate = true; } // Don't overwrite photo in local contacts. if (!callerInfo.contactExists && callerInfo.contactDisplayPhotoUri == null - && cequintCallerIdContact.imageUrl != null) { - callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.imageUrl); + && cequintCallerIdContact.photoUri() != null) { + callerInfo.contactDisplayPhotoUri = Uri.parse(cequintCallerIdContact.photoUri()); hasUpdate = true; } // Set contact to exist to avoid phone number service lookup. diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 91f557bd5..2b6c2b49d 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -55,12 +55,15 @@ import android.view.animation.AnimationUtils; import android.widget.CheckBox; import android.widget.Toast; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; +import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions; +import com.android.contacts.common.widget.SelectPhoneAccountDialogOptionsUtil; import com.android.dialer.animation.AnimUtils; import com.android.dialer.animation.AnimationListenerAdapter; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.common.concurrent.UiListener; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.logging.DialerImpression.Type; import com.android.dialer.logging.Logger; @@ -69,6 +72,7 @@ import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.preferredsim.PreferredAccountRecorder; import com.android.dialer.preferredsim.PreferredAccountWorker; +import com.android.dialer.preferredsim.PreferredAccountWorker.Result; import com.android.dialer.preferredsim.suggestion.SuggestionProvider; import com.android.dialer.util.ViewUtil; import com.android.incallui.answer.bindings.AnswerBindings; @@ -101,6 +105,7 @@ import com.android.incallui.video.protocol.VideoCallScreen; import com.android.incallui.video.protocol.VideoCallScreenDelegate; import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory; import com.google.common.base.Optional; +import com.google.common.util.concurrent.ListenableFuture; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -129,10 +134,8 @@ public class InCallActivity extends TransactionSafeFragmentActivity private static Optional<Integer> audioRouteForTesting = Optional.absent(); - private final InternationalCallOnWifiCallback internationalCallOnWifiCallback = - new InternationalCallOnWifiCallback(); - private SelectPhoneAccountListener selectPhoneAccountListener; + private UiListener<Result> preferredAccountWorkerResultListener; private Animation dialpadSlideInAnimation; private Animation dialpadSlideOutAnimation; @@ -189,6 +192,10 @@ public class InCallActivity extends TransactionSafeFragmentActivity Trace.beginSection("InCallActivity.onCreate"); super.onCreate(bundle); + preferredAccountWorkerResultListener = + DialerExecutorComponent.get(this) + .createUiListener(getFragmentManager(), "preferredAccountWorkerResultListener"); + selectPhoneAccountListener = new SelectPhoneAccountListener(getApplicationContext()); if (bundle != null) { @@ -245,13 +252,6 @@ public class InCallActivity extends TransactionSafeFragmentActivity } } - InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment = - (InternationalCallOnWifiDialogFragment) - getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI); - if (existingInternationalCallOnWifiDialogFragment != null) { - existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback); - } - inCallOrientationEventListener = new InCallOrientationEventListener(this); getWindow() @@ -368,58 +368,84 @@ public class InCallActivity extends TransactionSafeFragmentActivity return false; } - DialerExecutorComponent.get(this) - .dialerExecutorFactory() - .createNonUiTaskBuilder(new PreferredAccountWorker(waitingForAccountCall.getNumber())) - .onSuccess( - (result -> { - if (result.getPhoneAccountHandle().isPresent()) { - Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED); - selectPhoneAccountListener.onPhoneAccountSelected( - result.getPhoneAccountHandle().get(), false, waitingForAccountCall.getId()); - return; - } - if (result.getSuggestion().isPresent()) { - LogUtil.i( - "CallingAccountSelector.processPreferredAccount", - "SIM suggested: " + result.getSuggestion().get().reason); - if (result.getSuggestion().get().shouldAutoSelect) { - Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED); - LogUtil.i( - "CallingAccountSelector.processPreferredAccount", "Auto selected suggestion"); - selectPhoneAccountListener.onPhoneAccountSelected( - result.getSuggestion().get().phoneAccountHandle, - false, - waitingForAccountCall.getId()); - return; - } - } - Bundle extras = waitingForAccountCall.getIntentExtras(); - List<PhoneAccountHandle> phoneAccountHandles = - extras == null - ? new ArrayList<>() - : extras.getParcelableArrayList(Call.AVAILABLE_PHONE_ACCOUNTS); - - waitingForAccountCall.setPreferredAccountRecorder( - new PreferredAccountRecorder( - waitingForAccountCall.getNumber(), - result.getSuggestion().orNull(), - result.getDataId().orNull())); - selectPhoneAccountDialogFragment = - SelectPhoneAccountDialogFragment.newInstance( - R.string.select_phone_account_for_calls, - result.getDataId().isPresent() /* canSetDefault */, - R.string.select_phone_account_for_calls_remember /* setDefaultResId */, - phoneAccountHandles, - selectPhoneAccountListener, - waitingForAccountCall.getId(), - SuggestionProvider.buildHint( - this, phoneAccountHandles, result.getSuggestion().orNull() /* hints */)); - selectPhoneAccountDialogFragment.show( - getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT); - })) - .build() - .executeParallel(this); + ListenableFuture<PreferredAccountWorker.Result> preferredAccountFuture = + DialerExecutorComponent.get(this) + .backgroundExecutor() + .submit( + () -> { + try { + return new PreferredAccountWorker(waitingForAccountCall.getNumber()) + .doInBackground(getApplicationContext()); + } catch (Throwable throwable) { + throw new Exception(throwable); + } + }); + + preferredAccountWorkerResultListener.listen( + this, + preferredAccountFuture, + result -> { + if (result.getPhoneAccountHandle().isPresent()) { + Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED); + selectPhoneAccountListener.onPhoneAccountSelected( + result.getPhoneAccountHandle().get(), false, waitingForAccountCall.getId()); + return; + } + if (result.getSuggestion().isPresent()) { + LogUtil.i( + "CallingAccountSelector.processPreferredAccount", + "SIM suggested: " + result.getSuggestion().get().reason); + if (result.getSuggestion().get().shouldAutoSelect) { + Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED); + LogUtil.i( + "CallingAccountSelector.processPreferredAccount", "Auto selected suggestion"); + selectPhoneAccountListener.onPhoneAccountSelected( + result.getSuggestion().get().phoneAccountHandle, + false, + waitingForAccountCall.getId()); + return; + } + } + Bundle extras = waitingForAccountCall.getIntentExtras(); + List<PhoneAccountHandle> phoneAccountHandles = + extras == null + ? new ArrayList<>() + : extras.getParcelableArrayList(Call.AVAILABLE_PHONE_ACCOUNTS); + + waitingForAccountCall.setPreferredAccountRecorder( + new PreferredAccountRecorder( + waitingForAccountCall.getNumber(), + result.getSuggestion().orNull(), + result.getDataId().orNull())); + SelectPhoneAccountDialogOptions.Builder optionsBuilder = + SelectPhoneAccountDialogOptions.newBuilder() + .setTitle(R.string.select_phone_account_for_calls) + .setCanSetDefault(result.getDataId().isPresent()) + .setSetDefaultLabel(R.string.select_phone_account_for_calls_remember) + .setCallId(waitingForAccountCall.getId()); + + for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) { + SelectPhoneAccountDialogOptions.Entry.Builder entryBuilder = + SelectPhoneAccountDialogOptions.Entry.newBuilder(); + SelectPhoneAccountDialogOptionsUtil.setPhoneAccountHandle( + entryBuilder, phoneAccountHandle); + Optional<String> hint = + SuggestionProvider.getHint( + this, phoneAccountHandle, result.getSuggestion().orNull()); + if (hint.isPresent()) { + entryBuilder.setHint(hint.get()); + } + optionsBuilder.addEntries(entryBuilder); + } + + selectPhoneAccountDialogFragment = + SelectPhoneAccountDialogFragment.newInstance( + optionsBuilder.build(), selectPhoneAccountListener); + selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT); + }, + throwable -> { + throw new RuntimeException(throwable); + }); return true; } @@ -1201,16 +1227,8 @@ public class InCallActivity extends TransactionSafeFragmentActivity } public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) { - if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) { - LogUtil.i( - "InCallActivity.showDialogForInternationalCallOnWifi", - "InternationalCallOnWifiDialogFragment.shouldShow returned false"); - return; - } - InternationalCallOnWifiDialogFragment fragment = - InternationalCallOnWifiDialogFragment.newInstance( - call.getId(), internationalCallOnWifiCallback); + InternationalCallOnWifiDialogFragment.newInstance(call.getId()); fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI); } @@ -1785,28 +1803,6 @@ public class InCallActivity extends TransactionSafeFragmentActivity static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled"; } - private static final class InternationalCallOnWifiCallback - implements InternationalCallOnWifiDialogFragment.Callback { - private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName(); - - @Override - public void continueCall(@NonNull String callId) { - LogUtil.i(TAG, "Continuing call with ID: %s", callId); - } - - @Override - public void cancelCall(@NonNull String callId) { - DialerCall call = CallList.getInstance().getCallById(callId); - if (call == null) { - LogUtil.i(TAG, "Call destroyed before the dialog is closed"); - return; - } - - LogUtil.i(TAG, "Disconnecting international call on WiFi"); - call.disconnect(); - } - } - private static final class SelectPhoneAccountListener extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener { private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName(); diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index 5e08c6969..526cc64d3 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -71,6 +71,8 @@ import com.android.incallui.latencyreport.LatencyReport; import com.android.incallui.legacyblocking.BlockedNumberContentObserver; import com.android.incallui.spam.SpamCallListListener; import com.android.incallui.speakeasy.SpeakEasyCallManager; +import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogActivity; +import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment; import com.android.incallui.videosurface.bindings.VideoSurfaceBindings; import com.android.incallui.videosurface.protocol.VideoSurfaceTexture; import com.android.incallui.videotech.utils.VideoUtils; @@ -197,6 +199,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud private InCallCameraManager inCallCameraManager; private FilteredNumberAsyncQueryHandler filteredQueryHandler; private CallList.Listener spamCallListListener; + private CallList.Listener activeCallsListener; /** Whether or not we are currently bound and waiting for Telecom to send us a new call. */ private boolean boundAndWaitingForOutgoingCall; /** Determines if the InCall UI is in fullscreen mode or not. */ @@ -383,6 +386,8 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud new SpamCallListListener( context, DialerExecutorComponent.get(context).dialerExecutorFactory()); this.callList.addListener(spamCallListListener); + activeCallsListener = new ActiveCallsCallListListener(context); + this.callList.addListener(activeCallsListener); VideoPauseController.getInstance().setUp(this); @@ -772,8 +777,22 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud @Override public void onInternationalCallOnWifi(@NonNull DialerCall call) { LogUtil.enterBlock("InCallPresenter.onInternationalCallOnWifi"); + + if (!InternationalCallOnWifiDialogFragment.shouldShow(context)) { + LogUtil.i( + "InCallPresenter.onInternationalCallOnWifi", + "InternationalCallOnWifiDialogFragment.shouldShow returned false"); + return; + } + if (inCallActivity != null) { inCallActivity.showDialogForInternationalCallOnWifi(call); + } else { + Intent intent = new Intent(context, InternationalCallOnWifiDialogActivity.class); + // Prevent showing MainActivity with InternationalCallOnWifiDialogActivity on above + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.putExtra(InternationalCallOnWifiDialogActivity.EXTRA_CALL_ID, call.getId()); + context.startActivity(intent); } } @@ -858,6 +877,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null; inCallActivity.dismissKeyguard(hasCall); } + Trace.endSection(); } diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index c153503ac..9dfe7abcb 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -157,7 +157,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa private String lastForwardedNumber; private boolean isCallForwarded; private String callSubject; - private PhoneAccountHandle phoneAccountHandle; + @Nullable private PhoneAccountHandle phoneAccountHandle; @CallHistoryStatus private int callHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN; private boolean isSpam; private boolean isBlocked; diff --git a/java/com/android/incallui/res/layout/caller_in_conference.xml b/java/com/android/incallui/res/layout/caller_in_conference.xml index be4eca5f6..23bb28512 100644 --- a/java/com/android/incallui/res/layout/caller_in_conference.xml +++ b/java/com/android/incallui/res/layout/caller_in_conference.xml @@ -68,7 +68,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/SecondaryText" - android:textColor="@color/dialer_secondary_text_color_hiden" + android:textColor="@color/dialer_disabled_text_color" android:visibility="gone"/> <!-- Number --> diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index 2d70b6b12..7bfa100ea 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -49,6 +49,8 @@ import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.common.UiUtil; import com.android.dialer.lettertile.LetterTileDrawable; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; import com.android.dialer.rtt.RttTranscript; import com.android.dialer.rtt.RttTranscriptMessage; import com.android.dialer.util.DrawableConverter; @@ -219,6 +221,7 @@ public class RttChatFragment extends Fragment submitButton = view.findViewById(R.id.rtt_chat_submit_button); submitButton.setOnClickListener( v -> { + Logger.get(getContext()).logImpression(DialerImpression.Type.RTT_SEND_BUTTON_CLICKED); adapter.submitLocalMessage(); resumeInput(""); rttCallScreenDelegate.onLocalMessage(Constants.BUBBLE_BREAKER); @@ -254,6 +257,8 @@ public class RttChatFragment extends Fragment public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEND) { if (!TextUtils.isEmpty(editText.getText())) { + Logger.get(getContext()) + .logImpression(DialerImpression.Type.RTT_KEYBOARD_SEND_BUTTON_CLICKED); submitButton.performClick(); } return true; diff --git a/java/com/android/incallui/telecomeventui/AndroidManifest.xml b/java/com/android/incallui/telecomeventui/AndroidManifest.xml index 861b9368a..a2134a3bd 100644 --- a/java/com/android/incallui/telecomeventui/AndroidManifest.xml +++ b/java/com/android/incallui/telecomeventui/AndroidManifest.xml @@ -12,4 +12,15 @@ See the License for the specific language governing permissions and limitations under the License. --> -<manifest package="com.android.incallui.telecomeventui"/>
\ No newline at end of file +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.incallui.telecomeventui"> + <application> + <activity + android:excludeFromRecents="true" + android:exported="true" + android:name="com.android.incallui.telecomeventui.InternationalCallOnWifiDialogActivity" + android:noHistory="true" + android:theme="@style/Theme.Incall.DialogHolder"/> + </application> +</manifest> diff --git a/java/com/android/incallui/telecomeventui/InternationalCallOnWifiDialogActivity.java b/java/com/android/incallui/telecomeventui/InternationalCallOnWifiDialogActivity.java new file mode 100644 index 000000000..192c1603a --- /dev/null +++ b/java/com/android/incallui/telecomeventui/InternationalCallOnWifiDialogActivity.java @@ -0,0 +1,101 @@ +/* + * 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.telecomeventui; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; + +/** + * Activity containing dialog that may be shown when users place an outgoing call to an + * international number while on Wifi. + */ +public class InternationalCallOnWifiDialogActivity extends AppCompatActivity + implements CallList.Listener { + + public static final String EXTRA_CALL_ID = "extra_call_id"; + 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); + if (TextUtils.isEmpty(callId)) { + finish(); + return; + } + + InternationalCallOnWifiDialogFragment fragment = + InternationalCallOnWifiDialogFragment.newInstance(callId); + 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/telecomeventui/InternationalCallOnWifiDialogFragment.java b/java/com/android/incallui/telecomeventui/InternationalCallOnWifiDialogFragment.java index 2b602f876..71a8be483 100644 --- a/java/com/android/incallui/telecomeventui/InternationalCallOnWifiDialogFragment.java +++ b/java/com/android/incallui/telecomeventui/InternationalCallOnWifiDialogFragment.java @@ -21,16 +21,18 @@ import android.app.Dialog; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.PreferenceManager; 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.os.UserManagerCompat; import android.view.View; import android.widget.CheckBox; import com.android.dialer.common.Assert; +import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; +import com.android.dialer.storage.StorageComponent; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; /** * Dialog that may be shown when users place an outgoing call to an international number while on @@ -53,7 +55,7 @@ public class InternationalCallOnWifiDialogFragment extends DialogFragment { return false; } - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences preferences = StorageComponent.get(context).unencryptedSharedPrefs(); boolean shouldShow = preferences.getBoolean(ALWAYS_SHOW_WARNING_PREFERENCE_KEY, true); LogUtil.i("InternationalCallOnWifiDialogFragment.shouldShow", "result: %b", shouldShow); @@ -61,27 +63,13 @@ public class InternationalCallOnWifiDialogFragment extends DialogFragment { } /** - * Called in response to user interaction with the {@link InternationalCallOnWifiDialogFragment}. - */ - public interface Callback { - - /** Indicates that the user wishes to proceed with the call represented by the given call id. */ - void continueCall(@NonNull String callId); - - /** Indicates that the user wishes to cancel the call represented by the given call id. */ - void cancelCall(@NonNull String callId); - } - - /** * Returns a new instance of {@link InternationalCallOnWifiDialogFragment} with the given * callback. * * <p>Prefer this method over the default constructor. */ - public static InternationalCallOnWifiDialogFragment newInstance( - @NonNull String callId, @NonNull Callback callback) { + public static InternationalCallOnWifiDialogFragment newInstance(@NonNull String callId) { InternationalCallOnWifiDialogFragment fragment = new InternationalCallOnWifiDialogFragment(); - fragment.setCallback(callback); Bundle args = new Bundle(); args.putString(ARG_CALL_ID, Assert.isNotNull(callId)); fragment.setArguments(args); @@ -93,31 +81,12 @@ public class InternationalCallOnWifiDialogFragment extends DialogFragment { * InternationalCallOnWifiDialogFragment InternationalCallOnWifiDialogFragments}. */ @VisibleForTesting - static final String ALWAYS_SHOW_WARNING_PREFERENCE_KEY = + public static final String ALWAYS_SHOW_WARNING_PREFERENCE_KEY = "ALWAYS_SHOW_INTERNATIONAL_CALL_ON_WIFI_WARNING"; /** Key in the arguments bundle for call id. */ private static final String ARG_CALL_ID = "call_id"; - /** - * Callback which will receive information about user interactions with this dialog. - * - * <p>This is Nullable in the event that the dialog is destroyed by the framework, but doesn't - * have a callback reattached. Ideally, the InCallActivity would implement the callback and we - * would use FragmentUtils.getParentUnsafe instead of holding onto the callback here, but that's - * not possible with the existing InCallActivity/InCallActivityCommon implementation. - */ - @Nullable private Callback callback; - - /** - * Sets the callback for this dialog. - * - * <p>Used to reset the callback after state changes. - */ - public void setCallback(@NonNull Callback callback) { - this.callback = Assert.isNotNull(callback); - } - @NonNull @Override public Dialog onCreateDialog(Bundle bundle) { @@ -134,7 +103,7 @@ public class InternationalCallOnWifiDialogFragment extends DialogFragment { CheckBox alwaysWarn = dialogView.findViewById(R.id.always_warn); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + SharedPreferences preferences = StorageComponent.get(getActivity()).unencryptedSharedPrefs(); // The default is set to false in this case to ensure that the first time the dialog opens, // the checkbox is unchecked. alwaysWarn.setChecked(preferences.getBoolean(ALWAYS_SHOW_WARNING_PREFERENCE_KEY, false)); @@ -163,7 +132,7 @@ public class InternationalCallOnWifiDialogFragment extends DialogFragment { preferences.edit().putBoolean(ALWAYS_SHOW_WARNING_PREFERENCE_KEY, alwaysWarn).apply(); // Neither callback nor callId are null in normal circumstances. See comments on callback - callback.continueCall(getArguments().getString(ARG_CALL_ID)); + continueCall(getArguments().getString(ARG_CALL_ID)); } private void onNegativeButtonClick(@NonNull SharedPreferences preferences, boolean alwaysWarn) { @@ -174,6 +143,37 @@ public class InternationalCallOnWifiDialogFragment extends DialogFragment { preferences.edit().putBoolean(ALWAYS_SHOW_WARNING_PREFERENCE_KEY, alwaysWarn).apply(); // Neither callback nor callId are null in normal circumstances. See comments on callback - callback.cancelCall(getArguments().getString(ARG_CALL_ID)); + cancelCall(getArguments().getString(ARG_CALL_ID)); + } + + private void continueCall(@NonNull String callId) { + LogUtil.i( + "InternationalCallOnWifiDialogFragment.continueCall", + "Continuing call with ID: %s", + callId); + InternationalCallOnWifiDialogActivity activity = + FragmentUtils.getParent(this, InternationalCallOnWifiDialogActivity.class); + if (activity != null) { + activity.finish(); + } + } + + private void cancelCall(@NonNull String callId) { + DialerCall call = CallList.getInstance().getCallById(callId); + if (call == null) { + LogUtil.i( + "InternationalCallOnWifiDialogFragment.cancelCall", + "Call destroyed before the dialog is closed"); + } else { + LogUtil.i( + "InternationalCallOnWifiDialogFragment.cancelCall", + "Disconnecting international call on WiFi"); + call.disconnect(); + } + InternationalCallOnWifiDialogActivity activity = + FragmentUtils.getParent(this, InternationalCallOnWifiDialogActivity.class); + if (activity != null) { + activity.finish(); + } } } |