From d6cb3b9ac4db86cc5d37174a6e6c4d69c8a8a753 Mon Sep 17 00:00:00 2001 From: wangqi Date: Fri, 9 Mar 2018 16:33:43 -0800 Subject: Dismiss keyboard when opening overflow menu in RTT call. This is to prevent dialpad showing with keyboard open. The alternative way to dismiss keyboard after dialpad shows up doesn't work. Bug: 67596257 Test: manual PiperOrigin-RevId: 188556453 Change-Id: I4b917d8ac83246d2002641ae7759261699149c65 --- .../android/incallui/rtt/impl/RttChatFragment.java | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'java/com/android/incallui') diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index 9e8a24a48..e35ff4d73 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -37,7 +37,6 @@ import android.view.ViewGroup; import android.view.Window; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.Chronometer; import android.widget.EditText; import android.widget.ImageButton; @@ -46,6 +45,7 @@ import android.widget.TextView.OnEditorActionListener; import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.UiUtil; import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment; import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter; import com.android.incallui.call.DialerCall.State; @@ -87,7 +87,7 @@ public class RttChatFragment extends Fragment @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (dy < 0) { - hideKeyboard(); + UiUtil.hideKeyboardFrom(getContext(), editText); } } }; @@ -182,7 +182,13 @@ public class RttChatFragment extends Fragment overflowMenu = new RttOverflowMenu(getContext(), inCallButtonUiDelegate); view.findViewById(R.id.rtt_overflow_button) - .setOnClickListener(v -> overflowMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, 0)); + .setOnClickListener( + v -> { + // Hide keyboard when opening overflow menu. This is alternative solution since hiding + // keyboard after the menu is open or dialpad is shown doesn't work. + UiUtil.hideKeyboardFrom(getContext(), editText); + overflowMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, 0); + }); nameTextView = view.findViewById(R.id.rtt_name_or_number); chronometer = view.findViewById(R.id.rtt_timer); @@ -265,14 +271,6 @@ public class RttChatFragment extends Fragment onRttScreenStop(); } - private void hideKeyboard() { - InputMethodManager inputMethodManager = getContext().getSystemService(InputMethodManager.class); - if (inputMethodManager.isAcceptingText()) { - inputMethodManager.hideSoftInputFromWindow( - getActivity().getCurrentFocus().getWindowToken(), 0); - } - } - @Override public void onRttScreenStart() { rttCallScreenDelegate.onRttCallScreenUiReady(); -- cgit v1.2.3 From 48f93f40f66f5695cdb370af679db4580cebb641 Mon Sep 17 00:00:00 2001 From: yueg Date: Fri, 9 Mar 2018 16:49:38 -0800 Subject: Remove bubble "new" prefix. Test: BubbleImplTest, BubbleImplIntegrationTest, ReturnToCallControllerTest, ReturnToCallActionReceiverTest, BottomActionViewControllerTest, ChangeOnScreenBoundsTest PiperOrigin-RevId: 188558386 Change-Id: Ieab3ee3f148092312a50597b41c0975419756e23 --- java/com/android/bubble/Bubble.java | 77 +++++ java/com/android/bubble/BubbleComponent.java | 39 +++ java/com/android/bubble/BubbleInfo.java | 129 +++++++ java/com/android/bubble/stub/BubbleStub.java | 56 +++ java/com/android/bubble/stub/StubBubbleModule.java | 30 ++ .../binary/aosp/AospDialerRootComponent.java | 4 +- .../basecomponent/BaseDialerRootComponent.java | 4 +- .../google/GoogleStubDialerRootComponent.java | 4 +- java/com/android/incallui/AndroidManifest.xml | 2 +- java/com/android/incallui/InCallServiceImpl.java | 14 +- .../incallui/NewReturnToCallActionReceiver.java | 155 --------- .../incallui/NewReturnToCallController.java | 385 --------------------- .../incallui/ReturnToCallActionReceiver.java | 155 +++++++++ .../android/incallui/ReturnToCallController.java | 382 ++++++++++++++++++++ java/com/android/incallui/StatusBarNotifier.java | 4 +- java/com/android/newbubble/NewBubble.java | 78 ----- java/com/android/newbubble/NewBubbleComponent.java | 39 --- java/com/android/newbubble/NewBubbleInfo.java | 129 ------- java/com/android/newbubble/stub/NewBubbleStub.java | 57 --- .../newbubble/stub/StubNewBubbleModule.java | 30 -- 20 files changed, 884 insertions(+), 889 deletions(-) create mode 100644 java/com/android/bubble/Bubble.java create mode 100644 java/com/android/bubble/BubbleComponent.java create mode 100644 java/com/android/bubble/BubbleInfo.java create mode 100644 java/com/android/bubble/stub/BubbleStub.java create mode 100644 java/com/android/bubble/stub/StubBubbleModule.java delete mode 100644 java/com/android/incallui/NewReturnToCallActionReceiver.java delete mode 100644 java/com/android/incallui/NewReturnToCallController.java create mode 100644 java/com/android/incallui/ReturnToCallActionReceiver.java create mode 100644 java/com/android/incallui/ReturnToCallController.java delete mode 100644 java/com/android/newbubble/NewBubble.java delete mode 100644 java/com/android/newbubble/NewBubbleComponent.java delete mode 100644 java/com/android/newbubble/NewBubbleInfo.java delete mode 100644 java/com/android/newbubble/stub/NewBubbleStub.java delete mode 100644 java/com/android/newbubble/stub/StubNewBubbleModule.java (limited to 'java/com/android/incallui') diff --git a/java/com/android/bubble/Bubble.java b/java/com/android/bubble/Bubble.java new file mode 100644 index 000000000..e192e06f4 --- /dev/null +++ b/java/com/android/bubble/Bubble.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.bubble; + +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import java.util.List; + +/** + * Creates and manages a bubble window from information in a {@link BubbleInfo}. Before creating, be + * sure to check whether bubbles may be shown using {@code Settings.canDrawOverlays(context)} and + * request permission if necessary + */ +public interface Bubble { + + /** + * Make the bubble visible. Will show a short entrance animation as it enters. If the bubble is + * already showing this method does nothing. + */ + void show(); + + /** Hide the bubble. */ + void hide(); + + /** Returns whether the bubble is currently visible */ + boolean isVisible(); + + /** + * Set the info for this Bubble to display + * + * @param bubbleInfo the BubbleInfo to display in this Bubble. + */ + void setBubbleInfo(@NonNull BubbleInfo bubbleInfo); + + /** + * Update the state and behavior of actions. + * + * @param actions the new state of the bubble's actions + */ + void updateActions(@NonNull List actions); + + /** + * Update the avatar from photo. + * + * @param avatar the new photo avatar in the bubble's primary button + */ + void updatePhotoAvatar(@NonNull Drawable avatar); + + /** + * Update the avatar. + * + * @param avatar the new avatar in the bubble's primary button + */ + void updateAvatar(@NonNull Drawable avatar); + + /** + * Display text. The bubble's drawer is not expandable while text is showing, and the drawer will + * be closed if already open. + * + * @param text the text to display to the user + */ + void showText(@NonNull CharSequence text); +} diff --git a/java/com/android/bubble/BubbleComponent.java b/java/com/android/bubble/BubbleComponent.java new file mode 100644 index 000000000..7a4665e74 --- /dev/null +++ b/java/com/android/bubble/BubbleComponent.java @@ -0,0 +1,39 @@ +/* + * 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.bubble; + +import android.content.Context; +import android.support.annotation.NonNull; +import com.android.dialer.inject.HasRootComponent; +import dagger.Subcomponent; + +@Subcomponent +public abstract class BubbleComponent { + + @NonNull + public abstract Bubble getBubble(); + + public static BubbleComponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .bubbleComponent(); + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + BubbleComponent bubbleComponent(); + } +} diff --git a/java/com/android/bubble/BubbleInfo.java b/java/com/android/bubble/BubbleInfo.java new file mode 100644 index 000000000..28793a78c --- /dev/null +++ b/java/com/android/bubble/BubbleInfo.java @@ -0,0 +1,129 @@ +/* + * 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.bubble; + +import android.app.PendingIntent; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.Px; +import com.google.auto.value.AutoValue; +import java.util.Collections; +import java.util.List; + +/** Info for displaying a {@link Bubble} */ +@AutoValue +public abstract class BubbleInfo { + @ColorInt + public abstract int getPrimaryColor(); + + public abstract Icon getPrimaryIcon(); + + @Nullable + public abstract Drawable getAvatar(); + + @Px + public abstract int getStartingYPosition(); + + @NonNull + public abstract List getActions(); + + public static Builder builder() { + return new AutoValue_BubbleInfo.Builder().setActions(Collections.emptyList()); + } + + public static Builder from(@NonNull BubbleInfo bubbleInfo) { + return builder() + .setPrimaryColor(bubbleInfo.getPrimaryColor()) + .setPrimaryIcon(bubbleInfo.getPrimaryIcon()) + .setStartingYPosition(bubbleInfo.getStartingYPosition()) + .setActions(bubbleInfo.getActions()) + .setAvatar(bubbleInfo.getAvatar()); + } + + /** Builder for {@link BubbleInfo} */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setPrimaryColor(@ColorInt int primaryColor); + + public abstract Builder setPrimaryIcon(@NonNull Icon primaryIcon); + + public abstract Builder setAvatar(@Nullable Drawable avatar); + + public abstract Builder setStartingYPosition(@Px int startingYPosition); + + public abstract Builder setActions(List actions); + + public abstract BubbleInfo build(); + } + + /** Represents actions to be shown in the bubble when expanded */ + @AutoValue + public abstract static class Action { + + public abstract Drawable getIconDrawable(); + + @Nullable + public abstract Drawable getSecondaryIconDrawable(); + + @NonNull + public abstract CharSequence getName(); + + @NonNull + public abstract PendingIntent getIntent(); + + public abstract boolean isCheckable(); + + public abstract boolean isChecked(); + + public static Builder builder() { + return new AutoValue_BubbleInfo_Action.Builder().setCheckable(true).setChecked(false); + } + + public static Builder from(@NonNull Action action) { + return builder() + .setIntent(action.getIntent()) + .setChecked(action.isChecked()) + .setCheckable(action.isCheckable()) + .setName(action.getName()) + .setIconDrawable(action.getIconDrawable()) + .setSecondaryIconDrawable(action.getSecondaryIconDrawable()); + } + + /** Builder for {@link Action} */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setIconDrawable(Drawable iconDrawable); + + public abstract Builder setSecondaryIconDrawable(@Nullable Drawable secondaryIconDrawable); + + public abstract Builder setName(@NonNull CharSequence name); + + public abstract Builder setIntent(@NonNull PendingIntent intent); + + public abstract Builder setCheckable(boolean enabled); + + public abstract Builder setChecked(boolean checked); + + public abstract Action build(); + } + } +} diff --git a/java/com/android/bubble/stub/BubbleStub.java b/java/com/android/bubble/stub/BubbleStub.java new file mode 100644 index 000000000..267f33f31 --- /dev/null +++ b/java/com/android/bubble/stub/BubbleStub.java @@ -0,0 +1,56 @@ +/* + * 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.bubble.stub; + +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import com.android.bubble.Bubble; +import com.android.bubble.BubbleInfo; +import java.util.List; +import javax.inject.Inject; + +public class BubbleStub implements Bubble { + + @Inject + public BubbleStub() {} + + @Override + public void show() {} + + @Override + public void hide() {} + + @Override + public boolean isVisible() { + return false; + } + + @Override + public void setBubbleInfo(@NonNull BubbleInfo bubbleInfo) {} + + @Override + public void updateActions(@NonNull List actions) {} + + @Override + public void updatePhotoAvatar(@NonNull Drawable avatar) {} + + @Override + public void updateAvatar(@NonNull Drawable avatar) {} + + @Override + public void showText(@NonNull CharSequence text) {} +} diff --git a/java/com/android/bubble/stub/StubBubbleModule.java b/java/com/android/bubble/stub/StubBubbleModule.java new file mode 100644 index 000000000..783983f2e --- /dev/null +++ b/java/com/android/bubble/stub/StubBubbleModule.java @@ -0,0 +1,30 @@ +/* + * 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.bubble.stub; + +import com.android.bubble.Bubble; +import dagger.Binds; +import dagger.Module; +import javax.inject.Singleton; + +@Module +public abstract class StubBubbleModule { + + @Binds + @Singleton + public abstract Bubble bindsBubble(BubbleStub bubbleStub); +} diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java index 35f854010..969172b7f 100644 --- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java +++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java @@ -16,6 +16,7 @@ package com.android.dialer.binary.aosp; +import com.android.bubble.stub.StubBubbleModule; import com.android.dialer.binary.basecomponent.BaseDialerRootComponent; import com.android.dialer.calllog.CallLogModule; import com.android.dialer.commandline.CommandLineModule; @@ -39,7 +40,6 @@ import com.android.dialer.strictmode.impl.SystemStrictModeModule; import com.android.incallui.calllocation.stub.StubCallLocationModule; import com.android.incallui.maps.stub.StubMapsModule; import com.android.incallui.speakeasy.StubSpeakEasyModule; -import com.android.newbubble.stub.StubNewBubbleModule; import com.android.voicemail.impl.VoicemailModule; import dagger.Component; import javax.inject.Singleton; @@ -63,7 +63,7 @@ import javax.inject.Singleton; StubCallLocationModule.class, StubDuoModule.class, StubEnrichedCallModule.class, - StubNewBubbleModule.class, + StubBubbleModule.class, StubMetricsModule.class, StubFeedbackModule.class, StubMapsModule.class, diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java index cd95c3ee7..0495ba6ef 100644 --- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java +++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java @@ -16,6 +16,7 @@ package com.android.dialer.binary.basecomponent; +import com.android.bubble.BubbleComponent; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.database.CallLogDatabaseComponent; import com.android.dialer.calllog.ui.CallLogUiComponent; @@ -39,7 +40,6 @@ import com.android.dialer.strictmode.StrictModeComponent; import com.android.incallui.calllocation.CallLocationComponent; import com.android.incallui.maps.MapsComponent; import com.android.incallui.speakeasy.SpeakEasyComponent; -import com.android.newbubble.NewBubbleComponent; import com.android.voicemail.VoicemailComponent; /** @@ -61,7 +61,7 @@ public interface BaseDialerRootComponent MainComponent.HasComponent, MapsComponent.HasComponent, MetricsComponent.HasComponent, - NewBubbleComponent.HasComponent, + BubbleComponent.HasComponent, PhoneLookupComponent.HasComponent, PhoneNumberGeoUtilComponent.HasComponent, PreCallComponent.HasComponent, diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java index 497d97724..f9f561a0e 100644 --- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java +++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java @@ -16,6 +16,7 @@ package com.android.dialer.binary.google; +import com.android.bubble.stub.StubBubbleModule; import com.android.dialer.binary.basecomponent.BaseDialerRootComponent; import com.android.dialer.calllog.CallLogModule; import com.android.dialer.commandline.CommandLineModule; @@ -39,7 +40,6 @@ import com.android.dialer.strictmode.impl.SystemStrictModeModule; import com.android.incallui.calllocation.impl.CallLocationModule; import com.android.incallui.maps.impl.MapsModule; import com.android.incallui.speakeasy.StubSpeakEasyModule; -import com.android.newbubble.stub.StubNewBubbleModule; import com.android.voicemail.impl.VoicemailModule; import dagger.Component; import javax.inject.Singleton; @@ -69,7 +69,7 @@ import javax.inject.Singleton; StubEnrichedCallModule.class, StubFeedbackModule.class, StubMetricsModule.class, - StubNewBubbleModule.class, + StubBubbleModule.class, StubSimSuggestionModule.class, StubSpamModule.class, StubSpeakEasyModule.class, diff --git a/java/com/android/incallui/AndroidManifest.xml b/java/com/android/incallui/AndroidManifest.xml index a45330b73..1b5f09973 100644 --- a/java/com/android/incallui/AndroidManifest.xml +++ b/java/com/android/incallui/AndroidManifest.xml @@ -115,7 +115,7 @@ + android:name=".ReturnToCallActionReceiver"/> diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java index a7095f818..959f13f2e 100644 --- a/java/com/android/incallui/InCallServiceImpl.java +++ b/java/com/android/incallui/InCallServiceImpl.java @@ -40,7 +40,7 @@ import com.android.incallui.speakeasy.SpeakEasyComponent; */ public class InCallServiceImpl extends InCallService { - private NewReturnToCallController newReturnToCallController; + private ReturnToCallController returnToCallController; private CallList.Listener feedbackListener; // We only expect there to be one speakEasyCallManager to be instantiated at a time. // We did not use a singleton SpeakEasyCallManager to avoid holding on to state beyond the @@ -111,9 +111,9 @@ public class InCallServiceImpl extends InCallService { InCallPresenter.getInstance().onServiceBind(); InCallPresenter.getInstance().maybeStartRevealAnimation(intent); TelecomAdapter.getInstance().setInCallService(this); - if (NewReturnToCallController.isEnabled(this)) { - newReturnToCallController = - new NewReturnToCallController(this, ContactInfoCache.getInstance(context)); + if (ReturnToCallController.isEnabled(this)) { + returnToCallController = + new ReturnToCallController(this, ContactInfoCache.getInstance(context)); } feedbackListener = FeedbackComponent.get(context).getCallFeedbackListener(); CallList.getInstance().addListener(feedbackListener); @@ -141,9 +141,9 @@ public class InCallServiceImpl extends InCallService { // Tear down the InCall system InCallPresenter.getInstance().tearDown(); TelecomAdapter.getInstance().clearInCallService(); - if (newReturnToCallController != null) { - newReturnToCallController.tearDown(); - newReturnToCallController = null; + if (returnToCallController != null) { + returnToCallController.tearDown(); + returnToCallController = null; } if (feedbackListener != null) { CallList.getInstance().removeListener(feedbackListener); diff --git a/java/com/android/incallui/NewReturnToCallActionReceiver.java b/java/com/android/incallui/NewReturnToCallActionReceiver.java deleted file mode 100644 index 527a79b51..000000000 --- a/java/com/android/incallui/NewReturnToCallActionReceiver.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.incallui; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.telecom.CallAudioState; -import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import com.android.dialer.logging.DialerImpression; -import com.android.dialer.logging.Logger; -import com.android.incallui.audiomode.AudioModeProvider; -import com.android.incallui.call.CallList; -import com.android.incallui.call.DialerCall; -import com.android.incallui.call.TelecomAdapter; - -/** Handles clicks on the return-to-call bubble */ -public class NewReturnToCallActionReceiver extends BroadcastReceiver { - - public static final String ACTION_RETURN_TO_CALL = "returnToCallV2"; - public static final String ACTION_TOGGLE_SPEAKER = "toggleSpeakerV2"; - public static final String ACTION_SHOW_AUDIO_ROUTE_SELECTOR = "showAudioRouteSelectorV2"; - public static final String ACTION_TOGGLE_MUTE = "toggleMuteV2"; - public static final String ACTION_END_CALL = "endCallV2"; - - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case ACTION_RETURN_TO_CALL: - returnToCall(context); - break; - case ACTION_TOGGLE_SPEAKER: - toggleSpeaker(context); - break; - case ACTION_SHOW_AUDIO_ROUTE_SELECTOR: - showAudioRouteSelector(context); - break; - case ACTION_TOGGLE_MUTE: - toggleMute(context); - break; - case ACTION_END_CALL: - endCall(context); - break; - default: - throw Assert.createIllegalStateFailException( - "Invalid intent action: " + intent.getAction()); - } - } - - private void returnToCall(Context context) { - DialerCall call = getCall(); - Logger.get(context) - .logCallImpression( - DialerImpression.Type.BUBBLE_V2_RETURN_TO_CALL, - call != null ? call.getUniqueCallId() : "", - call != null ? call.getTimeAddedMs() : 0); - - Intent activityIntent = InCallActivity.getIntent(context, false, false, false); - activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(activityIntent); - } - - private void toggleSpeaker(Context context) { - CallAudioState audioState = AudioModeProvider.getInstance().getAudioState(); - - if ((audioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) - == CallAudioState.ROUTE_BLUETOOTH) { - LogUtil.w( - "ReturnToCallActionReceiver.toggleSpeaker", - "toggleSpeaker() called when bluetooth available." - + " Probably should have shown audio route selector"); - } - - DialerCall call = getCall(); - - int newRoute; - if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { - newRoute = CallAudioState.ROUTE_WIRED_OR_EARPIECE; - Logger.get(context) - .logCallImpression( - DialerImpression.Type.BUBBLE_V2_WIRED_OR_EARPIECE, - call != null ? call.getUniqueCallId() : "", - call != null ? call.getTimeAddedMs() : 0); - } else { - newRoute = CallAudioState.ROUTE_SPEAKER; - Logger.get(context) - .logCallImpression( - DialerImpression.Type.BUBBLE_V2_SPEAKERPHONE, - call != null ? call.getUniqueCallId() : "", - call != null ? call.getTimeAddedMs() : 0); - } - TelecomAdapter.getInstance().setAudioRoute(newRoute); - } - - public void showAudioRouteSelector(Context context) { - Intent intent = new Intent(context, AudioRouteSelectorActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - context.startActivity(intent); - } - - private void toggleMute(Context context) { - DialerCall call = getCall(); - boolean shouldMute = !AudioModeProvider.getInstance().getAudioState().isMuted(); - Logger.get(context) - .logCallImpression( - shouldMute - ? DialerImpression.Type.BUBBLE_V2_MUTE_CALL - : DialerImpression.Type.BUBBLE_V2_UNMUTE_CALL, - call != null ? call.getUniqueCallId() : "", - call != null ? call.getTimeAddedMs() : 0); - TelecomAdapter.getInstance().mute(shouldMute); - } - - private void endCall(Context context) { - DialerCall call = getCall(); - - Logger.get(context) - .logCallImpression( - DialerImpression.Type.BUBBLE_V2_END_CALL, - call != null ? call.getUniqueCallId() : "", - call != null ? call.getTimeAddedMs() : 0); - if (call != null) { - call.disconnect(); - } - } - - private DialerCall getCall() { - CallList callList = InCallPresenter.getInstance().getCallList(); - if (callList != null) { - DialerCall call = callList.getOutgoingCall(); - if (call == null) { - call = callList.getActiveOrBackgroundCall(); - } - if (call != null) { - return call; - } - } - return null; - } -} diff --git a/java/com/android/incallui/NewReturnToCallController.java b/java/com/android/incallui/NewReturnToCallController.java deleted file mode 100644 index e77920524..000000000 --- a/java/com/android/incallui/NewReturnToCallController.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 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.incallui; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.telecom.CallAudioState; -import android.text.TextUtils; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.common.LogUtil; -import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.lettertile.LetterTileDrawable; -import com.android.dialer.telecom.TelecomUtil; -import com.android.incallui.ContactInfoCache.ContactCacheEntry; -import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; -import com.android.incallui.InCallPresenter.InCallUiListener; -import com.android.incallui.audiomode.AudioModeProvider; -import com.android.incallui.audiomode.AudioModeProvider.AudioModeListener; -import com.android.incallui.call.CallList; -import com.android.incallui.call.CallList.Listener; -import com.android.incallui.call.DialerCall; -import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo; -import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo.IconSize; -import com.android.newbubble.NewBubble; -import com.android.newbubble.NewBubbleComponent; -import com.android.newbubble.NewBubbleInfo; -import com.android.newbubble.NewBubbleInfo.Action; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -/** - * Listens for events relevant to the return-to-call bubble and updates the bubble's state as - * necessary. - * - *

Bubble shows when one of following happens: 1. a new outgoing/ongoing call appears 2. leave - * in-call UI with an outgoing/ongoing call - * - *

Bubble hides when one of following happens: 1. a call disconnect and there is no more - * outgoing/ongoing call 2. show in-call UI - */ -public class NewReturnToCallController implements InCallUiListener, Listener, AudioModeListener { - - private final Context context; - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - NewBubble bubble; - - private static Boolean canShowBubblesForTesting = null; - - private CallAudioState audioState; - - private final PendingIntent toggleSpeaker; - private final PendingIntent showSpeakerSelect; - private final PendingIntent toggleMute; - private final PendingIntent endCall; - private final PendingIntent fullScreen; - - private final ContactInfoCache contactInfoCache; - - public static boolean isEnabled(Context context) { - return ConfigProviderBindings.get(context).getBoolean("enable_return_to_call_bubble_v2", false); - } - - public NewReturnToCallController(Context context, ContactInfoCache contactInfoCache) { - this.context = context; - this.contactInfoCache = contactInfoCache; - - toggleSpeaker = createActionIntent(NewReturnToCallActionReceiver.ACTION_TOGGLE_SPEAKER); - showSpeakerSelect = - createActionIntent(NewReturnToCallActionReceiver.ACTION_SHOW_AUDIO_ROUTE_SELECTOR); - toggleMute = createActionIntent(NewReturnToCallActionReceiver.ACTION_TOGGLE_MUTE); - endCall = createActionIntent(NewReturnToCallActionReceiver.ACTION_END_CALL); - fullScreen = createActionIntent(NewReturnToCallActionReceiver.ACTION_RETURN_TO_CALL); - - InCallPresenter.getInstance().addInCallUiListener(this); - CallList.getInstance().addListener(this); - AudioModeProvider.getInstance().addListener(this); - audioState = AudioModeProvider.getInstance().getAudioState(); - } - - public void tearDown() { - hide(); - InCallPresenter.getInstance().removeInCallUiListener(this); - CallList.getInstance().removeListener(this); - AudioModeProvider.getInstance().removeListener(this); - } - - @Override - public void onUiShowing(boolean showing) { - LogUtil.i("ReturnToCallController.onUiShowing", "showing: " + showing); - if (showing) { - LogUtil.i("ReturnToCallController.onUiShowing", "going to hide"); - hide(); - } else { - if (getCall() != null) { - LogUtil.i("ReturnToCallController.onUiShowing", "going to show"); - show(); - } - } - } - - private void hide() { - if (bubble != null) { - bubble.hide(); - } else { - LogUtil.i("ReturnToCallController.hide", "hide() called without calling show()"); - } - } - - private void show() { - if (bubble == null) { - bubble = startBubble(); - } else { - bubble.show(); - } - startContactInfoSearch(); - } - - /** - * Determines whether bubbles can be shown based on permissions obtained. This should be checked - * before attempting to create a Bubble. - * - * @return true iff bubbles are able to be shown. - * @see Settings#canDrawOverlays(Context) - */ - private static boolean canShowBubbles(@NonNull Context context) { - return canShowBubblesForTesting != null - ? canShowBubblesForTesting - : Settings.canDrawOverlays(context); - } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - static void setCanShowBubblesForTesting(boolean canShowBubbles) { - canShowBubblesForTesting = canShowBubbles; - } - - @VisibleForTesting - public NewBubble startBubble() { - if (!canShowBubbles(context)) { - LogUtil.i("ReturnToCallController.startNewBubble", "can't show bubble, no permission"); - return null; - } - NewBubble returnToCallBubble = NewBubbleComponent.get(context).getNewBubble(); - returnToCallBubble.setBubbleInfo(generateBubbleInfo()); - returnToCallBubble.show(); - return returnToCallBubble; - } - - @Override - public void onIncomingCall(DialerCall call) {} - - @Override - public void onUpgradeToVideo(DialerCall call) {} - - @Override - public void onSessionModificationStateChange(DialerCall call) {} - - @Override - public void onCallListChange(CallList callList) { - if ((bubble == null || !bubble.isVisible()) - && getCall() != null - && !InCallPresenter.getInstance().isShowingInCallUi()) { - LogUtil.i("NewReturnToCallController.onCallListChange", "going to show bubble"); - show(); - } - } - - @Override - public void onDisconnect(DialerCall call) { - LogUtil.enterBlock("ReturnToCallController.onDisconnect"); - if (bubble != null && bubble.isVisible() && (getCall() == null)) { - // Show "Call ended" and hide bubble when there is no outgoing, active or background call - LogUtil.i("ReturnToCallController.onDisconnect", "show call ended and hide bubble"); - // Don't show text if it's Duo upgrade - // It doesn't work for Duo fallback upgrade since we're not considered in call - if (!TelecomUtil.isInCall(context) || CallList.getInstance().getIncomingCall() != null) { - bubble.showText(context.getText(R.string.incall_call_ended)); - } - hide(); - } else { - startContactInfoSearch(); - } - } - - @Override - public void onWiFiToLteHandover(DialerCall call) {} - - @Override - public void onHandoverToWifiFailed(DialerCall call) {} - - @Override - public void onInternationalCallOnWifi(@NonNull DialerCall call) {} - - @Override - public void onAudioStateChanged(CallAudioState audioState) { - this.audioState = audioState; - if (bubble != null) { - bubble.updateActions(generateActions()); - } - } - - private void startContactInfoSearch() { - DialerCall dialerCall = getCall(); - if (dialerCall != null) { - contactInfoCache.findInfo( - dialerCall, false /* isIncoming */, new ReturnToCallContactInfoCacheCallback(this)); - } - } - - private DialerCall getCall() { - DialerCall dialerCall = CallList.getInstance().getOutgoingCall(); - if (dialerCall == null) { - dialerCall = CallList.getInstance().getActiveOrBackgroundCall(); - } - return dialerCall; - } - - private void onPhotoAvatarReceived(@NonNull Drawable photo) { - if (bubble != null) { - bubble.updatePhotoAvatar(photo); - } - } - - private void onLetterTileAvatarReceived(@NonNull Drawable photo) { - if (bubble != null) { - bubble.updateAvatar(photo); - } - } - - private NewBubbleInfo generateBubbleInfo() { - return NewBubbleInfo.builder() - .setPrimaryColor(context.getResources().getColor(R.color.dialer_theme_color, null)) - .setPrimaryIcon(Icon.createWithResource(context, R.drawable.on_going_call)) - .setStartingYPosition( - context.getResources().getDimensionPixelOffset(R.dimen.return_to_call_initial_offset_y)) - .setActions(generateActions()) - .build(); - } - - @NonNull - private List generateActions() { - List actions = new ArrayList<>(); - SpeakerButtonInfo speakerButtonInfo = new SpeakerButtonInfo(audioState, IconSize.SIZE_24_DP); - - // Return to call - actions.add( - Action.builder() - .setIconDrawable( - context.getDrawable(R.drawable.quantum_ic_exit_to_app_flip_vd_theme_24)) - .setIntent(fullScreen) - .setName(context.getText(R.string.bubble_return_to_call)) - .setCheckable(false) - .build()); - // Mute/unmute - actions.add( - Action.builder() - .setIconDrawable(context.getDrawable(R.drawable.quantum_ic_mic_off_white_24)) - .setChecked(audioState.isMuted()) - .setIntent(toggleMute) - .setName(context.getText(R.string.incall_label_mute)) - .build()); - // Speaker/audio selector - actions.add( - Action.builder() - .setIconDrawable(context.getDrawable(speakerButtonInfo.icon)) - .setSecondaryIconDrawable( - speakerButtonInfo.checkable - ? null - : context.getDrawable(R.drawable.quantum_ic_arrow_drop_down_vd_theme_24)) - .setName(context.getText(speakerButtonInfo.label)) - .setCheckable(speakerButtonInfo.checkable) - .setChecked(speakerButtonInfo.isChecked) - .setIntent(speakerButtonInfo.checkable ? toggleSpeaker : showSpeakerSelect) - .build()); - // End call - actions.add( - Action.builder() - .setIconDrawable(context.getDrawable(R.drawable.quantum_ic_call_end_vd_theme_24)) - .setIntent(endCall) - .setName(context.getText(R.string.incall_label_end_call)) - .setCheckable(false) - .build()); - return actions; - } - - @NonNull - private PendingIntent createActionIntent(String action) { - Intent intent = new Intent(context, NewReturnToCallActionReceiver.class); - intent.setAction(action); - return PendingIntent.getBroadcast(context, 0, intent, 0); - } - - @NonNull - private LetterTileDrawable createLettleTileDrawable( - DialerCall dialerCall, ContactCacheEntry entry) { - String preferredName = - ContactDisplayUtils.getPreferredDisplayName( - entry.namePrimary, - entry.nameAlternative, - ContactsPreferencesFactory.newContactsPreferences(context)); - if (TextUtils.isEmpty(preferredName)) { - preferredName = entry.number; - } - - LetterTileDrawable letterTile = new LetterTileDrawable(context.getResources()); - letterTile.setCanonicalDialerLetterTileDetails( - dialerCall.updateNameIfRestricted(preferredName), - entry.lookupKey, - LetterTileDrawable.SHAPE_CIRCLE, - LetterTileDrawable.getContactTypeFromPrimitives( - dialerCall.isVoiceMailNumber(), - dialerCall.isSpam(), - entry.isBusiness, - dialerCall.getNumberPresentation(), - dialerCall.isConferenceCall())); - return letterTile; - } - - private static class ReturnToCallContactInfoCacheCallback implements ContactInfoCacheCallback { - - private final WeakReference newReturnToCallControllerWeakReference; - - private ReturnToCallContactInfoCacheCallback( - NewReturnToCallController newReturnToCallController) { - newReturnToCallControllerWeakReference = new WeakReference<>(newReturnToCallController); - } - - @Override - public void onContactInfoComplete(String callId, ContactCacheEntry entry) { - NewReturnToCallController newReturnToCallController = - newReturnToCallControllerWeakReference.get(); - if (newReturnToCallController == null) { - return; - } - if (entry.photo != null) { - newReturnToCallController.onPhotoAvatarReceived(entry.photo); - } else { - DialerCall dialerCall = CallList.getInstance().getCallById(callId); - if (dialerCall != null) { - newReturnToCallController.onLetterTileAvatarReceived( - newReturnToCallController.createLettleTileDrawable(dialerCall, entry)); - } - } - } - - @Override - public void onImageLoadComplete(String callId, ContactCacheEntry entry) { - NewReturnToCallController newReturnToCallController = - newReturnToCallControllerWeakReference.get(); - if (newReturnToCallController == null) { - return; - } - if (entry.photo != null) { - newReturnToCallController.onPhotoAvatarReceived(entry.photo); - } else { - DialerCall dialerCall = CallList.getInstance().getCallById(callId); - if (dialerCall != null) { - newReturnToCallController.onLetterTileAvatarReceived( - newReturnToCallController.createLettleTileDrawable(dialerCall, entry)); - } - } - } - } -} diff --git a/java/com/android/incallui/ReturnToCallActionReceiver.java b/java/com/android/incallui/ReturnToCallActionReceiver.java new file mode 100644 index 000000000..d6014aac6 --- /dev/null +++ b/java/com/android/incallui/ReturnToCallActionReceiver.java @@ -0,0 +1,155 @@ +/* + * 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.incallui; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.telecom.CallAudioState; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; +import com.android.incallui.audiomode.AudioModeProvider; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; +import com.android.incallui.call.TelecomAdapter; + +/** Handles clicks on the return-to-call bubble */ +public class ReturnToCallActionReceiver extends BroadcastReceiver { + + public static final String ACTION_RETURN_TO_CALL = "returnToCallV2"; + public static final String ACTION_TOGGLE_SPEAKER = "toggleSpeakerV2"; + public static final String ACTION_SHOW_AUDIO_ROUTE_SELECTOR = "showAudioRouteSelectorV2"; + public static final String ACTION_TOGGLE_MUTE = "toggleMuteV2"; + public static final String ACTION_END_CALL = "endCallV2"; + + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case ACTION_RETURN_TO_CALL: + returnToCall(context); + break; + case ACTION_TOGGLE_SPEAKER: + toggleSpeaker(context); + break; + case ACTION_SHOW_AUDIO_ROUTE_SELECTOR: + showAudioRouteSelector(context); + break; + case ACTION_TOGGLE_MUTE: + toggleMute(context); + break; + case ACTION_END_CALL: + endCall(context); + break; + default: + throw Assert.createIllegalStateFailException( + "Invalid intent action: " + intent.getAction()); + } + } + + private void returnToCall(Context context) { + DialerCall call = getCall(); + Logger.get(context) + .logCallImpression( + DialerImpression.Type.BUBBLE_V2_RETURN_TO_CALL, + call != null ? call.getUniqueCallId() : "", + call != null ? call.getTimeAddedMs() : 0); + + Intent activityIntent = InCallActivity.getIntent(context, false, false, false); + activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(activityIntent); + } + + private void toggleSpeaker(Context context) { + CallAudioState audioState = AudioModeProvider.getInstance().getAudioState(); + + if ((audioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) + == CallAudioState.ROUTE_BLUETOOTH) { + LogUtil.w( + "ReturnToCallActionReceiver.toggleSpeaker", + "toggleSpeaker() called when bluetooth available." + + " Probably should have shown audio route selector"); + } + + DialerCall call = getCall(); + + int newRoute; + if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { + newRoute = CallAudioState.ROUTE_WIRED_OR_EARPIECE; + Logger.get(context) + .logCallImpression( + DialerImpression.Type.BUBBLE_V2_WIRED_OR_EARPIECE, + call != null ? call.getUniqueCallId() : "", + call != null ? call.getTimeAddedMs() : 0); + } else { + newRoute = CallAudioState.ROUTE_SPEAKER; + Logger.get(context) + .logCallImpression( + DialerImpression.Type.BUBBLE_V2_SPEAKERPHONE, + call != null ? call.getUniqueCallId() : "", + call != null ? call.getTimeAddedMs() : 0); + } + TelecomAdapter.getInstance().setAudioRoute(newRoute); + } + + public void showAudioRouteSelector(Context context) { + Intent intent = new Intent(context, AudioRouteSelectorActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + context.startActivity(intent); + } + + private void toggleMute(Context context) { + DialerCall call = getCall(); + boolean shouldMute = !AudioModeProvider.getInstance().getAudioState().isMuted(); + Logger.get(context) + .logCallImpression( + shouldMute + ? DialerImpression.Type.BUBBLE_V2_MUTE_CALL + : DialerImpression.Type.BUBBLE_V2_UNMUTE_CALL, + call != null ? call.getUniqueCallId() : "", + call != null ? call.getTimeAddedMs() : 0); + TelecomAdapter.getInstance().mute(shouldMute); + } + + private void endCall(Context context) { + DialerCall call = getCall(); + + Logger.get(context) + .logCallImpression( + DialerImpression.Type.BUBBLE_V2_END_CALL, + call != null ? call.getUniqueCallId() : "", + call != null ? call.getTimeAddedMs() : 0); + if (call != null) { + call.disconnect(); + } + } + + private DialerCall getCall() { + CallList callList = InCallPresenter.getInstance().getCallList(); + if (callList != null) { + DialerCall call = callList.getOutgoingCall(); + if (call == null) { + call = callList.getActiveOrBackgroundCall(); + } + if (call != null) { + return call; + } + } + return null; + } +} diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java new file mode 100644 index 000000000..6227c77bd --- /dev/null +++ b/java/com/android/incallui/ReturnToCallController.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.telecom.CallAudioState; +import android.text.TextUtils; +import com.android.bubble.Bubble; +import com.android.bubble.BubbleComponent; +import com.android.bubble.BubbleInfo; +import com.android.bubble.BubbleInfo.Action; +import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.common.LogUtil; +import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.dialer.lettertile.LetterTileDrawable; +import com.android.dialer.telecom.TelecomUtil; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; +import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; +import com.android.incallui.InCallPresenter.InCallUiListener; +import com.android.incallui.audiomode.AudioModeProvider; +import com.android.incallui.audiomode.AudioModeProvider.AudioModeListener; +import com.android.incallui.call.CallList; +import com.android.incallui.call.CallList.Listener; +import com.android.incallui.call.DialerCall; +import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo; +import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo.IconSize; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * Listens for events relevant to the return-to-call bubble and updates the bubble's state as + * necessary. + * + *

Bubble shows when one of following happens: 1. a new outgoing/ongoing call appears 2. leave + * in-call UI with an outgoing/ongoing call + * + *

Bubble hides when one of following happens: 1. a call disconnect and there is no more + * outgoing/ongoing call 2. show in-call UI + */ +public class ReturnToCallController implements InCallUiListener, Listener, AudioModeListener { + + private final Context context; + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + Bubble bubble; + + private static Boolean canShowBubblesForTesting = null; + + private CallAudioState audioState; + + private final PendingIntent toggleSpeaker; + private final PendingIntent showSpeakerSelect; + private final PendingIntent toggleMute; + private final PendingIntent endCall; + private final PendingIntent fullScreen; + + private final ContactInfoCache contactInfoCache; + + public static boolean isEnabled(Context context) { + return ConfigProviderBindings.get(context).getBoolean("enable_return_to_call_bubble_v2", false); + } + + public ReturnToCallController(Context context, ContactInfoCache contactInfoCache) { + this.context = context; + this.contactInfoCache = contactInfoCache; + + toggleSpeaker = createActionIntent(ReturnToCallActionReceiver.ACTION_TOGGLE_SPEAKER); + showSpeakerSelect = + createActionIntent(ReturnToCallActionReceiver.ACTION_SHOW_AUDIO_ROUTE_SELECTOR); + toggleMute = createActionIntent(ReturnToCallActionReceiver.ACTION_TOGGLE_MUTE); + endCall = createActionIntent(ReturnToCallActionReceiver.ACTION_END_CALL); + fullScreen = createActionIntent(ReturnToCallActionReceiver.ACTION_RETURN_TO_CALL); + + InCallPresenter.getInstance().addInCallUiListener(this); + CallList.getInstance().addListener(this); + AudioModeProvider.getInstance().addListener(this); + audioState = AudioModeProvider.getInstance().getAudioState(); + } + + public void tearDown() { + hide(); + InCallPresenter.getInstance().removeInCallUiListener(this); + CallList.getInstance().removeListener(this); + AudioModeProvider.getInstance().removeListener(this); + } + + @Override + public void onUiShowing(boolean showing) { + LogUtil.i("ReturnToCallController.onUiShowing", "showing: " + showing); + if (showing) { + LogUtil.i("ReturnToCallController.onUiShowing", "going to hide"); + hide(); + } else { + if (getCall() != null) { + LogUtil.i("ReturnToCallController.onUiShowing", "going to show"); + show(); + } + } + } + + private void hide() { + if (bubble != null) { + bubble.hide(); + } else { + LogUtil.i("ReturnToCallController.hide", "hide() called without calling show()"); + } + } + + private void show() { + if (bubble == null) { + bubble = startBubble(); + } else { + bubble.show(); + } + startContactInfoSearch(); + } + + /** + * Determines whether bubbles can be shown based on permissions obtained. This should be checked + * before attempting to create a Bubble. + * + * @return true iff bubbles are able to be shown. + * @see Settings#canDrawOverlays(Context) + */ + private static boolean canShowBubbles(@NonNull Context context) { + return canShowBubblesForTesting != null + ? canShowBubblesForTesting + : Settings.canDrawOverlays(context); + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + static void setCanShowBubblesForTesting(boolean canShowBubbles) { + canShowBubblesForTesting = canShowBubbles; + } + + @VisibleForTesting + public Bubble startBubble() { + if (!canShowBubbles(context)) { + LogUtil.i("ReturnToCallController.startBubble", "can't show bubble, no permission"); + return null; + } + Bubble returnToCallBubble = BubbleComponent.get(context).getBubble(); + returnToCallBubble.setBubbleInfo(generateBubbleInfo()); + returnToCallBubble.show(); + return returnToCallBubble; + } + + @Override + public void onIncomingCall(DialerCall call) {} + + @Override + public void onUpgradeToVideo(DialerCall call) {} + + @Override + public void onSessionModificationStateChange(DialerCall call) {} + + @Override + public void onCallListChange(CallList callList) { + if ((bubble == null || !bubble.isVisible()) + && getCall() != null + && !InCallPresenter.getInstance().isShowingInCallUi()) { + LogUtil.i("ReturnToCallController.onCallListChange", "going to show bubble"); + show(); + } + } + + @Override + public void onDisconnect(DialerCall call) { + LogUtil.enterBlock("ReturnToCallController.onDisconnect"); + if (bubble != null && bubble.isVisible() && (getCall() == null)) { + // Show "Call ended" and hide bubble when there is no outgoing, active or background call + LogUtil.i("ReturnToCallController.onDisconnect", "show call ended and hide bubble"); + // Don't show text if it's Duo upgrade + // It doesn't work for Duo fallback upgrade since we're not considered in call + if (!TelecomUtil.isInCall(context) || CallList.getInstance().getIncomingCall() != null) { + bubble.showText(context.getText(R.string.incall_call_ended)); + } + hide(); + } else { + startContactInfoSearch(); + } + } + + @Override + public void onWiFiToLteHandover(DialerCall call) {} + + @Override + public void onHandoverToWifiFailed(DialerCall call) {} + + @Override + public void onInternationalCallOnWifi(@NonNull DialerCall call) {} + + @Override + public void onAudioStateChanged(CallAudioState audioState) { + this.audioState = audioState; + if (bubble != null) { + bubble.updateActions(generateActions()); + } + } + + private void startContactInfoSearch() { + DialerCall dialerCall = getCall(); + if (dialerCall != null) { + contactInfoCache.findInfo( + dialerCall, false /* isIncoming */, new ReturnToCallContactInfoCacheCallback(this)); + } + } + + private DialerCall getCall() { + DialerCall dialerCall = CallList.getInstance().getOutgoingCall(); + if (dialerCall == null) { + dialerCall = CallList.getInstance().getActiveOrBackgroundCall(); + } + return dialerCall; + } + + private void onPhotoAvatarReceived(@NonNull Drawable photo) { + if (bubble != null) { + bubble.updatePhotoAvatar(photo); + } + } + + private void onLetterTileAvatarReceived(@NonNull Drawable photo) { + if (bubble != null) { + bubble.updateAvatar(photo); + } + } + + private BubbleInfo generateBubbleInfo() { + return BubbleInfo.builder() + .setPrimaryColor(context.getResources().getColor(R.color.dialer_theme_color, null)) + .setPrimaryIcon(Icon.createWithResource(context, R.drawable.on_going_call)) + .setStartingYPosition( + context.getResources().getDimensionPixelOffset(R.dimen.return_to_call_initial_offset_y)) + .setActions(generateActions()) + .build(); + } + + @NonNull + private List generateActions() { + List actions = new ArrayList<>(); + SpeakerButtonInfo speakerButtonInfo = new SpeakerButtonInfo(audioState, IconSize.SIZE_24_DP); + + // Return to call + actions.add( + Action.builder() + .setIconDrawable( + context.getDrawable(R.drawable.quantum_ic_exit_to_app_flip_vd_theme_24)) + .setIntent(fullScreen) + .setName(context.getText(R.string.bubble_return_to_call)) + .setCheckable(false) + .build()); + // Mute/unmute + actions.add( + Action.builder() + .setIconDrawable(context.getDrawable(R.drawable.quantum_ic_mic_off_white_24)) + .setChecked(audioState.isMuted()) + .setIntent(toggleMute) + .setName(context.getText(R.string.incall_label_mute)) + .build()); + // Speaker/audio selector + actions.add( + Action.builder() + .setIconDrawable(context.getDrawable(speakerButtonInfo.icon)) + .setSecondaryIconDrawable( + speakerButtonInfo.checkable + ? null + : context.getDrawable(R.drawable.quantum_ic_arrow_drop_down_vd_theme_24)) + .setName(context.getText(speakerButtonInfo.label)) + .setCheckable(speakerButtonInfo.checkable) + .setChecked(speakerButtonInfo.isChecked) + .setIntent(speakerButtonInfo.checkable ? toggleSpeaker : showSpeakerSelect) + .build()); + // End call + actions.add( + Action.builder() + .setIconDrawable(context.getDrawable(R.drawable.quantum_ic_call_end_vd_theme_24)) + .setIntent(endCall) + .setName(context.getText(R.string.incall_label_end_call)) + .setCheckable(false) + .build()); + return actions; + } + + @NonNull + private PendingIntent createActionIntent(String action) { + Intent intent = new Intent(context, ReturnToCallActionReceiver.class); + intent.setAction(action); + return PendingIntent.getBroadcast(context, 0, intent, 0); + } + + @NonNull + private LetterTileDrawable createLettleTileDrawable( + DialerCall dialerCall, ContactCacheEntry entry) { + String preferredName = + ContactDisplayUtils.getPreferredDisplayName( + entry.namePrimary, + entry.nameAlternative, + ContactsPreferencesFactory.newContactsPreferences(context)); + if (TextUtils.isEmpty(preferredName)) { + preferredName = entry.number; + } + + LetterTileDrawable letterTile = new LetterTileDrawable(context.getResources()); + letterTile.setCanonicalDialerLetterTileDetails( + dialerCall.updateNameIfRestricted(preferredName), + entry.lookupKey, + LetterTileDrawable.SHAPE_CIRCLE, + LetterTileDrawable.getContactTypeFromPrimitives( + dialerCall.isVoiceMailNumber(), + dialerCall.isSpam(), + entry.isBusiness, + dialerCall.getNumberPresentation(), + dialerCall.isConferenceCall())); + return letterTile; + } + + private static class ReturnToCallContactInfoCacheCallback implements ContactInfoCacheCallback { + + private final WeakReference returnToCallControllerWeakReference; + + private ReturnToCallContactInfoCacheCallback(ReturnToCallController returnToCallController) { + returnToCallControllerWeakReference = new WeakReference<>(returnToCallController); + } + + @Override + public void onContactInfoComplete(String callId, ContactCacheEntry entry) { + ReturnToCallController returnToCallController = returnToCallControllerWeakReference.get(); + if (returnToCallController == null) { + return; + } + if (entry.photo != null) { + returnToCallController.onPhotoAvatarReceived(entry.photo); + } else { + DialerCall dialerCall = CallList.getInstance().getCallById(callId); + if (dialerCall != null) { + returnToCallController.onLetterTileAvatarReceived( + returnToCallController.createLettleTileDrawable(dialerCall, entry)); + } + } + } + + @Override + public void onImageLoadComplete(String callId, ContactCacheEntry entry) { + ReturnToCallController returnToCallController = returnToCallControllerWeakReference.get(); + if (returnToCallController == null) { + return; + } + if (entry.photo != null) { + returnToCallController.onPhotoAvatarReceived(entry.photo); + } else { + DialerCall dialerCall = CallList.getInstance().getCallById(callId); + if (dialerCall != null) { + returnToCallController.onLetterTileAvatarReceived( + returnToCallController.createLettleTileDrawable(dialerCall, entry)); + } + } + } + } +} diff --git a/java/com/android/incallui/StatusBarNotifier.java b/java/com/android/incallui/StatusBarNotifier.java index 87f332a1d..e2340b5e7 100644 --- a/java/com/android/incallui/StatusBarNotifier.java +++ b/java/com/android/incallui/StatusBarNotifier.java @@ -657,8 +657,8 @@ public class StatusBarNotifier } else if (call.hasProperty(Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY)) { return R.drawable.quantum_ic_phone_locked_vd_theme_24; } - // If NewReturnToCall is enabled, use the static icon. The animated one will show in the bubble. - if (NewReturnToCallController.isEnabled(context)) { + // If ReturnToCall is enabled, use the static icon. The animated one will show in the bubble. + if (ReturnToCallController.isEnabled(context)) { return R.drawable.quantum_ic_call_vd_theme_24; } else { return R.drawable.on_going_call; diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java deleted file mode 100644 index 785593c5c..000000000 --- a/java/com/android/newbubble/NewBubble.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.newbubble; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import com.android.newbubble.NewBubbleInfo.Action; -import java.util.List; - -/** - * Creates and manages a bubble window from information in a {@link NewBubbleInfo}. Before creating, - * be sure to check whether bubbles may be shown using {@code Settings.canDrawOverlays(context)} and - * request permission if necessary - */ -public interface NewBubble { - - /** - * Make the bubble visible. Will show a short entrance animation as it enters. If the bubble is - * already showing this method does nothing. - */ - void show(); - - /** Hide the bubble. */ - void hide(); - - /** Returns whether the bubble is currently visible */ - boolean isVisible(); - - /** - * Set the info for this Bubble to display - * - * @param bubbleInfo the BubbleInfo to display in this Bubble. - */ - void setBubbleInfo(@NonNull NewBubbleInfo bubbleInfo); - - /** - * Update the state and behavior of actions. - * - * @param actions the new state of the bubble's actions - */ - void updateActions(@NonNull List actions); - - /** - * Update the avatar from photo. - * - * @param avatar the new photo avatar in the bubble's primary button - */ - void updatePhotoAvatar(@NonNull Drawable avatar); - - /** - * Update the avatar. - * - * @param avatar the new avatar in the bubble's primary button - */ - void updateAvatar(@NonNull Drawable avatar); - - /** - * Display text. The bubble's drawer is not expandable while text is showing, and the drawer will - * be closed if already open. - * - * @param text the text to display to the user - */ - void showText(@NonNull CharSequence text); -} diff --git a/java/com/android/newbubble/NewBubbleComponent.java b/java/com/android/newbubble/NewBubbleComponent.java deleted file mode 100644 index e7edba6b4..000000000 --- a/java/com/android/newbubble/NewBubbleComponent.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.newbubble; - -import android.content.Context; -import android.support.annotation.NonNull; -import com.android.dialer.inject.HasRootComponent; -import dagger.Subcomponent; - -@Subcomponent -public abstract class NewBubbleComponent { - - @NonNull - public abstract NewBubble getNewBubble(); - - public static NewBubbleComponent get(Context context) { - return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) - .newBubbleComponent(); - } - - /** Used to refer to the root application component. */ - public interface HasComponent { - NewBubbleComponent newBubbleComponent(); - } -} diff --git a/java/com/android/newbubble/NewBubbleInfo.java b/java/com/android/newbubble/NewBubbleInfo.java deleted file mode 100644 index d9232a5a0..000000000 --- a/java/com/android/newbubble/NewBubbleInfo.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.newbubble; - -import android.app.PendingIntent; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.Px; -import com.google.auto.value.AutoValue; -import java.util.Collections; -import java.util.List; - -/** Info for displaying a {@link NewBubble} */ -@AutoValue -public abstract class NewBubbleInfo { - @ColorInt - public abstract int getPrimaryColor(); - - public abstract Icon getPrimaryIcon(); - - @Nullable - public abstract Drawable getAvatar(); - - @Px - public abstract int getStartingYPosition(); - - @NonNull - public abstract List getActions(); - - public static Builder builder() { - return new AutoValue_NewBubbleInfo.Builder().setActions(Collections.emptyList()); - } - - public static Builder from(@NonNull NewBubbleInfo bubbleInfo) { - return builder() - .setPrimaryColor(bubbleInfo.getPrimaryColor()) - .setPrimaryIcon(bubbleInfo.getPrimaryIcon()) - .setStartingYPosition(bubbleInfo.getStartingYPosition()) - .setActions(bubbleInfo.getActions()) - .setAvatar(bubbleInfo.getAvatar()); - } - - /** Builder for {@link NewBubbleInfo} */ - @AutoValue.Builder - public abstract static class Builder { - - public abstract Builder setPrimaryColor(@ColorInt int primaryColor); - - public abstract Builder setPrimaryIcon(@NonNull Icon primaryIcon); - - public abstract Builder setAvatar(@Nullable Drawable avatar); - - public abstract Builder setStartingYPosition(@Px int startingYPosition); - - public abstract Builder setActions(List actions); - - public abstract NewBubbleInfo build(); - } - - /** Represents actions to be shown in the bubble when expanded */ - @AutoValue - public abstract static class Action { - - public abstract Drawable getIconDrawable(); - - @Nullable - public abstract Drawable getSecondaryIconDrawable(); - - @NonNull - public abstract CharSequence getName(); - - @NonNull - public abstract PendingIntent getIntent(); - - public abstract boolean isCheckable(); - - public abstract boolean isChecked(); - - public static Builder builder() { - return new AutoValue_NewBubbleInfo_Action.Builder().setCheckable(true).setChecked(false); - } - - public static Builder from(@NonNull Action action) { - return builder() - .setIntent(action.getIntent()) - .setChecked(action.isChecked()) - .setCheckable(action.isCheckable()) - .setName(action.getName()) - .setIconDrawable(action.getIconDrawable()) - .setSecondaryIconDrawable(action.getSecondaryIconDrawable()); - } - - /** Builder for {@link Action} */ - @AutoValue.Builder - public abstract static class Builder { - - public abstract Builder setIconDrawable(Drawable iconDrawable); - - public abstract Builder setSecondaryIconDrawable(@Nullable Drawable secondaryIconDrawable); - - public abstract Builder setName(@NonNull CharSequence name); - - public abstract Builder setIntent(@NonNull PendingIntent intent); - - public abstract Builder setCheckable(boolean enabled); - - public abstract Builder setChecked(boolean checked); - - public abstract Action build(); - } - } -} diff --git a/java/com/android/newbubble/stub/NewBubbleStub.java b/java/com/android/newbubble/stub/NewBubbleStub.java deleted file mode 100644 index f5121cff9..000000000 --- a/java/com/android/newbubble/stub/NewBubbleStub.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.newbubble.stub; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import com.android.newbubble.NewBubble; -import com.android.newbubble.NewBubbleInfo; -import com.android.newbubble.NewBubbleInfo.Action; -import java.util.List; -import javax.inject.Inject; - -public class NewBubbleStub implements NewBubble { - - @Inject - public NewBubbleStub() {} - - @Override - public void show() {} - - @Override - public void hide() {} - - @Override - public boolean isVisible() { - return false; - } - - @Override - public void setBubbleInfo(@NonNull NewBubbleInfo bubbleInfo) {} - - @Override - public void updateActions(@NonNull List actions) {} - - @Override - public void updatePhotoAvatar(@NonNull Drawable avatar) {} - - @Override - public void updateAvatar(@NonNull Drawable avatar) {} - - @Override - public void showText(@NonNull CharSequence text) {} -} diff --git a/java/com/android/newbubble/stub/StubNewBubbleModule.java b/java/com/android/newbubble/stub/StubNewBubbleModule.java deleted file mode 100644 index 227ba5e7e..000000000 --- a/java/com/android/newbubble/stub/StubNewBubbleModule.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.newbubble.stub; - -import com.android.newbubble.NewBubble; -import dagger.Binds; -import dagger.Module; -import javax.inject.Singleton; - -@Module -public abstract class StubNewBubbleModule { - - @Binds - @Singleton - public abstract NewBubble bindsNewBubble(NewBubbleStub newBubbleStub); -} -- cgit v1.2.3