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/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 +- 7 files changed, 547 insertions(+), 550 deletions(-) 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 (limited to 'java/com/android/incallui') 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; -- cgit v1.2.3