blob: c588dc4e1eaf071accfb4835529c1908a684a533 [file] [log] [blame]
/*
* 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
*/
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) {
if (showing) {
hide();
} else {
if (getCall() != null) {
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) {}
@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<Action> generateActions() {
List<Action> 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<NewReturnToCallController> 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));
}
}
}
}
}