diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2017-10-17 16:52:28 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2017-10-17 16:52:28 +0000 |
commit | 0d1362b28afb4a71b8b2a56bad2be3fcf2802de6 (patch) | |
tree | 91fb2d0f5c065f024a7332ec664c794cdf30f413 /java | |
parent | 5a6cb962ae30ad9e308b5bcc00920daf84618808 (diff) | |
parent | c114cce52656e6ace2ffdfd075b0e8cd140cd498 (diff) |
Merge changes I84ec0ac5,I2b92e1e0,Ic30fa6c6
* changes:
Move Duo related constants out of interface
Dismissing location prompt now actually prevents it from reshowing.
Add GSM conference calling to simulator.
Diffstat (limited to 'java')
20 files changed, 580 insertions, 102 deletions
diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java index 679901e9f..016bce322 100644 --- a/java/com/android/dialer/app/calllog/CallLogAdapter.java +++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java @@ -75,6 +75,7 @@ import com.android.dialer.compat.android.provider.VoicemailCompat; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.duo.Duo; import com.android.dialer.duo.DuoComponent; +import com.android.dialer.duo.DuoConstants; import com.android.dialer.duo.DuoListener; import com.android.dialer.enrichedcall.EnrichedCallCapabilities; import com.android.dialer.enrichedcall.EnrichedCallComponent; @@ -399,11 +400,7 @@ public class CallLogAdapter extends GroupingListAdapter if (intentProvider == null) { return false; } - String packageName = DuoComponent.get(mActivity).getDuo().getPackageName(); - if (packageName == null) { - return false; - } - return packageName.equals(intentProvider.getIntent(mActivity).getPackage()); + return DuoConstants.PACKAGE_NAME.equals(intentProvider.getIntent(mActivity).getPackage()); } }; @@ -697,7 +694,7 @@ public class CallLogAdapter extends GroupingListAdapter @Override protected void addGroups(Cursor cursor) { - mCallLogGroupBuilder.addGroups(cursor, mActivity); + mCallLogGroupBuilder.addGroups(cursor); } @Override @@ -981,11 +978,9 @@ public class CallLogAdapter extends GroupingListAdapter .setFeatures(cursor.getInt(CallLogQuery.FEATURES)); String phoneAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME); - if (getDuo().getPhoneAccountComponentName() != null - && getDuo() - .getPhoneAccountComponentName() - .flattenToString() - .equals(phoneAccountComponentName)) { + if (DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME + .flattenToString() + .equals(phoneAccountComponentName)) { entry.setIsDuoCall(true); } diff --git a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java index 57a8be730..513c8aa59 100644 --- a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java +++ b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java @@ -16,7 +16,6 @@ package com.android.dialer.app.calllog; -import android.content.Context; import android.database.Cursor; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; @@ -74,7 +73,7 @@ public class CallLogGroupBuilder { * * @see GroupingListAdapter#addGroups(Cursor) */ - public void addGroups(Cursor cursor, Context context) { + public void addGroups(Cursor cursor) { final int count = cursor.getCount(); if (count == 0) { return; @@ -99,7 +98,7 @@ public class CallLogGroupBuilder { int groupFeatures = cursor.getInt(CallLogQuery.FEATURES); int groupCallbackAction = CallbackActionHelper.getCallbackAction( - groupNumber, groupFeatures, groupAccountComponentName, context); + groupNumber, groupFeatures, groupAccountComponentName); mGroupCreator.setCallbackAction(firstRowId, groupCallbackAction); // Instantiate other group values to those of the first call in the cursor. @@ -134,7 +133,7 @@ public class CallLogGroupBuilder { accountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME); accountId = cursor.getString(CallLogQuery.ACCOUNT_ID); callbackAction = - CallbackActionHelper.getCallbackAction(number, features, accountComponentName, context); + CallbackActionHelper.getCallbackAction(number, features, accountComponentName); final boolean isSameNumber = equalNumbers(groupNumber, number); final boolean isSamePostDialDigits = groupPostDialDigits.equals(numberPostDialDigits); diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index f0852bdb5..d5dfb06dc 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -27,7 +27,6 @@ import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.IntDef; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.CardView; @@ -75,8 +74,7 @@ import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.dialercontact.SimDetails; -import com.android.dialer.duo.Duo; -import com.android.dialer.duo.DuoComponent; +import com.android.dialer.duo.DuoConstants; import com.android.dialer.lettertile.LetterTileDrawable; import com.android.dialer.lettertile.LetterTileDrawable.ContactType; import com.android.dialer.logging.ContactSource; @@ -746,7 +744,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder private boolean showDuoPrimaryButton() { return accountHandle != null - && accountHandle.getComponentName().equals(getDuo().getPhoneAccountComponentName()) + && accountHandle.getComponentName().equals(DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME) && duoReady; } @@ -961,7 +959,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder // We check to see if we are starting a Duo intent. The reason is Duo // intents need to be started using startActivityForResult instead of the usual startActivity String packageName = intent.getPackage(); - if (packageName != null && packageName.equals(getDuo().getPackageName())) { + if (DuoConstants.PACKAGE_NAME.equals(packageName)) { Logger.get(mContext) .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG); if (isNonContactEntry(info)) { @@ -1125,11 +1123,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder return callDetailsEntries; } - @NonNull - private Duo getDuo() { - return DuoComponent.get(mContext).getDuo(); - } - @Override public void onCreateContextMenu( final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index f50751e2b..fff68d4c4 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -53,6 +53,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.compat.android.provider.VoicemailCompat; +import com.android.dialer.duo.DuoConstants; import com.android.dialer.enrichedcall.FuzzyPhoneNumberMatcher; import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; @@ -253,6 +254,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { if (newCalls == null) { return; } + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); Iterator<NewCall> iterator = newCalls.iterator(); while (iterator.hasNext()) { @@ -269,6 +271,10 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { if (phoneAccount == null) { continue; } + if (DuoConstants.PHONE_ACCOUNT_HANDLE.equals(phoneAccountHandle)) { + iterator.remove(); + continue; + } if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) { LogUtil.i( "MissedCallNotifier.removeSelfManagedCalls", diff --git a/java/com/android/dialer/calllogutils/CallbackActionHelper.java b/java/com/android/dialer/calllogutils/CallbackActionHelper.java index 304994305..1e219f144 100644 --- a/java/com/android/dialer/calllogutils/CallbackActionHelper.java +++ b/java/com/android/dialer/calllogutils/CallbackActionHelper.java @@ -16,12 +16,10 @@ package com.android.dialer.calllogutils; -import android.content.Context; import android.provider.CallLog.Calls; import android.support.annotation.IntDef; import android.text.TextUtils; -import com.android.dialer.duo.Duo; -import com.android.dialer.duo.DuoComponent; +import com.android.dialer.duo.DuoConstants; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -45,12 +43,11 @@ public class CallbackActionHelper { * @param features Value of features in column {@link android.provider.CallLog.Calls#FEATURES}. * @param phoneAccountComponentName Account name in column {@link * android.provider.CallLog.Calls#PHONE_ACCOUNT_COMPONENT_NAME}. - * @param context The context in which the method is called. * @return One of the values in {@link CallbackAction} */ public static @CallbackAction int getCallbackAction( - String number, int features, String phoneAccountComponentName, Context context) { - return getCallbackAction(number, features, isDuoCall(phoneAccountComponentName, context)); + String number, int features, String phoneAccountComponentName) { + return getCallbackAction(number, features, isDuoCall(phoneAccountComponentName)); } /** @@ -78,12 +75,9 @@ public class CallbackActionHelper { return CallbackAction.VOICE; } - private static boolean isDuoCall(String phoneAccountComponentName, Context context) { - Duo lightBringer = DuoComponent.get(context).getDuo(); - return lightBringer.getPhoneAccountComponentName() != null - && lightBringer - .getPhoneAccountComponentName() - .flattenToString() - .equals(phoneAccountComponentName); + private static boolean isDuoCall(String phoneAccountComponentName) { + return DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME + .flattenToString() + .equals(phoneAccountComponentName); } } diff --git a/java/com/android/dialer/duo/Duo.java b/java/com/android/dialer/duo/Duo.java index ec07ad4b4..839c1d3a8 100644 --- a/java/com/android/dialer/duo/Duo.java +++ b/java/com/android/dialer/duo/Duo.java @@ -16,7 +16,6 @@ package com.android.dialer.duo; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.support.annotation.MainThread; @@ -24,7 +23,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.telecom.Call; -import android.telecom.PhoneAccountHandle; import com.google.auto.value.AutoValue; import com.google.common.base.Optional; import java.util.List; @@ -55,18 +53,6 @@ public interface Duo { @MainThread void unregisterListener(@NonNull DuoListener listener); - @Nullable - @MainThread - ComponentName getPhoneAccountComponentName(); - - @Nullable - @MainThread - PhoneAccountHandle getPhoneAccountHandle(); - - @Nullable - @MainThread - String getPackageName(); - @StringRes @MainThread int getOutgoingCallTypeText(); diff --git a/java/com/android/dialer/duo/DuoConstants.java b/java/com/android/dialer/duo/DuoConstants.java new file mode 100644 index 000000000..50254ee13 --- /dev/null +++ b/java/com/android/dialer/duo/DuoConstants.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.duo; + +import android.content.ComponentName; +import android.telecom.PhoneAccountHandle; + +/** Constants to reference the Duo application. */ +public final class DuoConstants { + public static final String PACKAGE_NAME = "com.google.android.apps.tachyon"; + + public static final String CONNECTION_SERVICE = + "com.google.android.apps.tachyon.telecom.TachyonTelecomConnectionService"; + + public static final String PHONE_ACCOUNT_ID = "0"; + + public static final ComponentName PHONE_ACCOUNT_COMPONENT_NAME = + new ComponentName(PACKAGE_NAME, CONNECTION_SERVICE); + + public static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = + new PhoneAccountHandle(PHONE_ACCOUNT_COMPONENT_NAME, PHONE_ACCOUNT_ID); + + private DuoConstants() {} +} diff --git a/java/com/android/dialer/duo/stub/DuoStub.java b/java/com/android/dialer/duo/stub/DuoStub.java index 99f03adfd..82b9c79e3 100644 --- a/java/com/android/dialer/duo/stub/DuoStub.java +++ b/java/com/android/dialer/duo/stub/DuoStub.java @@ -16,7 +16,6 @@ package com.android.dialer.duo.stub; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.support.annotation.MainThread; @@ -24,7 +23,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.telecom.Call; -import android.telecom.PhoneAccountHandle; import com.android.dialer.common.Assert; import com.android.dialer.duo.Duo; import com.android.dialer.duo.DuoListener; @@ -95,24 +93,6 @@ public class DuoStub implements Duo { Assert.isNotNull(listener); } - @Nullable - @Override - public ComponentName getPhoneAccountComponentName() { - return null; - } - - @Nullable - @Override - public PhoneAccountHandle getPhoneAccountHandle() { - return null; - } - - @Nullable - @Override - public String getPackageName() { - return null; - } - @StringRes @Override public int getOutgoingCallTypeText() { diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 393b07a6b..6115c2f1c 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -345,7 +345,7 @@ public final class NewSearchFragment extends Fragment if (!PermissionsUtil.hasLocationPermissions(getContext()) && !DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(getContext()) .getBoolean(KEY_LOCATION_PROMPT_DISMISSED, false)) { - if (adapter != null && isRegularSearch()) { + if (adapter != null && isRegularSearch() && !hasBeenDismissed()) { adapter.showLocationPermissionRequest( v -> requestLocationPermission(), v -> dismissLocationPermission()); } @@ -372,7 +372,8 @@ public final class NewSearchFragment extends Fragment requestPermissions(deniedPermissions, LOCATION_PERMISSION_REQUEST_CODE); } - private void dismissLocationPermission() { + @VisibleForTesting + public void dismissLocationPermission() { PreferenceManager.getDefaultSharedPreferences(getContext()) .edit() .putBoolean(KEY_LOCATION_PROMPT_DISMISSED, true) @@ -380,6 +381,11 @@ public final class NewSearchFragment extends Fragment adapter.hideLocationPermissionRequest(); } + private boolean hasBeenDismissed() { + return PreferenceManager.getDefaultSharedPreferences(getContext()) + .getBoolean(KEY_LOCATION_PROMPT_DISMISSED, false); + } + @Override public void onResume() { super.onResume(); diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index 29e4e2320..949c2a2c5 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -187,7 +187,7 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> this.allowClickListener = Assert.isNotNull(allowClickListener); this.dismissClickListener = Assert.isNotNull(dismissClickListener); if (searchCursorManager.showLocationPermissionRequest(true)) { - notifyItemRemoved(0); + notifyItemInserted(0); } } diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java index f753e5f6b..4812fa5d6 100644 --- a/java/com/android/dialer/simulator/Simulator.java +++ b/java/com/android/dialer/simulator/Simulator.java @@ -30,6 +30,16 @@ public interface Simulator { ActionProvider getActionProvider(Context context); + /** The type of conference to emulate. */ + // TODO(b/67785540): add VoLTE and CDMA conference call + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + CONFERENCE_TYPE_GSM, + }) + @interface ConferenceType {} + + static final int CONFERENCE_TYPE_GSM = 1; + /** Information about a connection event. */ public static class Event { /** The type of connection event. */ @@ -44,6 +54,11 @@ public interface Simulator { STATE_CHANGE, DTMF, SESSION_MODIFY_REQUEST, + CALL_AUDIO_STATE_CHANGED, + CONNECTION_ADDED, + MERGE, + SEPARATE, + SWAP, }) public @interface Type {} @@ -56,6 +71,11 @@ public interface Simulator { public static final int STATE_CHANGE = 6; public static final int DTMF = 7; public static final int SESSION_MODIFY_REQUEST = 8; + public static final int CALL_AUDIO_STATE_CHANGED = 9; + public static final int CONNECTION_ADDED = 10; + public static final int MERGE = 11; + public static final int SEPARATE = 12; + public static final int SWAP = 13; @Type public final int type; /** Holds event specific information. For example, for DTMF this could be the keycode. */ diff --git a/java/com/android/dialer/simulator/impl/SimulatorConference.java b/java/com/android/dialer/simulator/impl/SimulatorConference.java new file mode 100644 index 000000000..7468b56b5 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorConference.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 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.simulator.impl; + +import android.support.annotation.NonNull; +import android.telecom.CallAudioState; +import android.telecom.Conference; +import android.telecom.Connection; +import android.telecom.PhoneAccountHandle; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.simulator.Simulator; +import com.android.dialer.simulator.Simulator.Event; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a conference call. When a user merges two phone calls we create an instance of this + * conference object and add it to the connection service. All operations such as hold and DTMF are + * then performed on this object. + */ +public final class SimulatorConference extends Conference implements SimulatorConnection.Listener { + static final int PROPERTY_GENERIC_CONFERENCE = 1 << 1; + + private final List<Listener> listeners = new ArrayList<>(); + private final List<Event> events = new ArrayList<>(); + private final int conferenceType; + + private SimulatorConference( + PhoneAccountHandle handle, @Simulator.ConferenceType int conferenceType) { + super(handle); + this.conferenceType = conferenceType; + setActive(); + } + + static SimulatorConference newGsmConference(PhoneAccountHandle handle) { + SimulatorConference simulatorConference = + new SimulatorConference(handle, Simulator.CONFERENCE_TYPE_GSM); + simulatorConference.setConnectionCapabilities( + Connection.CAPABILITY_MUTE + | Connection.CAPABILITY_SUPPORT_HOLD + | Connection.CAPABILITY_HOLD + | Connection.CAPABILITY_MANAGE_CONFERENCE); + return simulatorConference; + } + + public void addListener(@NonNull Listener listener) { + listeners.add(Assert.isNotNull(listener)); + } + + public void removeListener(@NonNull Listener listener) { + listeners.remove(Assert.isNotNull(listener)); + } + + @NonNull + public List<Event> getEvents() { + return events; + } + + @Override + public void onCallAudioStateChanged(CallAudioState state) { + LogUtil.enterBlock("SimulatorConference.onCallAudioStateChanged"); + onEvent(new Event(Event.CALL_AUDIO_STATE_CHANGED)); + } + + @Override + public void onConnectionAdded(Connection connection) { + LogUtil.enterBlock("SimulatorConference.onConnectionAdded"); + onEvent( + new Event( + Event.CONNECTION_ADDED, SimulatorSimCallManager.getConnectionTag(connection), null)); + ((SimulatorConnection) connection).addListener(this); + } + + @Override + public void onDisconnect() { + LogUtil.enterBlock("SimulatorConference.onDisconnect"); + onEvent(new Event(Event.DISCONNECT)); + } + + @Override + public void onHold() { + LogUtil.enterBlock("SimulatorConference.onHold"); + onEvent(new Event(Event.HOLD)); + } + + @Override + public void onMerge(Connection connection) { + LogUtil.i("SimulatorConference.onMerge", "connection: " + connection); + onEvent(new Event(Event.MERGE, SimulatorSimCallManager.getConnectionTag(connection), null)); + } + + @Override + public void onMerge() { + LogUtil.enterBlock("SimulatorConference.onMerge"); + onEvent(new Event(Event.MERGE)); + } + + @Override + public void onPlayDtmfTone(char c) { + LogUtil.enterBlock("SimulatorConference.onPlayDtmfTone"); + onEvent(new Event(Event.DTMF, Character.toString(c), null)); + } + + @Override + public void onSeparate(Connection connection) { + LogUtil.i("SimulatorConference.onSeparate", "connection: " + connection); + onEvent(new Event(Event.SEPARATE, SimulatorSimCallManager.getConnectionTag(connection), null)); + } + + @Override + public void onSwap() { + LogUtil.enterBlock("SimulatorConference.onSwap"); + onEvent(new Event(Event.SWAP)); + } + + @Override + public void onUnhold() { + LogUtil.enterBlock("SimulatorConference.onUnhold"); + onEvent(new Event(Event.UNHOLD)); + } + + @Override + public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { + if (conferenceType == Simulator.CONFERENCE_TYPE_GSM) { + onGsmEvent(connection, event); + } + } + + private void onGsmEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { + if (event.type == Event.STATE_CHANGE + && Connection.stateToString(Connection.STATE_DISCONNECTED).equals(event.data2)) { + removeConnection(connection); + connection.removeListener(this); + if (getConnections().size() <= 1) { + // When only one connection exists, it's not conference call anymore + setDisconnected(connection.getDisconnectCause()); + destroy(); + } + } + } + + void onEvent(@NonNull Event event) { + events.add(Assert.isNotNull(event)); + for (Listener listener : new ArrayList<>(listeners)) { + listener.onEvent(this, event); + } + } + + /** Callback for when a new event arrives. */ + public interface Listener { + void onEvent(@NonNull SimulatorConference conference, @NonNull Event event); + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java new file mode 100644 index 000000000..838b58dc2 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2017 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.simulator.impl; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.telecom.Conferenceable; +import android.telecom.Connection; +import android.telecom.DisconnectCause; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.simulator.Simulator; +import com.android.dialer.simulator.Simulator.Event; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** Creates a conference with a given number of participants. */ +final class SimulatorConferenceCreator + implements SimulatorConnectionService.Listener, + SimulatorConnection.Listener, + SimulatorConference.Listener { + private static final String EXTRA_CALL_COUNT = "call_count"; + + @NonNull private final Context context; + @NonNull private final List<String> connectionTags = new ArrayList<>(); + @Simulator.ConferenceType private final int conferenceType; + + public SimulatorConferenceCreator( + @NonNull Context context, @Simulator.ConferenceType int conferenceType) { + this.context = Assert.isNotNull(context); + this.conferenceType = conferenceType; + } + + void start(int callCount) { + SimulatorConnectionService.addListener(this); + addNextCall(callCount); + } + + private void addNextCall(int callCount) { + LogUtil.i("SimulatorConferenceCreator.addNextIncomingCall", "callCount: " + callCount); + if (callCount <= 0) { + LogUtil.i("SimulatorConferenceCreator.addNextCall", "done adding calls"); + return; + } + + String callerId = String.format(Locale.US, "+1-650-234%04d", callCount); + Bundle extras = new Bundle(); + extras.putInt(EXTRA_CALL_COUNT, callCount - 1); + connectionTags.add( + SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras)); + } + + @Override + public void onNewIncomingConnection(@NonNull SimulatorConnection connection) { + if (!isMyConnection(connection)) { + LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "unknown connection"); + return; + } + + LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "connection created"); + connection.addListener(this); + + // Telecom will force the connection to switch to DIALING when we return it. Wait until after + // we're returned it before changing call state. + ThreadUtil.postOnUiThread(() -> connection.setActive()); + + // Once the connection is active, go ahead and conference it and add the next call. + ThreadUtil.postDelayedOnUiThread( + () -> { + SimulatorConference conference = findCurrentConference(); + if (conference == null) { + Assert.checkArgument(conferenceType == Simulator.CONFERENCE_TYPE_GSM); + conference = + SimulatorConference.newGsmConference( + SimulatorSimCallManager.getSystemPhoneAccountHandle(context)); + conference.addListener(this); + SimulatorConnectionService.getInstance().addConference(conference); + } + updateConferenceableConnections(); + conference.addConnection(connection); + addNextCall(getCallCount(connection)); + }, + 1000); + } + + @Override + public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {} + + /** + * This is called when the user clicks the merge button. We create the initial conference + * automatically but with this method we can let the user split and merge calls as desired. + */ + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) { + LogUtil.enterBlock("SimulatorConferenceCreator.onConference"); + if (!isMyConnection(connection1) || !isMyConnection(connection2)) { + LogUtil.i("SimulatorConferenceCreator.onConference", "unknown connections, ignoring"); + return; + } + + if (connection1.getConference() != null) { + connection1.getConference().addConnection(connection2); + } else if (connection2.getConference() != null) { + connection2.getConference().addConnection(connection1); + } else { + Assert.checkArgument(conferenceType == Simulator.CONFERENCE_TYPE_GSM); + SimulatorConference conference = + SimulatorConference.newGsmConference( + SimulatorSimCallManager.getSystemPhoneAccountHandle(context)); + conference.addConnection(connection1); + conference.addConnection(connection2); + conference.addListener(this); + SimulatorConnectionService.getInstance().addConference(conference); + } + } + + private boolean isMyConnection(@NonNull Connection connection) { + for (String connectionTag : connectionTags) { + if (connection.getExtras().getBoolean(connectionTag)) { + return true; + } + } + return false; + } + + private void updateConferenceableConnections() { + LogUtil.enterBlock("SimulatorConferenceCreator.updateConferenceableConnections"); + for (String connectionTag : connectionTags) { + SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag); + List<Conferenceable> conferenceables = getMyConferenceables(); + conferenceables.remove(connection); + conferenceables.remove(connection.getConference()); + connection.setConferenceables(conferenceables); + } + } + + private List<Conferenceable> getMyConferenceables() { + List<Conferenceable> conferenceables = new ArrayList<>(); + for (String connectionTag : connectionTags) { + SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag); + conferenceables.add(connection); + if (connection.getConference() != null + && !conferenceables.contains(connection.getConference())) { + conferenceables.add(connection.getConference()); + } + } + return conferenceables; + } + + @Nullable + private SimulatorConference findCurrentConference() { + for (String connectionTag : connectionTags) { + SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag); + if (connection.getConference() != null) { + return (SimulatorConference) connection.getConference(); + } + } + return null; + } + + private static int getCallCount(@NonNull Connection connection) { + return connection.getExtras().getInt(EXTRA_CALL_COUNT); + } + + @Override + public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { + switch (event.type) { + case Event.NONE: + throw Assert.createIllegalStateFailException(); + case Event.HOLD: + connection.setOnHold(); + break; + case Event.UNHOLD: + connection.setActive(); + break; + case Event.DISCONNECT: + connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); + break; + default: + LogUtil.i( + "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type); + break; + } + } + + @Override + public void onEvent(@NonNull SimulatorConference conference, @NonNull Event event) { + switch (event.type) { + case Event.MERGE: + int capabilities = conference.getConnectionCapabilities(); + capabilities |= Connection.CAPABILITY_SWAP_CONFERENCE; + conference.setConnectionCapabilities(capabilities); + break; + case Event.SEPARATE: + SimulatorConnection connectionToRemove = + SimulatorSimCallManager.findConnectionByTag(event.data1); + conference.removeConnection(connectionToRemove); + break; + case Event.DISCONNECT: + for (Connection connection : new ArrayList<>(conference.getConnections())) { + connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); + } + break; + default: + LogUtil.i( + "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type); + break; + } + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java index 70c1095dc..e4a34b51b 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java @@ -41,7 +41,9 @@ public final class SimulatorConnection extends Connection { CAPABILITY_MUTE | CAPABILITY_SUPPORT_HOLD | CAPABILITY_HOLD - | CAPABILITY_CAN_UPGRADE_TO_VIDEO); + | CAPABILITY_CAN_UPGRADE_TO_VIDEO + | CAPABILITY_DISCONNECT_FROM_CONFERENCE + | CAPABILITY_SEPARATE_FROM_CONFERENCE); setVideoProvider(new SimulatorVideoProvider(context, this)); } @@ -108,7 +110,7 @@ public final class SimulatorConnection extends Connection { void onEvent(@NonNull Event event) { events.add(Assert.isNotNull(event)); - for (Listener listener : listeners) { + for (Listener listener : new ArrayList<>(listeners)) { listener.onEvent(this, event); } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java index 25d4a7240..465890cf0 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java @@ -109,6 +109,19 @@ public class SimulatorConnectionService extends ConnectionService { return connection; } + @Override + public void onConference(Connection connection1, Connection connection2) { + LogUtil.i( + "SimulatorConnectionService.onConference", + "connection1: " + + SimulatorSimCallManager.getConnectionTag(connection1) + + ", connection2: " + + SimulatorSimCallManager.getConnectionTag(connection2)); + for (Listener listener : listeners) { + listener.onConference((SimulatorConnection) connection1, (SimulatorConnection) connection2); + } + } + private static Uri getPhoneNumber(ConnectionRequest request) { String phoneNumber = request.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER); return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null); @@ -116,8 +129,11 @@ public class SimulatorConnectionService extends ConnectionService { /** Callback used to notify listeners when a new connection has been added. */ public interface Listener { - void onNewOutgoingConnection(SimulatorConnection connection); + void onNewOutgoingConnection(@NonNull SimulatorConnection connection); + + void onNewIncomingConnection(@NonNull SimulatorConnection connection); - void onNewIncomingConnection(SimulatorConnection connection); + void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2); } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java index f85f46602..6d4a26278 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java +++ b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java @@ -62,6 +62,10 @@ final class SimulatorMissedCallCreator implements SimulatorConnectionService.Lis DISCONNECT_DELAY_MILLIS); } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private void addNextIncomingCall(int callCount) { if (callCount <= 0) { LogUtil.i("SimulatorMissedCallCreator.addNextIncomingCall", "done adding calls"); diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java index 33eac51d1..00899fd69 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java @@ -21,6 +21,7 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; +import android.telecom.Connection; import android.telecom.ConnectionRequest; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; @@ -47,6 +48,7 @@ public class SimulatorSimCallManager { private static final String SIM_CALL_MANAGER_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID"; private static final String VIDEO_PROVIDER_ACCOUNT_ID = "SIMULATOR_VIDEO_ACCOUNT_ID"; private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection"; + private static final String EXTRA_CONNECTION_TAG = "connection_tag"; static void register(@NonNull Context context) { LogUtil.enterBlock("SimulatorSimCallManager.register"); @@ -85,9 +87,7 @@ public class SimulatorSimCallManager { register(context); extras = new Bundle(extras); - extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); - String connectionTag = createUniqueConnectionTag(); - extras.putBoolean(connectionTag, true); + extras.putAll(createSimulatorConnectionExtras()); Bundle outgoingCallExtras = new Bundle(); outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); @@ -102,7 +102,7 @@ public class SimulatorSimCallManager { } catch (SecurityException e) { throw Assert.createIllegalStateFailException("Unable to place call: " + e); } - return connectionTag; + return extras.getString(EXTRA_CONNECTION_TAG); } @NonNull @@ -123,14 +123,12 @@ public class SimulatorSimCallManager { extras = new Bundle(extras); extras.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId); - extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); - String connectionTag = createUniqueConnectionTag(); - extras.putBoolean(connectionTag, true); + extras.putAll(createSimulatorConnectionExtras()); TelecomManager telecomManager = context.getSystemService(TelecomManager.class); telecomManager.addNewIncomingCall( isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context), extras); - return connectionTag; + return extras.getString(EXTRA_CONNECTION_TAG); } @NonNull @@ -167,7 +165,7 @@ public class SimulatorSimCallManager { } @NonNull - private static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) { + public static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) { TelecomManager telecomManager = context.getSystemService(TelecomManager.class); List<PhoneAccountHandle> handles; try { @@ -190,10 +188,37 @@ public class SimulatorSimCallManager { } @NonNull + public static String getConnectionTag(@NonNull Connection connection) { + String connectionTag = connection.getExtras().getString(EXTRA_CONNECTION_TAG); + return Assert.isNotNull(connectionTag); + } + + @NonNull + public static SimulatorConnection findConnectionByTag(@NonNull String connectionTag) { + Assert.isNotNull(connectionTag); + for (Connection connection : SimulatorConnectionService.getInstance().getAllConnections()) { + if (connection.getExtras().getBoolean(connectionTag)) { + return (SimulatorConnection) connection; + } + } + throw Assert.createIllegalStateFailException(); + } + + @NonNull private static String createUniqueConnectionTag() { int callId = new Random().nextInt(); return String.format("simulator_phone_call_%x", Math.abs(callId)); } + @NonNull + static Bundle createSimulatorConnectionExtras() { + Bundle extras = new Bundle(); + extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); + String connectionTag = createUniqueConnectionTag(); + extras.putString(EXTRA_CONNECTION_TAG, connectionTag); + extras.putBoolean(connectionTag, true); + return extras; + } + private SimulatorSimCallManager() {} } diff --git a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java index 757658ddf..a843ec03f 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java @@ -74,6 +74,10 @@ final class SimulatorSpamCallCreator implements SimulatorConnectionService.Liste DISCONNECT_DELAY_MILLIS); } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private void addNextIncomingCall(int callCount) { if (callCount <= 0) { LogUtil.i("SimulatorSpamCallCreator.addNextIncomingCall", "done adding calls"); diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java index 3f00ab183..f7256a11c 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java @@ -109,6 +109,10 @@ final class SimulatorVideoCall } } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private boolean isVideoAccountEnabled() { SimulatorSimCallManager.register(context); return context @@ -150,15 +154,12 @@ final class SimulatorVideoCall case Event.DISCONNECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); break; - case Event.STATE_CHANGE: - break; - case Event.DTMF: - break; case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; default: - throw Assert.createIllegalStateFailException(); + LogUtil.i("SimulatorVideoCall.onEvent", "unexpected event: " + event.type); + break; } } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java index 8eefb48d9..f478d55d0 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -25,6 +25,7 @@ import android.view.ActionProvider; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.simulator.Simulator; import com.android.dialer.simulator.Simulator.Event; /** Entry point in the simulator to create voice calls. */ @@ -37,7 +38,10 @@ final class SimulatorVoiceCall return new SimulatorSubMenu(context) .addItem("Incoming call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(false)) .addItem("Outgoing call", () -> new SimulatorVoiceCall(context).addNewOutgoingCall()) - .addItem("Spam call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(true)); + .addItem("Spam call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(true)) + .addItem( + "GSM conference", + () -> new SimulatorConferenceCreator(context, Simulator.CONFERENCE_TYPE_GSM).start(5)); } private SimulatorVoiceCall(@NonNull Context context) { @@ -62,21 +66,28 @@ final class SimulatorVoiceCall @Override public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) { - if (connection.getExtras().getBoolean(connectionTag)) { + if (isMyConnection(connection)) { LogUtil.i("SimulatorVoiceCall.onNewOutgoingConnection", "connection created"); handleNewConnection(connection); - connection.setActive(); + + // Telecom will force the connection to switch to Dialing when we return it. Wait until after + // we're returned it before changing call state. + ThreadUtil.postOnUiThread(connection::setActive); } } @Override public void onNewIncomingConnection(@NonNull SimulatorConnection connection) { - if (connection.getExtras().getBoolean(connectionTag)) { + if (isMyConnection(connection)) { LogUtil.i("SimulatorVoiceCall.onNewIncomingConnection", "connection created"); handleNewConnection(connection); } } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private void handleNewConnection(@NonNull SimulatorConnection connection) { connection.addListener(this); connection.setConnectionCapabilities( @@ -85,6 +96,10 @@ final class SimulatorVoiceCall | Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL); } + private boolean isMyConnection(@NonNull Connection connection) { + return connection.getExtras().getBoolean(connectionTag); + } + @Override public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { switch (event.type) { @@ -105,15 +120,12 @@ final class SimulatorVoiceCall case Event.DISCONNECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); break; - case Event.STATE_CHANGE: - break; - case Event.DTMF: - break; case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; default: - throw Assert.createIllegalStateFailException(); + LogUtil.i("SimulatorVoiceCall.onEvent", "unexpected event: " + event.type); + break; } } } |