diff options
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(); + } } } |