Update dialer sources.
Test: Built package and system image.
This change clobbers the old source, and is an export
from an internal Google repository.
The internal repository was forked form Android in March,
and this change includes modifications since then, to
near the v8 release.
Since the fork, we've moved code from monolithic to independent modules. In addition,
we've switched to Blaze/Bazel as the build sysetm. This export, however, still uses make.
New dependencies have been added:
- Dagger
- Auto-Value
- Glide
- Libshortcutbadger
Going forward, development will still be in Google3, and the Gerrit release
will become an automated export, with the next drop happening in ~ two weeks.
Android.mk includes local modifications from ToT.
Abridged changelog:
Bug fixes
● Not able to mute, add a call when using Phone app in multiwindow mode
● Double tap on keypad triggering multiple key and tones
● Reported spam numbers not showing as spam in the call log
● Crash when user tries to block number while Phone app is not set as default
● Crash when user picks a number from search auto-complete list
Visual Voicemail (VVM) improvements
● Share Voicemail audio via standard exporting mechanisms that support file attachment
(email, MMS, etc.)
● Make phone number, email and web sites in VVM transcript clickable
● Set PIN before declining VVM Terms of Service {Carrier}
● Set client type for outbound visual voicemail SMS {Carrier}
New incoming call and incall UI on older devices
(Android M)
● Updated Phone app icon
● New incall UI (large buttons, button labels)
● New and animated Answer/Reject gestures
Accessibility
● Add custom answer/decline call buttons on answer screen for touch exploration
accessibility services
● Increase size of touch target
● Add verbal feedback when a Voicemail fails to load
● Fix pressing of Phone buttons while in a phone call using Switch Access
● Fix selecting and opening contacts in talkback mode
● Split focus for ‘Learn More’ link in caller id & spam to help distinguish similar text
Other
● Backup & Restore for App Preferences
● Prompt user to enable Wi-Fi calling if the call ends due to out of service and Wi-Fi is
connected
● Rename “Dialpad” to “Keypad”
● Show "Private number" for restricted calls
● Delete unused items (vcard, add contact, call history) from Phone menu
Change-Id: I2a7e53532a24c21bf308bf0a6d178d7ddbca4958
diff --git a/java/com/android/incallui/InCallActivityCommon.java b/java/com/android/incallui/InCallActivityCommon.java
new file mode 100644
index 0000000..a2467dd
--- /dev/null
+++ b/java/com/android/incallui/InCallActivityCommon.java
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2016 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.ActivityManager;
+import android.app.ActivityManager.AppTask;
+import android.app.ActivityManager.TaskDescription;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.content.res.ResourcesCompat;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.CheckBox;
+import android.widget.Toast;
+import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
+import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
+import com.android.dialer.animation.AnimUtils;
+import com.android.dialer.animation.AnimationListenerAdapter;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.CompatUtils;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.nano.ScreenEvent;
+import com.android.dialer.util.ViewUtil;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall;
+import com.android.incallui.call.DialerCall.State;
+import com.android.incallui.call.TelecomAdapter;
+import com.android.incallui.wifi.EnableWifiCallingPrompt;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Shared functionality between the new and old in call activity. */
+public class InCallActivityCommon {
+
+ private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
+ private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
+ private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
+ "InCallActivity.for_full_screen_intent";
+
+ private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
+
+ private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
+ private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DIALPAD_REQUEST_NONE,
+ DIALPAD_REQUEST_SHOW,
+ DIALPAD_REQUEST_HIDE,
+ })
+ @interface DialpadRequestType {}
+
+ private static final int DIALPAD_REQUEST_NONE = 1;
+ private static final int DIALPAD_REQUEST_SHOW = 2;
+ private static final int DIALPAD_REQUEST_HIDE = 3;
+
+ private final InCallActivity inCallActivity;
+ private boolean dismissKeyguard;
+ private boolean showPostCharWaitDialogOnResume;
+ private String showPostCharWaitDialogCallId;
+ private String showPostCharWaitDialogChars;
+ private Dialog dialog;
+ private InCallOrientationEventListener inCallOrientationEventListener;
+ private Animation dialpadSlideInAnimation;
+ private Animation dialpadSlideOutAnimation;
+ private boolean animateDialpadOnShow;
+ private String dtmfTextToPreopulate;
+ @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
+
+ private SelectPhoneAccountListener selectAccountListener =
+ new SelectPhoneAccountListener() {
+ @Override
+ public void onPhoneAccountSelected(
+ PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
+ DialerCall call = CallList.getInstance().getCallById(callId);
+ LogUtil.i(
+ "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
+ "call: " + call);
+ if (call != null) {
+ call.phoneAccountSelected(selectedAccountHandle, setDefault);
+ }
+ }
+
+ @Override
+ public void onDialogDismissed(String callId) {
+ DialerCall call = CallList.getInstance().getCallById(callId);
+ LogUtil.i(
+ "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
+ "disconnecting call: " + call);
+ if (call != null) {
+ call.disconnect();
+ }
+ }
+ };
+
+ public static void setIntentExtras(
+ Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
+ if (showDialpad) {
+ intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
+ }
+ intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
+ intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
+ }
+
+ public InCallActivityCommon(InCallActivity inCallActivity) {
+ this.inCallActivity = inCallActivity;
+ }
+
+ public void onCreate(Bundle icicle) {
+ // set this flag so this activity will stay in front of the keyguard
+ // Have the WindowManager filter out touch events that are "too fat".
+ int flags =
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+
+ inCallActivity.getWindow().addFlags(flags);
+
+ inCallActivity.setContentView(R.layout.incall_screen);
+
+ internalResolveIntent(inCallActivity.getIntent());
+
+ boolean isLandscape =
+ inCallActivity.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ boolean isRtl = ViewUtil.isRtl();
+
+ if (isLandscape) {
+ dialpadSlideInAnimation =
+ AnimationUtils.loadAnimation(
+ inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
+ dialpadSlideOutAnimation =
+ AnimationUtils.loadAnimation(
+ inCallActivity,
+ isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
+ } else {
+ dialpadSlideInAnimation =
+ AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
+ dialpadSlideOutAnimation =
+ AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
+ }
+
+ dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
+ dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
+
+ dialpadSlideOutAnimation.setAnimationListener(
+ new AnimationListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ performHideDialpadFragment();
+ }
+ });
+
+ if (icicle != null) {
+ // If the dialpad was shown before, set variables indicating it should be shown and
+ // populated with the previous DTMF text. The dialpad is actually shown and populated
+ // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
+ if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
+ boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
+ showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
+ animateDialpadOnShow = false;
+ }
+ dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
+
+ SelectPhoneAccountDialogFragment dialogFragment =
+ (SelectPhoneAccountDialogFragment)
+ inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
+ if (dialogFragment != null) {
+ dialogFragment.setListener(selectAccountListener);
+ }
+ }
+
+ inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity);
+ }
+
+ public void onSaveInstanceState(Bundle out) {
+ // TODO: The dialpad fragment should handle this as part of its own state
+ out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null) {
+ out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
+ }
+ }
+
+ public void onStart() {
+ // setting activity should be last thing in setup process
+ InCallPresenter.getInstance().setActivity(inCallActivity);
+ enableInCallOrientationEventListener(
+ inCallActivity.getRequestedOrientation()
+ == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
+
+ InCallPresenter.getInstance().onActivityStarted();
+ }
+
+ public void onResume() {
+ if (InCallPresenter.getInstance().isReadyForTearDown()) {
+ LogUtil.i(
+ "InCallActivityCommon.onResume",
+ "InCallPresenter is ready for tear down, not sending updates");
+ } else {
+ updateTaskDescription();
+ InCallPresenter.getInstance().onUiShowing(true);
+ }
+
+ // If there is a pending request to show or hide the dialpad, handle that now.
+ if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
+ if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
+ // Exit fullscreen so that the user has access to the dialpad hide/show button and
+ // can hide the dialpad. Important when showing the dialpad from within dialer.
+ InCallPresenter.getInstance().setFullScreen(false, true /* force */);
+
+ inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
+ animateDialpadOnShow = false;
+
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null) {
+ dialpadFragment.setDtmfText(dtmfTextToPreopulate);
+ dtmfTextToPreopulate = null;
+ }
+ } else {
+ LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
+ if (getDialpadFragment() != null) {
+ inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
+ }
+ }
+ showDialpadRequest = DIALPAD_REQUEST_NONE;
+ }
+
+ if (showPostCharWaitDialogOnResume) {
+ showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
+ }
+
+ CallList.getInstance()
+ .onInCallUiShown(
+ inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
+ }
+
+ // onPause is guaranteed to be called when the InCallActivity goes
+ // in the background.
+ public void onPause() {
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null) {
+ dialpadFragment.onDialerKeyUp(null);
+ }
+
+ InCallPresenter.getInstance().onUiShowing(false);
+ if (inCallActivity.isFinishing()) {
+ InCallPresenter.getInstance().unsetActivity(inCallActivity);
+ }
+ }
+
+ public void onStop() {
+ enableInCallOrientationEventListener(false);
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
+ InCallPresenter.getInstance().onActivityStopped();
+ }
+
+ public void onDestroy() {
+ InCallPresenter.getInstance().unsetActivity(inCallActivity);
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
+ }
+
+ public void onNewIntent(Intent intent) {
+ LogUtil.i("InCallActivityCommon.onNewIntent", "");
+
+ // We're being re-launched with a new Intent. Since it's possible for a
+ // single InCallActivity instance to persist indefinitely (even if we
+ // finish() ourselves), this sequence can potentially happen any time
+ // the InCallActivity needs to be displayed.
+
+ // Stash away the new intent so that we can get it in the future
+ // by calling getIntent(). (Otherwise getIntent() will return the
+ // original Intent from when we first got created!)
+ inCallActivity.setIntent(intent);
+
+ // Activities are always paused before receiving a new intent, so
+ // we can count on our onResume() method being called next.
+
+ // Just like in onCreate(), handle the intent.
+ internalResolveIntent(intent);
+ }
+
+ public boolean onBackPressed(boolean isInCallScreenVisible) {
+ LogUtil.i("InCallActivityCommon.onBackPressed", "");
+
+ // BACK is also used to exit out of any "special modes" of the
+ // in-call UI:
+ if (!inCallActivity.isVisible()) {
+ return true;
+ }
+
+ if (!isInCallScreenVisible) {
+ return true;
+ }
+
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null && dialpadFragment.isVisible()) {
+ inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
+ return true;
+ }
+
+ // Always disable the Back key while an incoming call is ringing
+ DialerCall call = CallList.getInstance().getIncomingCall();
+ if (call != null) {
+ LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
+ return true;
+ }
+
+ // Nothing special to do. Fall back to the default behavior.
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ // push input to the dialer.
+ if (dialpadFragment != null
+ && (dialpadFragment.isVisible())
+ && (dialpadFragment.onDialerKeyUp(event))) {
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_CALL) {
+ // Always consume CALL to be sure the PhoneWindow won't do anything with it
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CALL:
+ boolean handled = InCallPresenter.getInstance().handleCallKey();
+ if (!handled) {
+ LogUtil.e(
+ "InCallActivityCommon.onKeyDown",
+ "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
+ }
+ // Always consume CALL to be sure the PhoneWindow won't do anything with it
+ return true;
+
+ // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
+ // The standard system-wide handling of the ENDCALL key
+ // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
+ // already implements exactly what the UI spec wants,
+ // namely (1) "hang up" if there's a current active call,
+ // or (2) "don't answer" if there's a current ringing call.
+
+ case KeyEvent.KEYCODE_CAMERA:
+ // Disable the CAMERA button while in-call since it's too
+ // easy to press accidentally.
+ return true;
+
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ // Ringer silencing handled by PhoneWindowManager.
+ break;
+
+ case KeyEvent.KEYCODE_MUTE:
+ TelecomAdapter.getInstance()
+ .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
+ return true;
+
+ // Various testing/debugging features, enabled ONLY when VERBOSE == true.
+ case KeyEvent.KEYCODE_SLASH:
+ if (LogUtil.isVerboseEnabled()) {
+ LogUtil.v(
+ "InCallActivityCommon.onKeyDown",
+ "----------- InCallActivity View dump --------------");
+ // Dump starting from the top-level view of the entire activity:
+ Window w = inCallActivity.getWindow();
+ View decorView = w.getDecorView();
+ LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_EQUALS:
+ break;
+ }
+
+ return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
+ }
+
+ private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
+ LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
+
+ // As soon as the user starts typing valid dialable keys on the
+ // keyboard (presumably to type DTMF tones) we start passing the
+ // key events to the DTMFDialer's onDialerKeyDown.
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null && dialpadFragment.isVisible()) {
+ return dialpadFragment.onDialerKeyDown(event);
+ }
+
+ return false;
+ }
+
+ public void dismissKeyguard(boolean dismiss) {
+ if (dismissKeyguard == dismiss) {
+ return;
+ }
+ dismissKeyguard = dismiss;
+ if (dismiss) {
+ inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ } else {
+ inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+ }
+
+ public void showPostCharWaitDialog(String callId, String chars) {
+ if (inCallActivity.isVisible()) {
+ PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
+ fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
+
+ showPostCharWaitDialogOnResume = false;
+ showPostCharWaitDialogCallId = null;
+ showPostCharWaitDialogChars = null;
+ } else {
+ showPostCharWaitDialogOnResume = true;
+ showPostCharWaitDialogCallId = callId;
+ showPostCharWaitDialogChars = chars;
+ }
+ }
+
+ public void maybeShowErrorDialogOnDisconnect(DisconnectCause cause) {
+ LogUtil.i(
+ "InCallActivityCommon.maybeShowErrorDialogOnDisconnect", "disconnect cause: %s", cause);
+
+ if (!inCallActivity.isFinishing()) {
+ if (EnableWifiCallingPrompt.shouldShowPrompt(cause)) {
+ Pair<Dialog, CharSequence> pair =
+ EnableWifiCallingPrompt.createDialog(inCallActivity, cause);
+ showErrorDialog(pair.first, pair.second);
+ } else if (shouldShowDisconnectErrorDialog(cause)) {
+ Pair<Dialog, CharSequence> pair = getDisconnectErrorDialog(inCallActivity, cause);
+ showErrorDialog(pair.first, pair.second);
+ }
+ }
+ }
+
+ /**
+ * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
+ * be shown on launch.
+ *
+ * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
+ * false} to indicate no change should be made to the dialpad visibility.
+ */
+ private void relaunchedFromDialer(boolean showDialpad) {
+ showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
+ animateDialpadOnShow = true;
+
+ if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
+ // If there's only one line in use, AND it's on hold, then we're sure the user
+ // wants to use the dialpad toward the exact line, so un-hold the holding line.
+ DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
+ if (call != null && call.getState() == State.ONHOLD) {
+ call.unhold();
+ }
+ }
+ }
+
+ public void dismissPendingDialogs() {
+ if (dialog != null) {
+ dialog.dismiss();
+ dialog = null;
+ }
+ }
+
+ private static boolean shouldShowDisconnectErrorDialog(@NonNull DisconnectCause cause) {
+ return !TextUtils.isEmpty(cause.getDescription())
+ && (cause.getCode() == DisconnectCause.ERROR
+ || cause.getCode() == DisconnectCause.RESTRICTED);
+ }
+
+ private static Pair<Dialog, CharSequence> getDisconnectErrorDialog(
+ @NonNull Context context, @NonNull DisconnectCause cause) {
+ CharSequence message = cause.getDescription();
+ Dialog dialog =
+ new AlertDialog.Builder(context)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ return new Pair<>(dialog, message);
+ }
+
+ private void showErrorDialog(Dialog dialog, CharSequence message) {
+ LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
+ inCallActivity.dismissPendingDialogs();
+
+ // Show toast if apps is in background when dialog won't be visible.
+ if (!inCallActivity.isVisible()) {
+ Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ this.dialog = dialog;
+ dialog.setOnDismissListener(
+ new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
+ onDialogDismissed();
+ }
+ });
+ dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ dialog.show();
+ }
+
+ private void onDialogDismissed() {
+ dialog = null;
+ CallList.getInstance().onErrorDialogDismissed();
+ InCallPresenter.getInstance().onDismissDialog();
+ }
+
+ public void enableInCallOrientationEventListener(boolean enable) {
+ if (enable) {
+ inCallOrientationEventListener.enable(true);
+ } else {
+ inCallOrientationEventListener.disable();
+ }
+ }
+
+ public void setExcludeFromRecents(boolean exclude) {
+ List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
+ int taskId = inCallActivity.getTaskId();
+ for (int i = 0; i < tasks.size(); i++) {
+ ActivityManager.AppTask task = tasks.get(i);
+ try {
+ if (task.getTaskInfo().id == taskId) {
+ task.setExcludeFromRecents(exclude);
+ }
+ } catch (RuntimeException e) {
+ LogUtil.e(
+ "InCallActivityCommon.setExcludeFromRecents",
+ "RuntimeException when excluding task from recents.",
+ e);
+ }
+ }
+ }
+
+ public void showWifiToLteHandoverToast(DialerCall call) {
+ if (call.hasShownWiFiToLteHandoverToast()) {
+ return;
+ }
+ Toast.makeText(
+ inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
+ .show();
+ call.setHasShownWiFiToLteHandoverToast();
+ }
+
+ public void showWifiFailedDialog(final DialerCall call) {
+ if (call.showWifiHandoverAlertAsToast()) {
+ LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
+ Toast.makeText(
+ inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+
+ dismissPendingDialogs();
+
+ AlertDialog.Builder builder =
+ new AlertDialog.Builder(inCallActivity)
+ .setTitle(R.string.video_call_lte_to_wifi_failed_title);
+
+ // This allows us to use the theme of the dialog instead of the activity
+ View dialogCheckBoxView =
+ View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
+ final CheckBox wifiHandoverFailureCheckbox =
+ (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
+ wifiHandoverFailureCheckbox.setChecked(false);
+
+ dialog =
+ builder
+ .setView(dialogCheckBoxView)
+ .setMessage(R.string.video_call_lte_to_wifi_failed_message)
+ .setOnCancelListener(
+ new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ onDialogDismissed();
+ }
+ })
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ call.setDoNotShowDialogForHandoffToWifiFailure(
+ wifiHandoverFailureCheckbox.isChecked());
+ dialog.cancel();
+ onDialogDismissed();
+ }
+ })
+ .create();
+
+ LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
+ dialog.show();
+ }
+
+ public boolean showDialpadFragment(boolean show, boolean animate) {
+ // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
+ boolean isDialpadVisible = isDialpadVisible();
+ LogUtil.i(
+ "InCallActivityCommon.showDialpadFragment",
+ "show: %b, animate: %b, " + "isDialpadVisible: %b",
+ show,
+ animate,
+ isDialpadVisible);
+ if (show == isDialpadVisible) {
+ return false;
+ }
+
+ FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
+ if (dialpadFragmentManager == null) {
+ LogUtil.i(
+ "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
+ return false;
+ }
+
+ // We don't do a FragmentTransaction on the hide case because it will be dealt with when
+ // the listener is fired after an animation finishes.
+ if (!animate) {
+ if (show) {
+ performShowDialpadFragment(dialpadFragmentManager);
+ } else {
+ performHideDialpadFragment();
+ }
+ } else {
+ if (show) {
+ performShowDialpadFragment(dialpadFragmentManager);
+ getDialpadFragment().animateShowDialpad();
+ }
+ getDialpadFragment()
+ .getView()
+ .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
+ }
+
+ ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
+ if (sensor != null) {
+ sensor.onDialpadVisible(show);
+ }
+ showDialpadRequest = DIALPAD_REQUEST_NONE;
+ return true;
+ }
+
+ private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
+ FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment == null) {
+ transaction.add(
+ inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
+ } else {
+ transaction.show(dialpadFragment);
+ }
+
+ transaction.commitAllowingStateLoss();
+ dialpadFragmentManager.executePendingTransactions();
+
+ Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
+ }
+
+ private void performHideDialpadFragment() {
+ FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
+ if (fragmentManager == null) {
+ LogUtil.e(
+ "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
+ return;
+ }
+
+ Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
+ if (fragment != null) {
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ transaction.hide(fragment);
+ transaction.commitAllowingStateLoss();
+ fragmentManager.executePendingTransactions();
+ }
+ }
+
+ public boolean isDialpadVisible() {
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ return dialpadFragment != null && dialpadFragment.isVisible();
+ }
+
+ /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
+ @Nullable
+ private DialpadFragment getDialpadFragment() {
+ FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
+ if (fragmentManager == null) {
+ return null;
+ }
+ return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
+ }
+
+ public void updateTaskDescription() {
+ Resources resources = inCallActivity.getResources();
+ int color;
+ if (resources.getBoolean(R.bool.is_layout_landscape)) {
+ color =
+ ResourcesCompat.getColor(
+ resources, R.color.statusbar_background_color, inCallActivity.getTheme());
+ } else {
+ color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
+ }
+
+ TaskDescription td =
+ new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
+ inCallActivity.setTaskDescription(td);
+ }
+
+ public boolean hasPendingDialogs() {
+ return dialog != null;
+ }
+
+ private void internalResolveIntent(Intent intent) {
+ if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
+ return;
+ }
+
+ if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
+ // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
+ // dialpad should be initially visible. If the extra isn't
+ // present at all, we just leave the dialpad in its previous state.
+ boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
+ LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
+
+ relaunchedFromDialer(showDialpad);
+ }
+
+ DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
+ if (outgoingCall == null) {
+ outgoingCall = CallList.getInstance().getPendingOutgoingCall();
+ }
+
+ boolean isNewOutgoingCall = false;
+ if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
+ isNewOutgoingCall = true;
+ intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
+
+ // InCallActivity is responsible for disconnecting a new outgoing call if there
+ // is no way of making it (i.e. no valid call capable accounts).
+ // If the version is not MSIM compatible, then ignore this code.
+ if (CompatUtils.isMSIMCompatible()
+ && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
+ LogUtil.i(
+ "InCallActivityCommon.internalResolveIntent",
+ "call with no valid accounts, disconnecting");
+ outgoingCall.disconnect();
+ }
+
+ dismissKeyguard(true);
+ }
+
+ boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
+ inCallActivity.onResolveIntent(outgoingCall, isNewOutgoingCall, didShowAccountSelectionDialog);
+ }
+
+ private boolean maybeShowAccountSelectionDialog() {
+ DialerCall call = CallList.getInstance().getWaitingForAccountCall();
+ if (call == null) {
+ return false;
+ }
+
+ Bundle extras = call.getIntentExtras();
+ List<PhoneAccountHandle> phoneAccountHandles;
+ if (extras != null) {
+ phoneAccountHandles =
+ extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
+ } else {
+ phoneAccountHandles = new ArrayList<>();
+ }
+
+ DialogFragment dialogFragment =
+ SelectPhoneAccountDialogFragment.newInstance(
+ R.string.select_phone_account_for_calls,
+ true,
+ phoneAccountHandles,
+ selectAccountListener,
+ call.getId());
+ dialogFragment.show(inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
+ return true;
+ }
+}