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 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