summaryrefslogtreecommitdiff
path: root/InCallUI/src/com/android/incallui
diff options
context:
space:
mode:
Diffstat (limited to 'InCallUI/src/com/android/incallui')
-rw-r--r--InCallUI/src/com/android/incallui/AccelerometerListener.java169
-rw-r--r--InCallUI/src/com/android/incallui/AccessibleAnswerFragment.java157
-rw-r--r--InCallUI/src/com/android/incallui/AnswerFragment.java307
-rw-r--r--InCallUI/src/com/android/incallui/AnswerPresenter.java312
-rw-r--r--InCallUI/src/com/android/incallui/AudioModeProvider.java105
-rw-r--r--InCallUI/src/com/android/incallui/BaseFragment.java84
-rw-r--r--InCallUI/src/com/android/incallui/Call.java1023
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonFragment.java819
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonPresenter.java486
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java1510
-rw-r--r--InCallUI/src/com/android/incallui/CallCardPresenter.java1181
-rw-r--r--InCallUI/src/com/android/incallui/CallList.java695
-rw-r--r--InCallUI/src/com/android/incallui/CallTimer.java90
-rw-r--r--InCallUI/src/com/android/incallui/CallerInfo.java585
-rw-r--r--InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java599
-rw-r--r--InCallUI/src/com/android/incallui/CallerInfoUtils.java234
-rw-r--r--InCallUI/src/com/android/incallui/CircularRevealFragment.java170
-rw-r--r--InCallUI/src/com/android/incallui/ConferenceManagerFragment.java139
-rw-r--r--InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java144
-rw-r--r--InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java533
-rw-r--r--InCallUI/src/com/android/incallui/ContactInfoCache.java699
-rw-r--r--InCallUI/src/com/android/incallui/ContactUtils.java48
-rw-r--r--InCallUI/src/com/android/incallui/ContactsAsyncHelper.java258
-rw-r--r--InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java61
-rw-r--r--InCallUI/src/com/android/incallui/DialpadFragment.java563
-rw-r--r--InCallUI/src/com/android/incallui/DialpadPresenter.java84
-rw-r--r--InCallUI/src/com/android/incallui/DistanceHelper.java37
-rw-r--r--InCallUI/src/com/android/incallui/ExternalCallList.java105
-rw-r--r--InCallUI/src/com/android/incallui/ExternalCallNotifier.java406
-rw-r--r--InCallUI/src/com/android/incallui/FragmentDisplayManager.java23
-rw-r--r--InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java155
-rw-r--r--InCallUI/src/com/android/incallui/GlowPadWrapper.java158
-rw-r--r--InCallUI/src/com/android/incallui/InCallActivity.java980
-rw-r--r--InCallUI/src/com/android/incallui/InCallAnimationUtils.java184
-rw-r--r--InCallUI/src/com/android/incallui/InCallCameraManager.java184
-rw-r--r--InCallUI/src/com/android/incallui/InCallContactInteractions.java399
-rw-r--r--InCallUI/src/com/android/incallui/InCallDateUtils.java53
-rw-r--r--InCallUI/src/com/android/incallui/InCallOrientationEventListener.java178
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java1938
-rw-r--r--InCallUI/src/com/android/incallui/InCallServiceImpl.java100
-rw-r--r--InCallUI/src/com/android/incallui/InCallServiceListener.java41
-rw-r--r--InCallUI/src/com/android/incallui/InCallUIMaterialColorMapUtils.java55
-rw-r--r--InCallUI/src/com/android/incallui/InCallVideoCallCallback.java156
-rw-r--r--InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java284
-rw-r--r--InCallUI/src/com/android/incallui/LatencyReport.java145
-rw-r--r--InCallUI/src/com/android/incallui/Log.java176
-rw-r--r--InCallUI/src/com/android/incallui/NeededForReflection.java30
-rw-r--r--InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java82
-rw-r--r--InCallUI/src/com/android/incallui/PostCharDialogFragment.java95
-rw-r--r--InCallUI/src/com/android/incallui/Presenter.java59
-rw-r--r--InCallUI/src/com/android/incallui/ProximitySensor.java317
-rw-r--r--InCallUI/src/com/android/incallui/StatusBarNotifier.java793
-rw-r--r--InCallUI/src/com/android/incallui/TelecomAdapter.java226
-rw-r--r--InCallUI/src/com/android/incallui/Ui.java24
-rw-r--r--InCallUI/src/com/android/incallui/VideoCallFragment.java901
-rw-r--r--InCallUI/src/com/android/incallui/VideoCallPresenter.java1306
-rw-r--r--InCallUI/src/com/android/incallui/VideoPauseController.java420
-rw-r--r--InCallUI/src/com/android/incallui/VideoUtils.java109
-rw-r--r--InCallUI/src/com/android/incallui/async/PausableExecutor.java61
-rw-r--r--InCallUI/src/com/android/incallui/async/PausableExecutorImpl.java42
-rw-r--r--InCallUI/src/com/android/incallui/ringtone/DialerRingtoneManager.java140
-rw-r--r--InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java168
-rw-r--r--InCallUI/src/com/android/incallui/ringtone/ToneGeneratorFactory.java36
-rw-r--r--InCallUI/src/com/android/incallui/service/PhoneNumberService.java67
-rw-r--r--InCallUI/src/com/android/incallui/spam/SpamCallListListener.java117
-rw-r--r--InCallUI/src/com/android/incallui/util/AccessibilityUtil.java30
-rw-r--r--InCallUI/src/com/android/incallui/util/TelecomCallUtil.java53
-rw-r--r--InCallUI/src/com/android/incallui/widget/multiwaveview/Ease.java132
-rw-r--r--InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java1473
-rw-r--r--InCallUI/src/com/android/incallui/widget/multiwaveview/PointCloud.java235
-rw-r--r--InCallUI/src/com/android/incallui/widget/multiwaveview/TargetDrawable.java250
-rw-r--r--InCallUI/src/com/android/incallui/widget/multiwaveview/Tweener.java178
72 files changed, 0 insertions, 24156 deletions
diff --git a/InCallUI/src/com/android/incallui/AccelerometerListener.java b/InCallUI/src/com/android/incallui/AccelerometerListener.java
deleted file mode 100644
index b5ad29675..000000000
--- a/InCallUI/src/com/android/incallui/AccelerometerListener.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2009 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.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * This class is used to listen to the accelerometer to monitor the
- * orientation of the phone. The client of this class is notified when
- * the orientation changes between horizontal and vertical.
- */
-public class AccelerometerListener {
- private static final String TAG = "AccelerometerListener";
- private static final boolean DEBUG = true;
- private static final boolean VDEBUG = false;
-
- private SensorManager mSensorManager;
- private Sensor mSensor;
-
- // mOrientation is the orientation value most recently reported to the client.
- private int mOrientation;
-
- // mPendingOrientation is the latest orientation computed based on the sensor value.
- // This is sent to the client after a rebounce delay, at which point it is copied to
- // mOrientation.
- private int mPendingOrientation;
-
- private OrientationListener mListener;
-
- // Device orientation
- public static final int ORIENTATION_UNKNOWN = 0;
- public static final int ORIENTATION_VERTICAL = 1;
- public static final int ORIENTATION_HORIZONTAL = 2;
-
- private static final int ORIENTATION_CHANGED = 1234;
-
- private static final int VERTICAL_DEBOUNCE = 100;
- private static final int HORIZONTAL_DEBOUNCE = 500;
- private static final double VERTICAL_ANGLE = 50.0;
-
- public interface OrientationListener {
- public void orientationChanged(int orientation);
- }
-
- public AccelerometerListener(Context context) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- }
-
- public void setListener(OrientationListener listener) {
- mListener = listener;
- }
-
- public void enable(boolean enable) {
- if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
- synchronized (this) {
- if (enable) {
- mOrientation = ORIENTATION_UNKNOWN;
- mPendingOrientation = ORIENTATION_UNKNOWN;
- mSensorManager.registerListener(mSensorListener, mSensor,
- SensorManager.SENSOR_DELAY_NORMAL);
- } else {
- mSensorManager.unregisterListener(mSensorListener);
- mHandler.removeMessages(ORIENTATION_CHANGED);
- }
- }
- }
-
- private void setOrientation(int orientation) {
- synchronized (this) {
- if (mPendingOrientation == orientation) {
- // Pending orientation has not changed, so do nothing.
- return;
- }
-
- // Cancel any pending messages.
- // We will either start a new timer or cancel alltogether
- // if the orientation has not changed.
- mHandler.removeMessages(ORIENTATION_CHANGED);
-
- if (mOrientation != orientation) {
- // Set timer to send an event if the orientation has changed since its
- // previously reported value.
- mPendingOrientation = orientation;
- final Message m = mHandler.obtainMessage(ORIENTATION_CHANGED);
- // set delay to our debounce timeout
- int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE
- : HORIZONTAL_DEBOUNCE);
- mHandler.sendMessageDelayed(m, delay);
- } else {
- // no message is pending
- mPendingOrientation = ORIENTATION_UNKNOWN;
- }
- }
- }
-
- private void onSensorEvent(double x, double y, double z) {
- if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");
-
- // If some values are exactly zero, then likely the sensor is not powered up yet.
- // ignore these events to avoid false horizontal positives.
- if (x == 0.0 || y == 0.0 || z == 0.0) return;
-
- // magnitude of the acceleration vector projected onto XY plane
- final double xy = Math.hypot(x, y);
- // compute the vertical angle
- double angle = Math.atan2(xy, z);
- // convert to degrees
- angle = angle * 180.0 / Math.PI;
- final int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);
- if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation);
- setOrientation(orientation);
- }
-
- SensorEventListener mSensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- onSensorEvent(event.values[0], event.values[1], event.values[2]);
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // ignore
- }
- };
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case ORIENTATION_CHANGED:
- synchronized (this) {
- mOrientation = mPendingOrientation;
- if (DEBUG) {
- Log.d(TAG, "orientation: " +
- (mOrientation == ORIENTATION_HORIZONTAL ? "horizontal"
- : (mOrientation == ORIENTATION_VERTICAL ? "vertical"
- : "unknown")));
- }
- if (mListener != null) {
- mListener.orientationChanged(mOrientation);
- }
- }
- break;
- }
- }
- };
-}
diff --git a/InCallUI/src/com/android/incallui/AccessibleAnswerFragment.java b/InCallUI/src/com/android/incallui/AccessibleAnswerFragment.java
deleted file mode 100644
index 89c78ec61..000000000
--- a/InCallUI/src/com/android/incallui/AccessibleAnswerFragment.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2013 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.os.Bundle;
-import android.telecom.VideoProfile;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.dialer.R;
-
-/**
- * AnswerFragment to use when touch exploration is enabled in accessibility.
- */
-public class AccessibleAnswerFragment extends AnswerFragment {
-
- private static final String TAG = AccessibleAnswerFragment.class.getSimpleName();
- private static final int SWIPE_THRESHOLD = 100;
-
- private View mAnswer;
- private View mDecline;
- private View mText;
-
- private TouchListener mTouchListener;
- private GestureDetector mGestureDetector;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- ViewGroup group = (ViewGroup) inflater.inflate(R.layout.accessible_answer_fragment,
- container, false);
-
- mTouchListener = new TouchListener();
- mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- return AccessibleAnswerFragment.this.onFling(e1, e2, velocityX, velocityX);
- }
- });
-
- mAnswer = group.findViewById(R.id.accessible_answer_fragment_answer);
- mAnswer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "Answer Button Clicked");
- onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
- }
- });
- mDecline = group.findViewById(R.id.accessible_answer_fragment_decline);
- mDecline.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "Decline Button Clicked");
- onDecline(getContext());
- }
- });
-
- mText = group.findViewById(R.id.accessible_answer_fragment_text);
- mText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "Text Button Clicked");
- onText();
- }
- });
- return group;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // Intercept all touch events for full screen swiping gesture.
- InCallActivity activity = (InCallActivity) getActivity();
- activity.setDispatchTouchEventListener(mTouchListener);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- InCallActivity activity = (InCallActivity) getActivity();
- activity.setDispatchTouchEventListener(null);
- }
-
- private class TouchListener implements View.OnTouchListener {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return mGestureDetector.onTouchEvent(event);
- }
- }
-
- private boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- if (hasPendingDialogs()) {
- return false;
- }
-
- float diffY = e2.getY() - e1.getY();
- float diffX = e2.getX() - e1.getX();
- if (Math.abs(diffX) > Math.abs(diffY)) {
- if (Math.abs(diffX) > SWIPE_THRESHOLD) {
- if (diffX > 0) {
- onSwipeRight();
- } else {
- onSwipeLeft();
- }
- }
- return true;
- } else if (Math.abs(diffY) > SWIPE_THRESHOLD) {
- if (diffY > 0) {
- onSwipeDown();
- } else {
- onSwipeUp();
- }
- return true;
- }
-
- return false;
- }
-
- private void onSwipeUp() {
- Log.d(TAG, "onSwipeUp");
- onText();
- }
-
- private void onSwipeDown() {
- Log.d(TAG, "onSwipeDown");
- }
-
- private void onSwipeLeft() {
- Log.d(TAG, "onSwipeLeft");
- onDecline(getContext());
- }
-
- private void onSwipeRight() {
- Log.d(TAG, "onSwipeRight");
- onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
- }
-}
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
deleted file mode 100644
index 44ddfcd49..000000000
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2013 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.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ListView;
-
-import com.android.dialer.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Provides only common interface and functions. Should be derived to implement the actual UI.
- */
-public abstract class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresenter.AnswerUi>
- implements AnswerPresenter.AnswerUi {
-
- public static final int TARGET_SET_FOR_AUDIO_WITHOUT_SMS = 0;
- public static final int TARGET_SET_FOR_AUDIO_WITH_SMS = 1;
- public static final int TARGET_SET_FOR_VIDEO_WITHOUT_SMS = 2;
- public static final int TARGET_SET_FOR_VIDEO_WITH_SMS = 3;
- public static final int TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST = 4;
-
- /**
- * This fragment implement no UI at all. Derived class should do it.
- */
- @Override
- public abstract View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState);
-
- /**
- * The popup showing the list of canned responses.
- *
- * This is an AlertDialog containing a ListView showing the possible choices. This may be null
- * if the InCallScreen hasn't ever called showRespondViaSmsPopup() yet, or if the popup was
- * visible once but then got dismissed.
- */
- private Dialog mCannedResponsePopup = null;
-
- /**
- * The popup showing a text field for users to type in their custom message.
- */
- private AlertDialog mCustomMessagePopup = null;
-
- private ArrayAdapter<String> mSmsResponsesAdapter;
-
- private final List<String> mSmsResponses = new ArrayList<>();
-
- @Override
- public AnswerPresenter createPresenter() {
- return InCallPresenter.getInstance().getAnswerPresenter();
- }
-
- @Override
- public AnswerPresenter.AnswerUi getUi() {
- return this;
- }
-
- @Override
- public void showMessageDialog() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-
- mSmsResponsesAdapter = new ArrayAdapter<>(builder.getContext(),
- android.R.layout.simple_list_item_1, android.R.id.text1, mSmsResponses);
-
- final ListView lv = new ListView(getActivity());
- lv.setAdapter(mSmsResponsesAdapter);
- lv.setOnItemClickListener(new RespondViaSmsItemClickListener());
-
- builder.setCancelable(true).setView(lv).setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialogInterface) {
- onMessageDialogCancel();
- dismissCannedResponsePopup();
- getPresenter().onDismissDialog();
- }
- });
- mCannedResponsePopup = builder.create();
- mCannedResponsePopup.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- mCannedResponsePopup.show();
- }
-
- private boolean isCannedResponsePopupShowing() {
- if (mCannedResponsePopup != null) {
- return mCannedResponsePopup.isShowing();
- }
- return false;
- }
-
- private boolean isCustomMessagePopupShowing() {
- if (mCustomMessagePopup != null) {
- return mCustomMessagePopup.isShowing();
- }
- return false;
- }
-
- /**
- * Dismiss the canned response list popup.
- *
- * This is safe to call even if the popup is already dismissed, and even if you never called
- * showRespondViaSmsPopup() in the first place.
- */
- protected void dismissCannedResponsePopup() {
- if (mCannedResponsePopup != null) {
- mCannedResponsePopup.dismiss(); // safe even if already dismissed
- mCannedResponsePopup = null;
- }
- }
-
- /**
- * Dismiss the custom compose message popup.
- */
- private void dismissCustomMessagePopup() {
- if (mCustomMessagePopup != null) {
- mCustomMessagePopup.dismiss();
- mCustomMessagePopup = null;
- }
- }
-
- public void dismissPendingDialogs() {
- if (isCannedResponsePopupShowing()) {
- dismissCannedResponsePopup();
- }
-
- if (isCustomMessagePopupShowing()) {
- dismissCustomMessagePopup();
- }
- }
-
- public boolean hasPendingDialogs() {
- return !(mCannedResponsePopup == null && mCustomMessagePopup == null);
- }
-
- /**
- * Shows the custom message entry dialog.
- */
- public void showCustomMessageDialog() {
- // Create an alert dialog containing an EditText
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- final EditText et = new EditText(builder.getContext());
- builder.setCancelable(true).setView(et)
- .setPositiveButton(R.string.custom_message_send,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The order is arranged in a way that the popup will be destroyed
- // when the InCallActivity is about to finish.
- final String textMessage = et.getText().toString().trim();
- dismissCustomMessagePopup();
- getPresenter().rejectCallWithMessage(textMessage);
- }
- })
- .setNegativeButton(R.string.custom_message_cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dismissCustomMessagePopup();
- getPresenter().onDismissDialog();
- }
- })
- .setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialogInterface) {
- dismissCustomMessagePopup();
- getPresenter().onDismissDialog();
- }
- })
- .setTitle(R.string.respond_via_sms_custom_message);
- mCustomMessagePopup = builder.create();
-
- // Enable/disable the send button based on whether there is a message in the EditText
- et.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- final Button sendButton = mCustomMessagePopup.getButton(
- DialogInterface.BUTTON_POSITIVE);
- sendButton.setEnabled(s != null && s.toString().trim().length() != 0);
- }
- });
-
- // Keyboard up, show the dialog
- mCustomMessagePopup.getWindow().setSoftInputMode(
- WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- mCustomMessagePopup.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- mCustomMessagePopup.show();
-
- // Send button starts out disabled
- final Button sendButton = mCustomMessagePopup.getButton(DialogInterface.BUTTON_POSITIVE);
- sendButton.setEnabled(false);
- }
-
- @Override
- public void configureMessageDialog(List<String> textResponses) {
- mSmsResponses.clear();
- mSmsResponses.addAll(textResponses);
- mSmsResponses.add(getResources().getString(
- R.string.respond_via_sms_custom_message));
- if (mSmsResponsesAdapter != null) {
- mSmsResponsesAdapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public Context getContext() {
- return getActivity();
- }
-
- public void onAnswer(int videoState, Context context) {
- Log.d(this, "onAnswer videoState=" + videoState + " context=" + context);
- getPresenter().onAnswer(videoState, context);
- }
-
- public void onDecline(Context context) {
- getPresenter().onDecline(context);
- }
-
- public void onDeclineUpgradeRequest(Context context) {
- InCallPresenter.getInstance().declineUpgradeRequest(context);
- }
-
- public void onText() {
- getPresenter().onText();
- }
-
- /**
- * OnItemClickListener for the "Respond via SMS" popup.
- */
- public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener {
-
- /**
- * Handles the user selecting an item from the popup.
- */
- @Override
- public void onItemClick(AdapterView<?> parent, // The ListView
- View view, // The TextView that was clicked
- int position, long id) {
- Log.d(this, "RespondViaSmsItemClickListener.onItemClick(" + position + ")...");
- final String message = (String) parent.getItemAtPosition(position);
- Log.v(this, "- message: '" + message + "'");
- dismissCannedResponsePopup();
-
- // The "Custom" choice is a special case.
- // (For now, it's guaranteed to be the last item.)
- if (position == (parent.getCount() - 1)) {
- // Show the custom message dialog
- showCustomMessageDialog();
- } else {
- getPresenter().rejectCallWithMessage(message);
- }
- }
- }
-
- public void onShowAnswerUi(boolean shown) {
- // Do Nothing
- }
-
- public void showTargets(int targetSet) {
- // Do Nothing
- }
-
- public void showTargets(int targetSet, int videoState) {
- // Do Nothing
- }
-
- protected void onMessageDialogCancel() {
- // Do nothing.
- }
-}
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
deleted file mode 100644
index 883b54fed..000000000
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-
-import com.android.dialer.compat.UserManagerCompat;
-import com.android.dialer.util.TelecomUtil;
-import com.android.incallui.InCallPresenter.InCallState;
-
-import java.util.List;
-
-/**
- * Presenter for the Incoming call widget. The {@link AnswerPresenter} handles the logic during
- * incoming calls. It is also in charge of responding to incoming calls, so there needs to be
- * an instance alive so that it can receive onIncomingCall callbacks.
- *
- * An instance of {@link AnswerPresenter} is created by InCallPresenter at startup, registers
- * for callbacks via InCallPresenter, and shows/hides the {@link AnswerFragment} via IncallActivity.
- *
- */
-public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
- implements CallList.CallUpdateListener, InCallPresenter.InCallUiListener,
- InCallPresenter.IncomingCallListener,
- CallList.Listener {
-
- private static final String TAG = AnswerPresenter.class.getSimpleName();
-
- private String mCallId;
- private Call mCall = null;
- private boolean mHasTextMessages = false;
-
- @Override
- public void onUiShowing(boolean showing) {
- if (showing) {
- CallList.getInstance().addListener(this);
- final CallList calls = CallList.getInstance();
- Call call;
- call = calls.getIncomingCall();
- if (call != null) {
- processIncomingCall(call);
- }
- call = calls.getVideoUpgradeRequestCall();
- Log.d(this, "getVideoUpgradeRequestCall call =" + call);
- if (call != null) {
- showAnswerUi(true);
- processVideoUpgradeRequestCall(call);
- }
- } else {
- CallList.getInstance().removeListener(this);
- // This is necessary because the activity can be destroyed while an incoming call exists.
- // This happens when back button is pressed while incoming call is still being shown.
- if (mCallId != null) {
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- }
- }
- }
-
- @Override
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
- Log.d(this, "onIncomingCall: " + this);
- Call modifyCall = CallList.getInstance().getVideoUpgradeRequestCall();
- if (modifyCall != null) {
- showAnswerUi(false);
- Log.d(this, "declining upgrade request id: ");
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- InCallPresenter.getInstance().declineUpgradeRequest();
- }
- if (!call.getId().equals(mCallId)) {
- // A new call is coming in.
- processIncomingCall(call);
- }
- }
-
- @Override
- public void onIncomingCall(Call call) {
- }
-
- @Override
- public void onCallListChange(CallList list) {
- }
-
- @Override
- public void onDisconnect(Call call) {
- // no-op
- }
-
- public void onSessionModificationStateChange(int sessionModificationState) {
- boolean isUpgradePending = sessionModificationState ==
- Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
-
- if (!isUpgradePending) {
- // Stop listening for updates.
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- showAnswerUi(false);
- }
- }
-
- @Override
- public void onLastForwardedNumberChange() {
- // no-op
- }
-
- @Override
- public void onChildNumberChange() {
- // no-op
- }
-
- private boolean isVideoUpgradePending(Call call) {
- return call.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
- }
-
- @Override
- public void onUpgradeToVideo(Call call) {
- Log.d(this, "onUpgradeToVideo: " + this + " call=" + call);
- showAnswerUi(true);
- boolean isUpgradePending = isVideoUpgradePending(call);
- InCallPresenter inCallPresenter = InCallPresenter.getInstance();
- if (isUpgradePending
- && inCallPresenter.getInCallState() == InCallPresenter.InCallState.INCOMING) {
- Log.d(this, "declining upgrade request");
- //If there is incoming call reject upgrade request
- inCallPresenter.declineUpgradeRequest(getUi().getContext());
- } else if (isUpgradePending) {
- Log.d(this, "process upgrade request as no MT call");
- processVideoUpgradeRequestCall(call);
- }
- }
-
- private void processIncomingCall(Call call) {
- mCallId = call.getId();
- mCall = call;
-
- // Listen for call updates for the current call.
- CallList.getInstance().addCallUpdateListener(mCallId, this);
-
- Log.d(TAG, "Showing incoming for call id: " + mCallId + " " + this);
- if (showAnswerUi(true)) {
- final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
- configureAnswerTargetsForSms(call, textMsgs);
- }
- }
-
- private boolean showAnswerUi(boolean show) {
- final InCallActivity activity = InCallPresenter.getInstance().getActivity();
- if (activity != null) {
- activity.showAnswerFragment(show);
- if (getUi() != null) {
- getUi().onShowAnswerUi(show);
- }
- return true;
- } else {
- return false;
- }
- }
-
- private void processVideoUpgradeRequestCall(Call call) {
- Log.d(this, " processVideoUpgradeRequestCall call=" + call);
- mCallId = call.getId();
- mCall = call;
-
- // Listen for call updates for the current call.
- CallList.getInstance().addCallUpdateListener(mCallId, this);
-
- final int currentVideoState = call.getVideoState();
- final int modifyToVideoState = call.getRequestedVideoState();
-
- if (currentVideoState == modifyToVideoState) {
- Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return.");
- return;
- }
-
- AnswerUi ui = getUi();
-
- if (ui == null) {
- Log.e(this, "Ui is null. Can't process upgrade request");
- return;
- }
- showAnswerUi(true);
- ui.showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST,
- modifyToVideoState);
- }
-
- private boolean isEnabled(int videoState, int mask) {
- return (videoState & mask) == mask;
- }
-
- @Override
- public void onCallChanged(Call call) {
- Log.d(this, "onCallStateChange() " + call + " " + this);
- if (call.getState() != Call.State.INCOMING) {
- boolean isUpgradePending = isVideoUpgradePending(call);
- if (!isUpgradePending) {
- // Stop listening for updates.
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- }
-
- final Call incall = CallList.getInstance().getIncomingCall();
- if (incall != null || isUpgradePending) {
- showAnswerUi(true);
- } else {
- showAnswerUi(false);
- }
-
- mHasTextMessages = false;
- } else if (!mHasTextMessages) {
- final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
- if (textMsgs != null) {
- configureAnswerTargetsForSms(call, textMsgs);
- }
- }
- }
-
- public void onAnswer(int videoState, Context context) {
- if (mCallId == null) {
- return;
- }
-
- if (mCall.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- Log.d(this, "onAnswer (upgradeCall) mCallId=" + mCallId + " videoState=" + videoState);
- InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context);
- } else {
- Log.d(this, "onAnswer (answerCall) mCallId=" + mCallId + " videoState=" + videoState);
- TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState);
- }
- }
-
- /**
- * TODO: We are using reject and decline interchangeably. We should settle on
- * reject since it seems to be more prevalent.
- */
- public void onDecline(Context context) {
- Log.d(this, "onDecline " + mCallId);
- if (mCall.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- InCallPresenter.getInstance().declineUpgradeRequest(context);
- } else {
- TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
- }
- }
-
- public void onText() {
- if (getUi() != null) {
- TelecomUtil.silenceRinger(getUi().getContext());
- getUi().showMessageDialog();
- }
- }
-
- public void rejectCallWithMessage(String message) {
- Log.d(this, "sendTextToDefaultActivity()...");
- TelecomAdapter.getInstance().rejectCall(mCall.getId(), true, message);
-
- onDismissDialog();
- }
-
- public void onDismissDialog() {
- InCallPresenter.getInstance().onDismissDialog();
- }
-
- private void configureAnswerTargetsForSms(Call call, List<String> textMsgs) {
- if (getUi() == null) {
- return;
- }
- mHasTextMessages = textMsgs != null;
- boolean withSms = UserManagerCompat.isUserUnlocked(getUi().getContext())
- && call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT)
- && mHasTextMessages;
-
- // Only present the user with the option to answer as a video call if the incoming call is
- // a bi-directional video call.
- if (VideoUtils.isBidirectionalVideoCall(call)) {
- if (withSms) {
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS);
- getUi().configureMessageDialog(textMsgs);
- } else {
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITHOUT_SMS);
- }
- } else {
- if (withSms) {
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_AUDIO_WITH_SMS);
- getUi().configureMessageDialog(textMsgs);
- } else {
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_AUDIO_WITHOUT_SMS);
- }
- }
- }
-
- interface AnswerUi extends Ui {
- public void onShowAnswerUi(boolean shown);
- public void showTargets(int targetSet);
- public void showTargets(int targetSet, int videoState);
- public void showMessageDialog();
- public void configureMessageDialog(List<String> textResponses);
- public Context getContext();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java
deleted file mode 100644
index ea56dd624..000000000
--- a/InCallUI/src/com/android/incallui/AudioModeProvider.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2013 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.telecom.CallAudioState;
-
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * Proxy class for getting and setting the audio mode.
- */
-public class AudioModeProvider {
-
- static final int AUDIO_MODE_INVALID = 0;
-
- private static AudioModeProvider sAudioModeProvider = new AudioModeProvider();
- private int mAudioMode = CallAudioState.ROUTE_EARPIECE;
- private boolean mMuted = false;
- private int mSupportedModes = CallAudioState.ROUTE_EARPIECE
- | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET
- | CallAudioState.ROUTE_SPEAKER;
- private final List<AudioModeListener> mListeners = Lists.newArrayList();
-
- public static AudioModeProvider getInstance() {
- return sAudioModeProvider;
- }
-
- public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {
- onAudioModeChange(route, isMuted);
- onSupportedAudioModeChange(supportedRouteMask);
- }
-
- public void onAudioModeChange(int newMode, boolean muted) {
- if (mAudioMode != newMode) {
- mAudioMode = newMode;
- for (AudioModeListener l : mListeners) {
- l.onAudioMode(mAudioMode);
- }
- }
-
- if (mMuted != muted) {
- mMuted = muted;
- for (AudioModeListener l : mListeners) {
- l.onMute(mMuted);
- }
- }
- }
-
- public void onSupportedAudioModeChange(int newModeMask) {
- mSupportedModes = newModeMask;
-
- for (AudioModeListener l : mListeners) {
- l.onSupportedAudioMode(mSupportedModes);
- }
- }
-
- public void addListener(AudioModeListener listener) {
- if (!mListeners.contains(listener)) {
- mListeners.add(listener);
- listener.onSupportedAudioMode(mSupportedModes);
- listener.onAudioMode(mAudioMode);
- listener.onMute(mMuted);
- }
- }
-
- public void removeListener(AudioModeListener listener) {
- if (mListeners.contains(listener)) {
- mListeners.remove(listener);
- }
- }
-
- public int getSupportedModes() {
- return mSupportedModes;
- }
-
- public int getAudioMode() {
- return mAudioMode;
- }
-
- public boolean getMute() {
- return mMuted;
- }
-
- /* package */ interface AudioModeListener {
- void onAudioMode(int newMode);
- void onMute(boolean muted);
- void onSupportedAudioMode(int modeMask);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/BaseFragment.java b/InCallUI/src/com/android/incallui/BaseFragment.java
deleted file mode 100644
index 58d991acd..000000000
--- a/InCallUI/src/com/android/incallui/BaseFragment.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2013 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.Activity;
-import android.app.Fragment;
-import android.os.Bundle;
-
-/**
- * Parent for all fragments that use Presenters and Ui design.
- */
-public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment {
-
- private static final String KEY_FRAGMENT_HIDDEN = "key_fragment_hidden";
-
- private T mPresenter;
-
- public abstract T createPresenter();
-
- public abstract U getUi();
-
- protected BaseFragment() {
- mPresenter = createPresenter();
- }
-
- /**
- * Presenter will be available after onActivityCreated().
- *
- * @return The presenter associated with this fragment.
- */
- public T getPresenter() {
- return mPresenter;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mPresenter.onUiReady(getUi());
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mPresenter.onRestoreInstanceState(savedInstanceState);
- if (savedInstanceState.getBoolean(KEY_FRAGMENT_HIDDEN)) {
- getFragmentManager().beginTransaction().hide(this).commit();
- }
- }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mPresenter.onUiDestroy(getUi());
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mPresenter.onSaveInstanceState(outState);
- outState.putBoolean(KEY_FRAGMENT_HIDDEN, isHidden());
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- ((FragmentDisplayManager) activity).onFragmentAttached(this);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
deleted file mode 100644
index 1ad37e01a..000000000
--- a/InCallUI/src/com/android/incallui/Call.java
+++ /dev/null
@@ -1,1023 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-import android.hardware.camera2.CameraCharacteristics;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Trace;
-import android.support.annotation.IntDef;
-import android.telecom.Call.Details;
-import android.telecom.Connection;
-import android.telecom.DisconnectCause;
-import android.telecom.GatewayInfo;
-import android.telecom.InCallService.VideoCall;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telecom.VideoProfile;
-import android.text.TextUtils;
-
-import com.android.contacts.common.CallUtil;
-import com.android.contacts.common.compat.CallSdkCompat;
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.contacts.common.compat.SdkVersionOverride;
-import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.dialer.util.IntentUtil;
-import com.android.incallui.util.TelecomCallUtil;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-
-/**
- * Describes a single call and its state.
- */
-@NeededForTesting
-public class Call {
-
- /**
- * Specifies whether a number is in the call history or not.
- * {@link #CALL_HISTORY_STATUS_UNKNOWN} means there is no result.
- */
- @IntDef({CALL_HISTORY_STATUS_UNKNOWN, CALL_HISTORY_STATUS_PRESENT,
- CALL_HISTORY_STATUS_NOT_PRESENT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface CallHistoryStatus {}
- public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
- public static final int CALL_HISTORY_STATUS_PRESENT = 1;
- public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
-
- /* Defines different states of this call */
- public static class State {
- public static final int INVALID = 0;
- public static final int NEW = 1; /* The call is new. */
- public static final int IDLE = 2; /* The call is idle. Nothing active */
- public static final int ACTIVE = 3; /* There is an active call */
- public static final int INCOMING = 4; /* A normal incoming phone call */
- public static final int CALL_WAITING = 5; /* Incoming call while another is active */
- public static final int DIALING = 6; /* An outgoing call during dial phase */
- public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
- public static final int ONHOLD = 8; /* An active phone call placed on hold */
- public static final int DISCONNECTING = 9; /* A call is being ended. */
- public static final int DISCONNECTED = 10; /* State after a call disconnects */
- public static final int CONFERENCED = 11; /* Call part of a conference call */
- public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
- public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
- public static final int BLOCKED = 14; /* The number was found on the block list */
-
-
- public static boolean isConnectingOrConnected(int state) {
- switch(state) {
- case ACTIVE:
- case INCOMING:
- case CALL_WAITING:
- case CONNECTING:
- case DIALING:
- case REDIALING:
- case ONHOLD:
- case CONFERENCED:
- return true;
- default:
- }
- return false;
- }
-
- public static boolean isDialing(int state) {
- return state == DIALING || state == REDIALING;
- }
-
- public static String toString(int state) {
- switch (state) {
- case INVALID:
- return "INVALID";
- case NEW:
- return "NEW";
- case IDLE:
- return "IDLE";
- case ACTIVE:
- return "ACTIVE";
- case INCOMING:
- return "INCOMING";
- case CALL_WAITING:
- return "CALL_WAITING";
- case DIALING:
- return "DIALING";
- case REDIALING:
- return "REDIALING";
- case ONHOLD:
- return "ONHOLD";
- case DISCONNECTING:
- return "DISCONNECTING";
- case DISCONNECTED:
- return "DISCONNECTED";
- case CONFERENCED:
- return "CONFERENCED";
- case SELECT_PHONE_ACCOUNT:
- return "SELECT_PHONE_ACCOUNT";
- case CONNECTING:
- return "CONNECTING";
- case BLOCKED:
- return "BLOCKED";
- default:
- return "UNKNOWN";
- }
- }
- }
-
- /**
- * Defines different states of session modify requests, which are used to upgrade to video, or
- * downgrade to audio.
- */
- public static class SessionModificationState {
- public static final int NO_REQUEST = 0;
- public static final int WAITING_FOR_RESPONSE = 1;
- public static final int REQUEST_FAILED = 2;
- public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3;
- public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4;
- public static final int REQUEST_REJECTED = 5;
- }
-
- public static class VideoSettings {
- public static final int CAMERA_DIRECTION_UNKNOWN = -1;
- public static final int CAMERA_DIRECTION_FRONT_FACING =
- CameraCharacteristics.LENS_FACING_FRONT;
- public static final int CAMERA_DIRECTION_BACK_FACING =
- CameraCharacteristics.LENS_FACING_BACK;
-
- private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
-
- /**
- * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
- * the video state of the call should be used to infer the camera direction.
- *
- * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
- * @see {@link CameraCharacteristics#LENS_FACING_BACK}
- */
- public void setCameraDir(int cameraDirection) {
- if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING
- || cameraDirection == CAMERA_DIRECTION_BACK_FACING) {
- mCameraDirection = cameraDirection;
- } else {
- mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
- }
- }
-
- /**
- * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
- * the video state of the call should be used to infer the camera direction.
- *
- * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
- * @see {@link CameraCharacteristics#LENS_FACING_BACK}
- */
- public int getCameraDir() {
- return mCameraDirection;
- }
-
- @Override
- public String toString() {
- return "(CameraDir:" + getCameraDir() + ")";
- }
- }
-
- /**
- * Tracks any state variables that is useful for logging. There is some amount of overlap with
- * existing call member variables, but this duplication helps to ensure that none of these
- * logging variables will interface with/and affect call logic.
- */
- public static class LogState {
-
- // Contact lookup type constants
- // Unknown lookup result (lookup not completed yet?)
- public static final int LOOKUP_UNKNOWN = 0;
- public static final int LOOKUP_NOT_FOUND = 1;
- public static final int LOOKUP_LOCAL_CONTACT = 2;
- public static final int LOOKUP_LOCAL_CACHE = 3;
- public static final int LOOKUP_REMOTE_CONTACT = 4;
- public static final int LOOKUP_EMERGENCY = 5;
- public static final int LOOKUP_VOICEMAIL = 6;
-
- // Call initiation type constants
- public static final int INITIATION_UNKNOWN = 0;
- public static final int INITIATION_INCOMING = 1;
- public static final int INITIATION_DIALPAD = 2;
- public static final int INITIATION_SPEED_DIAL = 3;
- public static final int INITIATION_REMOTE_DIRECTORY = 4;
- public static final int INITIATION_SMART_DIAL = 5;
- public static final int INITIATION_REGULAR_SEARCH = 6;
- public static final int INITIATION_CALL_LOG = 7;
- public static final int INITIATION_CALL_LOG_FILTER = 8;
- public static final int INITIATION_VOICEMAIL_LOG = 9;
- public static final int INITIATION_CALL_DETAILS = 10;
- public static final int INITIATION_QUICK_CONTACTS = 11;
- public static final int INITIATION_EXTERNAL = 12;
-
- public DisconnectCause disconnectCause;
- public boolean isIncoming = false;
- public int contactLookupResult = LOOKUP_UNKNOWN;
- public int callInitiationMethod = INITIATION_EXTERNAL;
- // If this was a conference call, the total number of calls involved in the conference.
- public int conferencedCalls = 0;
- public long duration = 0;
- public boolean isLogged = false;
-
- @Override
- public String toString() {
- return String.format(Locale.US, "["
- + "%s, " // DisconnectCause toString already describes the object type
- + "isIncoming: %s, "
- + "contactLookup: %s, "
- + "callInitiation: %s, "
- + "duration: %s"
- + "]",
- disconnectCause,
- isIncoming,
- lookupToString(contactLookupResult),
- initiationToString(callInitiationMethod),
- duration);
- }
-
- private static String lookupToString(int lookupType) {
- switch (lookupType) {
- case LOOKUP_LOCAL_CONTACT:
- return "Local";
- case LOOKUP_LOCAL_CACHE:
- return "Cache";
- case LOOKUP_REMOTE_CONTACT:
- return "Remote";
- case LOOKUP_EMERGENCY:
- return "Emergency";
- case LOOKUP_VOICEMAIL:
- return "Voicemail";
- default:
- return "Not found";
- }
- }
-
- private static String initiationToString(int initiationType) {
- switch (initiationType) {
- case INITIATION_INCOMING:
- return "Incoming";
- case INITIATION_DIALPAD:
- return "Dialpad";
- case INITIATION_SPEED_DIAL:
- return "Speed Dial";
- case INITIATION_REMOTE_DIRECTORY:
- return "Remote Directory";
- case INITIATION_SMART_DIAL:
- return "Smart Dial";
- case INITIATION_REGULAR_SEARCH:
- return "Regular Search";
- case INITIATION_CALL_LOG:
- return "Call Log";
- case INITIATION_CALL_LOG_FILTER:
- return "Call Log Filter";
- case INITIATION_VOICEMAIL_LOG:
- return "Voicemail Log";
- case INITIATION_CALL_DETAILS:
- return "Call Details";
- case INITIATION_QUICK_CONTACTS:
- return "Quick Contacts";
- default:
- return "Unknown";
- }
- }
- }
-
-
- private static final String ID_PREFIX = Call.class.getSimpleName() + "_";
- private static int sIdCounter = 0;
-
- private final android.telecom.Call.Callback mTelecomCallCallback =
- new android.telecom.Call.Callback() {
- @Override
- public void onStateChanged(android.telecom.Call call, int newState) {
- Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " newState="
- + newState);
- update();
- }
-
- @Override
- public void onParentChanged(android.telecom.Call call,
- android.telecom.Call newParent) {
- Log.d(this, "TelecomCallCallback onParentChanged call=" + call + " newParent="
- + newParent);
- update();
- }
-
- @Override
- public void onChildrenChanged(android.telecom.Call call,
- List<android.telecom.Call> children) {
- update();
- }
-
- @Override
- public void onDetailsChanged(android.telecom.Call call,
- android.telecom.Call.Details details) {
- Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " details="
- + details);
- update();
- }
-
- @Override
- public void onCannedTextResponsesLoaded(android.telecom.Call call,
- List<String> cannedTextResponses) {
- Log.d(this, "TelecomCallCallback onStateChanged call=" + call
- + " cannedTextResponses=" + cannedTextResponses);
- update();
- }
-
- @Override
- public void onPostDialWait(android.telecom.Call call,
- String remainingPostDialSequence) {
- Log.d(this, "TelecomCallCallback onStateChanged call=" + call
- + " remainingPostDialSequence=" + remainingPostDialSequence);
- update();
- }
-
- @Override
- public void onVideoCallChanged(android.telecom.Call call,
- VideoCall videoCall) {
- Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " videoCall="
- + videoCall);
- update();
- }
-
- @Override
- public void onCallDestroyed(android.telecom.Call call) {
- Log.d(this, "TelecomCallCallback onStateChanged call=" + call);
- call.unregisterCallback(this);
- }
-
- @Override
- public void onConferenceableCallsChanged(android.telecom.Call call,
- List<android.telecom.Call> conferenceableCalls) {
- update();
- }
- };
-
- private final android.telecom.Call mTelecomCall;
- private final LatencyReport mLatencyReport;
- private boolean mIsEmergencyCall;
- private Uri mHandle;
- private final String mId;
- private int mState = State.INVALID;
- private DisconnectCause mDisconnectCause;
- private int mSessionModificationState;
- private final List<String> mChildCallIds = new ArrayList<>();
- private final VideoSettings mVideoSettings = new VideoSettings();
- private int mVideoState;
-
- /**
- * mRequestedVideoState is used to store requested upgrade / downgrade video state
- */
- private int mRequestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
-
- private InCallVideoCallCallback mVideoCallCallback;
- private boolean mIsVideoCallCallbackRegistered;
- private String mChildNumber;
- private String mLastForwardedNumber;
- private String mCallSubject;
- private PhoneAccountHandle mPhoneAccountHandle;
- @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
- private boolean mIsSpam;
-
- /**
- * Indicates whether the phone account associated with this call supports specifying a call
- * subject.
- */
- private boolean mIsCallSubjectSupported;
-
- private long mTimeAddedMs;
-
- private final LogState mLogState = new LogState();
-
- /**
- * Used only to create mock calls for testing
- */
- @NeededForTesting
- Call(int state) {
- mTelecomCall = null;
- mLatencyReport = new LatencyReport();
- mId = ID_PREFIX + Integer.toString(sIdCounter++);
- setState(state);
- }
-
- /**
- * Creates a new instance of a {@link Call}. Registers a callback for
- * {@link android.telecom.Call} events.
- */
- public Call(android.telecom.Call telecomCall, LatencyReport latencyReport) {
- this(telecomCall, latencyReport, true /* registerCallback */);
- }
-
- /**
- * Creates a new instance of a {@link Call}. Optionally registers a callback for
- * {@link android.telecom.Call} events.
- *
- * Intended for use when creating a {@link Call} instance for use with the
- * {@link ContactInfoCache}, where we do not want to register callbacks for the new call.
- */
- public Call(android.telecom.Call telecomCall, LatencyReport latencyReport,
- boolean registerCallback) {
- mTelecomCall = telecomCall;
- mLatencyReport = latencyReport;
- mId = ID_PREFIX + Integer.toString(sIdCounter++);
-
- updateFromTelecomCall(registerCallback);
-
- if (registerCallback) {
- mTelecomCall.registerCallback(mTelecomCallCallback);
- }
-
- mTimeAddedMs = System.currentTimeMillis();
- }
-
- public android.telecom.Call getTelecomCall() {
- return mTelecomCall;
- }
-
- /**
- * @return video settings of the call, null if the call is not a video call.
- * @see VideoProfile
- */
- public VideoSettings getVideoSettings() {
- return mVideoSettings;
- }
-
- private void update() {
- Trace.beginSection("Update");
- int oldState = getState();
- // We want to potentially register a video call callback here.
- updateFromTelecomCall(true /* registerCallback */);
- if (oldState != getState() && getState() == Call.State.DISCONNECTED) {
- CallList.getInstance().onDisconnect(this);
- } else {
- CallList.getInstance().onUpdate(this);
- }
- Trace.endSection();
- }
-
- private void updateFromTelecomCall(boolean registerCallback) {
- Log.d(this, "updateFromTelecomCall: " + mTelecomCall.toString());
- final int translatedState = translateState(mTelecomCall.getState());
- if (mState != State.BLOCKED) {
- setState(translatedState);
- setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
- maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState());
- }
-
- if (registerCallback && mTelecomCall.getVideoCall() != null) {
- if (mVideoCallCallback == null) {
- mVideoCallCallback = new InCallVideoCallCallback(this);
- }
- mTelecomCall.getVideoCall().registerCallback(mVideoCallCallback);
- mIsVideoCallCallbackRegistered = true;
- }
-
- mChildCallIds.clear();
- final int numChildCalls = mTelecomCall.getChildren().size();
- for (int i = 0; i < numChildCalls; i++) {
- mChildCallIds.add(
- CallList.getInstance().getCallByTelecomCall(
- mTelecomCall.getChildren().get(i)).getId());
- }
-
- // The number of conferenced calls can change over the course of the call, so use the
- // maximum number of conferenced child calls as the metric for conference call usage.
- mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
-
- updateFromCallExtras(mTelecomCall.getDetails().getExtras());
-
- // If the handle of the call has changed, update state for the call determining if it is an
- // emergency call.
- Uri newHandle = mTelecomCall.getDetails().getHandle();
- if (!Objects.equals(mHandle, newHandle)) {
- mHandle = newHandle;
- updateEmergencyCallState();
- }
-
- // If the phone account handle of the call is set, cache capability bit indicating whether
- // the phone account supports call subjects.
- PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
- if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
- mPhoneAccountHandle = newPhoneAccountHandle;
-
- if (mPhoneAccountHandle != null) {
- TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
- PhoneAccount phoneAccount =
- TelecomManagerCompat.getPhoneAccount(mgr, mPhoneAccountHandle);
- if (phoneAccount != null) {
- mIsCallSubjectSupported = phoneAccount.hasCapabilities(
- PhoneAccount.CAPABILITY_CALL_SUBJECT);
- }
- }
- }
- }
-
- /**
- * Tests corruption of the {@code callExtras} bundle by calling {@link
- * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException}
- * will be thrown and caught by this function.
- *
- * @param callExtras the bundle to verify
- * @returns {@code true} if the bundle is corrupted, {@code false} otherwise.
- */
- protected boolean areCallExtrasCorrupted(Bundle callExtras) {
- /**
- * There's currently a bug in Telephony service (b/25613098) that could corrupt the
- * extras bundle, resulting in a IllegalArgumentException while validating data under
- * {@link Bundle#containsKey(String)}.
- */
- try {
- callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
- return false;
- } catch (IllegalArgumentException e) {
- Log.e(this, "CallExtras is corrupted, ignoring exception", e);
- return true;
- }
- }
-
- protected void updateFromCallExtras(Bundle callExtras) {
- if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
- /**
- * If the bundle is corrupted, abandon information update as a work around. These are
- * not critical for the dialer to function.
- */
- return;
- }
- // Check for a change in the child address and notify any listeners.
- if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
- String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
- if (!Objects.equals(childNumber, mChildNumber)) {
- mChildNumber = childNumber;
- CallList.getInstance().onChildNumberChange(this);
- }
- }
-
- // Last forwarded number comes in as an array of strings. We want to choose the
- // last item in the array. The forwarding numbers arrive independently of when the
- // call is originally set up, so we need to notify the the UI of the change.
- if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
- ArrayList<String> lastForwardedNumbers =
- callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
-
- if (lastForwardedNumbers != null) {
- String lastForwardedNumber = null;
- if (!lastForwardedNumbers.isEmpty()) {
- lastForwardedNumber = lastForwardedNumbers.get(
- lastForwardedNumbers.size() - 1);
- }
-
- if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
- mLastForwardedNumber = lastForwardedNumber;
- CallList.getInstance().onLastForwardedNumberChange(this);
- }
- }
- }
-
- // Call subject is present in the extras at the start of call, so we do not need to
- // notify any other listeners of this.
- if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
- String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
- if (!Objects.equals(mCallSubject, callSubject)) {
- mCallSubject = callSubject;
- }
- }
- }
-
- /**
- * Determines if a received upgrade to video request should be cancelled. This can happen if
- * another InCall UI responds to the upgrade to video request.
- *
- * @param newVideoState The new video state.
- */
- private void maybeCancelVideoUpgrade(int newVideoState) {
- boolean isVideoStateChanged = mVideoState != newVideoState;
-
- if (mSessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST
- && isVideoStateChanged) {
-
- Log.v(this, "maybeCancelVideoUpgrade : cancelling upgrade notification");
- setSessionModificationState(SessionModificationState.NO_REQUEST);
- }
- mVideoState = newVideoState;
- }
- private static int translateState(int state) {
- switch (state) {
- case android.telecom.Call.STATE_NEW:
- case android.telecom.Call.STATE_CONNECTING:
- return Call.State.CONNECTING;
- case android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT:
- return Call.State.SELECT_PHONE_ACCOUNT;
- case android.telecom.Call.STATE_DIALING:
- return Call.State.DIALING;
- case android.telecom.Call.STATE_RINGING:
- return Call.State.INCOMING;
- case android.telecom.Call.STATE_ACTIVE:
- return Call.State.ACTIVE;
- case android.telecom.Call.STATE_HOLDING:
- return Call.State.ONHOLD;
- case android.telecom.Call.STATE_DISCONNECTED:
- return Call.State.DISCONNECTED;
- case android.telecom.Call.STATE_DISCONNECTING:
- return Call.State.DISCONNECTING;
- default:
- return Call.State.INVALID;
- }
- }
-
- public String getId() {
- return mId;
- }
-
- public long getTimeAddedMs() {
- return mTimeAddedMs;
- }
-
- public String getNumber() {
- return TelecomCallUtil.getNumber(mTelecomCall);
- }
-
- public void blockCall() {
- mTelecomCall.reject(false, null);
- setState(State.BLOCKED);
- }
-
- public Uri getHandle() {
- return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
- }
-
- public boolean isEmergencyCall() {
- return mIsEmergencyCall;
- }
-
- public int getState() {
- if (mTelecomCall != null && mTelecomCall.getParent() != null) {
- return State.CONFERENCED;
- } else {
- return mState;
- }
- }
-
- public void setState(int state) {
- mState = state;
- if (mState == State.INCOMING) {
- mLogState.isIncoming = true;
- } else if (mState == State.DISCONNECTED) {
- mLogState.duration = getConnectTimeMillis() == 0 ?
- 0: System.currentTimeMillis() - getConnectTimeMillis();
- }
- }
-
- public int getNumberPresentation() {
- return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandlePresentation();
- }
-
- public int getCnapNamePresentation() {
- return mTelecomCall == null ? null
- : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
- }
-
- public String getCnapName() {
- return mTelecomCall == null ? null
- : getTelecomCall().getDetails().getCallerDisplayName();
- }
-
- public Bundle getIntentExtras() {
- return mTelecomCall.getDetails().getIntentExtras();
- }
-
- public Bundle getExtras() {
- return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
- }
-
- /**
- * @return The child number for the call, or {@code null} if none specified.
- */
- public String getChildNumber() {
- return mChildNumber;
- }
-
- /**
- * @return The last forwarded number for the call, or {@code null} if none specified.
- */
- public String getLastForwardedNumber() {
- return mLastForwardedNumber;
- }
-
- /**
- * @return The call subject, or {@code null} if none specified.
- */
- public String getCallSubject() {
- return mCallSubject;
- }
-
- /**
- * @return {@code true} if the call's phone account supports call subjects, {@code false}
- * otherwise.
- */
- public boolean isCallSubjectSupported() {
- return mIsCallSubjectSupported;
- }
-
- /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
- public DisconnectCause getDisconnectCause() {
- if (mState == State.DISCONNECTED || mState == State.IDLE) {
- return mDisconnectCause;
- }
-
- return new DisconnectCause(DisconnectCause.UNKNOWN);
- }
-
- public void setDisconnectCause(DisconnectCause disconnectCause) {
- mDisconnectCause = disconnectCause;
- mLogState.disconnectCause = mDisconnectCause;
- }
-
- /** Returns the possible text message responses. */
- public List<String> getCannedSmsResponses() {
- return mTelecomCall.getCannedTextResponses();
- }
-
- /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
- public boolean can(int capabilities) {
- int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
-
- if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
- // We allow you to merge if the capabilities allow it or if it is a call with
- // conferenceable calls.
- if (mTelecomCall.getConferenceableCalls().isEmpty() &&
- ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE
- & supportedCapabilities) == 0)) {
- // Cannot merge calls if there are no calls to merge with.
- return false;
- }
- capabilities &= ~android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE;
- }
- return (capabilities == (capabilities & mTelecomCall.getDetails().getCallCapabilities()));
- }
-
- public boolean hasProperty(int property) {
- return mTelecomCall.getDetails().hasProperty(property);
- }
-
- /** Gets the time when the call first became active. */
- public long getConnectTimeMillis() {
- return mTelecomCall.getDetails().getConnectTimeMillis();
- }
-
- public boolean isConferenceCall() {
- return hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE);
- }
-
- public GatewayInfo getGatewayInfo() {
- return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
- }
-
- public PhoneAccountHandle getAccountHandle() {
- return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
- }
-
- /**
- * @return The {@link VideoCall} instance associated with the {@link android.telecom.Call}.
- * Will return {@code null} until {@link #updateFromTelecomCall()} has registered a valid
- * callback on the {@link VideoCall}.
- */
- public VideoCall getVideoCall() {
- return mTelecomCall == null || !mIsVideoCallCallbackRegistered ? null
- : mTelecomCall.getVideoCall();
- }
-
- public List<String> getChildCallIds() {
- return mChildCallIds;
- }
-
- public String getParentId() {
- android.telecom.Call parentCall = mTelecomCall.getParent();
- if (parentCall != null) {
- return CallList.getInstance().getCallByTelecomCall(parentCall).getId();
- }
- return null;
- }
-
- public int getVideoState() {
- return mTelecomCall.getDetails().getVideoState();
- }
-
- public boolean isVideoCall(Context context) {
- return CallUtil.isVideoEnabled(context) &&
- VideoUtils.isVideoCall(getVideoState());
- }
-
- /**
- * Handles incoming session modification requests. Stores the pending video request and sets
- * the session modification state to
- * {@link SessionModificationState#RECEIVED_UPGRADE_TO_VIDEO_REQUEST} so that we can keep track
- * of the fact the request was received. Only upgrade requests require user confirmation and
- * will be handled by this method. The remote user can turn off their own camera without
- * confirmation.
- *
- * @param videoState The requested video state.
- */
- public void setRequestedVideoState(int videoState) {
- Log.d(this, "setRequestedVideoState - video state= " + videoState);
- if (videoState == getVideoState()) {
- mSessionModificationState = Call.SessionModificationState.NO_REQUEST;
- Log.w(this,"setRequestedVideoState - Clearing session modification state");
- return;
- }
-
- mSessionModificationState = Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
- mRequestedVideoState = videoState;
- CallList.getInstance().onUpgradeToVideo(this);
-
- Log.d(this, "setRequestedVideoState - mSessionModificationState="
- + mSessionModificationState + " video state= " + videoState);
- update();
- }
-
- /**
- * Set the session modification state. Used to keep track of pending video session modification
- * operations and to inform listeners of these changes.
- *
- * @param state the new session modification state.
- */
- public void setSessionModificationState(int state) {
- boolean hasChanged = mSessionModificationState != state;
- mSessionModificationState = state;
- Log.d(this, "setSessionModificationState " + state + " mSessionModificationState="
- + mSessionModificationState);
- if (hasChanged) {
- CallList.getInstance().onSessionModificationStateChange(this, state);
- }
- }
-
- /**
- * Determines if the call handle is an emergency number or not and caches the result to avoid
- * repeated calls to isEmergencyNumber.
- */
- private void updateEmergencyCallState() {
- mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
- }
-
- /**
- * Gets the video state which was requested via a session modification request.
- *
- * @return The video state.
- */
- public int getRequestedVideoState() {
- return mRequestedVideoState;
- }
-
- public static boolean areSame(Call call1, Call call2) {
- if (call1 == null && call2 == null) {
- return true;
- } else if (call1 == null || call2 == null) {
- return false;
- }
-
- // otherwise compare call Ids
- return call1.getId().equals(call2.getId());
- }
-
- public static boolean areSameNumber(Call call1, Call call2) {
- if (call1 == null && call2 == null) {
- return true;
- } else if (call1 == null || call2 == null) {
- return false;
- }
-
- // otherwise compare call Numbers
- return TextUtils.equals(call1.getNumber(), call2.getNumber());
- }
-
- /**
- * Gets the current video session modification state.
- *
- * @return The session modification state.
- */
- public int getSessionModificationState() {
- return mSessionModificationState;
- }
-
- public LogState getLogState() {
- return mLogState;
- }
-
- /**
- * Determines if the call is an external call.
- *
- * An external call is one which does not exist locally for the
- * {@link android.telecom.ConnectionService} it is associated with.
- *
- * External calls are only supported in N and higher.
- *
- * @return {@code true} if the call is an external call, {@code false} otherwise.
- */
- public boolean isExternalCall() {
- return CompatUtils.isNCompatible() &&
- hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
- }
-
- /**
- * Determines if the external call is pullable.
- *
- * An external call is one which does not exist locally for the
- * {@link android.telecom.ConnectionService} it is associated with. An external call may be
- * "pullable", which means that the user can request it be transferred to the current device.
- *
- * External calls are only supported in N and higher.
- *
- * @return {@code true} if the call is an external call, {@code false} otherwise.
- */
- public boolean isPullableExternalCall() {
- return CompatUtils.isNCompatible() &&
- (mTelecomCall.getDetails().getCallCapabilities()
- & CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL)
- == CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL;
- }
-
- /**
- * Logging utility methods
- */
- public void logCallInitiationType() {
- if (isExternalCall()) {
- return;
- }
-
- if (getState() == State.INCOMING) {
- getLogState().callInitiationMethod = LogState.INITIATION_INCOMING;
- } else if (getIntentExtras() != null) {
- getLogState().callInitiationMethod =
- getIntentExtras().getInt(IntentUtil.EXTRA_CALL_INITIATION_TYPE,
- LogState.INITIATION_EXTERNAL);
- }
- }
-
- @Override
- public String toString() {
- if (mTelecomCall == null) {
- // This should happen only in testing since otherwise we would never have a null
- // Telecom call.
- return String.valueOf(mId);
- }
-
- return String.format(Locale.US, "[%s, %s, %s, %s, children:%s, parent:%s, " +
- "conferenceable:%s, videoState:%s, mSessionModificationState:%d, VideoSettings:%s]",
- mId,
- State.toString(getState()),
- Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
- Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
- mChildCallIds,
- getParentId(),
- this.mTelecomCall.getConferenceableCalls(),
- VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
- mSessionModificationState,
- getVideoSettings());
- }
-
- public String toSimpleString() {
- return super.toString();
- }
-
- public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
- mCallHistoryStatus = callHistoryStatus;
- }
-
- @CallHistoryStatus
- public int getCallHistoryStatus() {
- return mCallHistoryStatus;
- }
-
- public void setSpam(boolean isSpam) {
- mIsSpam = isSpam;
- }
-
- public boolean isSpam() {
- return mIsSpam;
- }
-
- public LatencyReport getLatencyReport() {
- return mLatencyReport;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
deleted file mode 100644
index 6b633eaf3..000000000
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ /dev/null
@@ -1,819 +0,0 @@
-/*
- * Copyright (C) 2013 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 static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_CALL;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_AUDIO;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_COUNT;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_DIALPAD;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_DOWNGRADE_TO_AUDIO;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_HOLD;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MANAGE_VIDEO_CONFERENCE;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MERGE;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MUTE;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_PAUSE_VIDEO;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWAP;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWITCH_CAMERA;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_UPGRADE_TO_VIDEO;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.graphics.drawable.StateListDrawable;
-import android.os.Bundle;
-import android.telecom.CallAudioState;
-import android.util.SparseIntArray;
-import android.view.ContextThemeWrapper;
-import android.view.HapticFeedbackConstants;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.ImageButton;
-import android.widget.PopupMenu;
-import android.widget.PopupMenu.OnDismissListener;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-import com.android.dialer.R;
-
-/**
- * Fragment for call control buttons
- */
-public class CallButtonFragment
- extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi>
- implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
- View.OnClickListener {
-
- private int mButtonMaxVisible;
- // The button is currently visible in the UI
- private static final int BUTTON_VISIBLE = 1;
- // The button is hidden in the UI
- private static final int BUTTON_HIDDEN = 2;
- // The button has been collapsed into the overflow menu
- private static final int BUTTON_MENU = 3;
-
- public interface Buttons {
-
- public static final int BUTTON_AUDIO = 0;
- public static final int BUTTON_MUTE = 1;
- public static final int BUTTON_DIALPAD = 2;
- public static final int BUTTON_HOLD = 3;
- public static final int BUTTON_SWAP = 4;
- public static final int BUTTON_UPGRADE_TO_VIDEO = 5;
- public static final int BUTTON_SWITCH_CAMERA = 6;
- public static final int BUTTON_DOWNGRADE_TO_AUDIO = 7;
- public static final int BUTTON_ADD_CALL = 8;
- public static final int BUTTON_MERGE = 9;
- public static final int BUTTON_PAUSE_VIDEO = 10;
- public static final int BUTTON_MANAGE_VIDEO_CONFERENCE = 11;
- public static final int BUTTON_COUNT = 12;
- }
-
- private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT);
-
- private CompoundButton mAudioButton;
- private CompoundButton mMuteButton;
- private CompoundButton mShowDialpadButton;
- private CompoundButton mHoldButton;
- private ImageButton mSwapButton;
- private ImageButton mChangeToVideoButton;
- private ImageButton mChangeToVoiceButton;
- private CompoundButton mSwitchCameraButton;
- private ImageButton mAddCallButton;
- private ImageButton mMergeButton;
- private CompoundButton mPauseVideoButton;
- private ImageButton mOverflowButton;
- private ImageButton mManageVideoCallConferenceButton;
-
- private PopupMenu mAudioModePopup;
- private boolean mAudioModePopupVisible;
- private PopupMenu mOverflowPopup;
-
- private int mPrevAudioMode = 0;
-
- // Constants for Drawable.setAlpha()
- private static final int HIDDEN = 0;
- private static final int VISIBLE = 255;
-
- private boolean mIsEnabled;
- private MaterialPalette mCurrentThemeColors;
-
- @Override
- public CallButtonPresenter createPresenter() {
- // TODO: find a cleaner way to include audio mode provider than having a singleton instance.
- return new CallButtonPresenter();
- }
-
- @Override
- public CallButtonPresenter.CallButtonUi getUi() {
- return this;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- for (int i = 0; i < BUTTON_COUNT; i++) {
- mButtonVisibilityMap.put(i, BUTTON_HIDDEN);
- }
-
- mButtonMaxVisible = getResources().getInteger(R.integer.call_card_max_buttons);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View parent = inflater.inflate(R.layout.call_button_fragment, container, false);
-
- mAudioButton = (CompoundButton) parent.findViewById(R.id.audioButton);
- mAudioButton.setOnClickListener(this);
- mMuteButton = (CompoundButton) parent.findViewById(R.id.muteButton);
- mMuteButton.setOnClickListener(this);
- mShowDialpadButton = (CompoundButton) parent.findViewById(R.id.dialpadButton);
- mShowDialpadButton.setOnClickListener(this);
- mHoldButton = (CompoundButton) parent.findViewById(R.id.holdButton);
- mHoldButton.setOnClickListener(this);
- mSwapButton = (ImageButton) parent.findViewById(R.id.swapButton);
- mSwapButton.setOnClickListener(this);
- mChangeToVideoButton = (ImageButton) parent.findViewById(R.id.changeToVideoButton);
- mChangeToVideoButton.setOnClickListener(this);
- mChangeToVoiceButton = (ImageButton) parent.findViewById(R.id.changeToVoiceButton);
- mChangeToVoiceButton.setOnClickListener(this);
- mSwitchCameraButton = (CompoundButton) parent.findViewById(R.id.switchCameraButton);
- mSwitchCameraButton.setOnClickListener(this);
- mAddCallButton = (ImageButton) parent.findViewById(R.id.addButton);
- mAddCallButton.setOnClickListener(this);
- mMergeButton = (ImageButton) parent.findViewById(R.id.mergeButton);
- mMergeButton.setOnClickListener(this);
- mPauseVideoButton = (CompoundButton) parent.findViewById(R.id.pauseVideoButton);
- mPauseVideoButton.setOnClickListener(this);
- mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
- mOverflowButton.setOnClickListener(this);
- mManageVideoCallConferenceButton = (ImageButton) parent.findViewById(
- R.id.manageVideoCallConferenceButton);
- mManageVideoCallConferenceButton.setOnClickListener(this);
- return parent;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- // set the buttons
- updateAudioButtons();
- }
-
- @Override
- public void onResume() {
- if (getPresenter() != null) {
- getPresenter().refreshMuteState();
- }
- super.onResume();
-
- updateColors();
- }
-
- @Override
- public void onClick(View view) {
- int id = view.getId();
- Log.d(this, "onClick(View " + view + ", id " + id + ")...");
-
- if (id == R.id.audioButton) {
- onAudioButtonClicked();
- } else if (id == R.id.addButton) {
- getPresenter().addCallClicked();
- } else if (id == R.id.muteButton) {
- getPresenter().muteClicked(!mMuteButton.isSelected());
- } else if (id == R.id.mergeButton) {
- getPresenter().mergeClicked();
- mMergeButton.setEnabled(false);
- } else if (id == R.id.holdButton) {
- getPresenter().holdClicked(!mHoldButton.isSelected());
- } else if (id == R.id.swapButton) {
- getPresenter().swapClicked();
- } else if (id == R.id.dialpadButton) {
- getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected());
- } else if (id == R.id.changeToVideoButton) {
- getPresenter().changeToVideoClicked();
- } else if (id == R.id.changeToVoiceButton) {
- getPresenter().changeToVoiceClicked();
- } else if (id == R.id.switchCameraButton) {
- getPresenter().switchCameraClicked(
- mSwitchCameraButton.isSelected() /* useFrontFacingCamera */);
- } else if (id == R.id.pauseVideoButton) {
- getPresenter().pauseVideoClicked(
- !mPauseVideoButton.isSelected() /* pause */);
- } else if (id == R.id.overflowButton) {
- if (mOverflowPopup != null) {
- mOverflowPopup.show();
- }
- } else if (id == R.id.manageVideoCallConferenceButton) {
- onManageVideoCallConferenceClicked();
- } else {
- Log.wtf(this, "onClick: unexpected");
- return;
- }
-
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
- }
-
- public void updateColors() {
- MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
-
- if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) {
- return;
- }
-
- View[] compoundButtons = {
- mAudioButton,
- mMuteButton,
- mShowDialpadButton,
- mHoldButton,
- mSwitchCameraButton,
- mPauseVideoButton
- };
-
- for (View button : compoundButtons) {
- final LayerDrawable layers = (LayerDrawable) button.getBackground();
- final RippleDrawable btnCompoundDrawable = compoundBackgroundDrawable(themeColors);
- layers.setDrawableByLayerId(R.id.compoundBackgroundItem, btnCompoundDrawable);
- }
-
- ImageButton[] normalButtons = {
- mSwapButton,
- mChangeToVideoButton,
- mChangeToVoiceButton,
- mAddCallButton,
- mMergeButton,
- mOverflowButton
- };
-
- for (ImageButton button : normalButtons) {
- final LayerDrawable layers = (LayerDrawable) button.getBackground();
- final RippleDrawable btnDrawable = backgroundDrawable(themeColors);
- layers.setDrawableByLayerId(R.id.backgroundItem, btnDrawable);
- }
-
- mCurrentThemeColors = themeColors;
- }
-
- /**
- * Generate a RippleDrawable which will be the background for a compound button, i.e.
- * a button with pressed and unpressed states. The unpressed state will be the same color
- * as the rest of the call card, the pressed state will be the dark version of that color.
- */
- private RippleDrawable compoundBackgroundDrawable(MaterialPalette palette) {
- Resources res = getResources();
- ColorStateList rippleColor =
- ColorStateList.valueOf(res.getColor(R.color.incall_accent_color));
-
- StateListDrawable stateListDrawable = new StateListDrawable();
- addSelectedAndFocused(res, stateListDrawable);
- addFocused(res, stateListDrawable);
- addSelected(res, stateListDrawable, palette);
- addUnselected(res, stateListDrawable, palette);
-
- return new RippleDrawable(rippleColor, stateListDrawable, null);
- }
-
- /**
- * Generate a RippleDrawable which will be the background of a button to ensure it
- * is the same color as the rest of the call card.
- */
- private RippleDrawable backgroundDrawable(MaterialPalette palette) {
- Resources res = getResources();
- ColorStateList rippleColor =
- ColorStateList.valueOf(res.getColor(R.color.incall_accent_color));
-
- StateListDrawable stateListDrawable = new StateListDrawable();
- addFocused(res, stateListDrawable);
- addUnselected(res, stateListDrawable, palette);
-
- return new RippleDrawable(rippleColor, stateListDrawable, null);
- }
-
- // state_selected and state_focused
- private void addSelectedAndFocused(Resources res, StateListDrawable drawable) {
- int[] selectedAndFocused = {android.R.attr.state_selected, android.R.attr.state_focused};
- Drawable selectedAndFocusedDrawable = res.getDrawable(R.drawable.btn_selected_focused);
- drawable.addState(selectedAndFocused, selectedAndFocusedDrawable);
- }
-
- // state_focused
- private void addFocused(Resources res, StateListDrawable drawable) {
- int[] focused = {android.R.attr.state_focused};
- Drawable focusedDrawable = res.getDrawable(R.drawable.btn_unselected_focused);
- drawable.addState(focused, focusedDrawable);
- }
-
- // state_selected
- private void addSelected(Resources res, StateListDrawable drawable, MaterialPalette palette) {
- int[] selected = {android.R.attr.state_selected};
- LayerDrawable selectedDrawable = (LayerDrawable) res.getDrawable(R.drawable.btn_selected);
- ((GradientDrawable) selectedDrawable.getDrawable(0)).setColor(palette.mSecondaryColor);
- drawable.addState(selected, selectedDrawable);
- }
-
- // default
- private void addUnselected(Resources res, StateListDrawable drawable, MaterialPalette palette) {
- LayerDrawable unselectedDrawable =
- (LayerDrawable) res.getDrawable(R.drawable.btn_unselected);
- ((GradientDrawable) unselectedDrawable.getDrawable(0)).setColor(palette.mPrimaryColor);
- drawable.addState(new int[0], unselectedDrawable);
- }
-
- @Override
- public void setEnabled(boolean isEnabled) {
- mIsEnabled = isEnabled;
-
- mAudioButton.setEnabled(isEnabled);
- mMuteButton.setEnabled(isEnabled);
- mShowDialpadButton.setEnabled(isEnabled);
- mHoldButton.setEnabled(isEnabled);
- mSwapButton.setEnabled(isEnabled);
- mChangeToVideoButton.setEnabled(isEnabled);
- mChangeToVoiceButton.setEnabled(isEnabled);
- mSwitchCameraButton.setEnabled(isEnabled);
- mAddCallButton.setEnabled(isEnabled);
- mMergeButton.setEnabled(isEnabled);
- mPauseVideoButton.setEnabled(isEnabled);
- mOverflowButton.setEnabled(isEnabled);
- mManageVideoCallConferenceButton.setEnabled(isEnabled);
- }
-
- @Override
- public void showButton(int buttonId, boolean show) {
- mButtonVisibilityMap.put(buttonId, show ? BUTTON_VISIBLE : BUTTON_HIDDEN);
- }
-
- @Override
- public void enableButton(int buttonId, boolean enable) {
- final View button = getButtonById(buttonId);
- if (button != null) {
- button.setEnabled(enable);
- }
- }
-
- private View getButtonById(int id) {
- if (id == BUTTON_AUDIO) {
- return mAudioButton;
- } else if (id == BUTTON_MUTE) {
- return mMuteButton;
- } else if (id == BUTTON_DIALPAD) {
- return mShowDialpadButton;
- } else if (id == BUTTON_HOLD) {
- return mHoldButton;
- } else if (id == BUTTON_SWAP) {
- return mSwapButton;
- } else if (id == BUTTON_UPGRADE_TO_VIDEO) {
- return mChangeToVideoButton;
- } else if (id == BUTTON_DOWNGRADE_TO_AUDIO) {
- return mChangeToVoiceButton;
- } else if (id == BUTTON_SWITCH_CAMERA) {
- return mSwitchCameraButton;
- } else if (id == BUTTON_ADD_CALL) {
- return mAddCallButton;
- } else if (id == BUTTON_MERGE) {
- return mMergeButton;
- } else if (id == BUTTON_PAUSE_VIDEO) {
- return mPauseVideoButton;
- } else if (id == BUTTON_MANAGE_VIDEO_CONFERENCE) {
- return mManageVideoCallConferenceButton;
- } else {
- Log.w(this, "Invalid button id");
- return null;
- }
- }
-
- @Override
- public void setHold(boolean value) {
- if (mHoldButton.isSelected() != value) {
- mHoldButton.setSelected(value);
- mHoldButton.setContentDescription(getContext().getString(
- value ? R.string.onscreenHoldText_selected
- : R.string.onscreenHoldText_unselected));
- }
- }
-
- @Override
- public void setCameraSwitched(boolean isBackFacingCamera) {
- mSwitchCameraButton.setSelected(isBackFacingCamera);
- }
-
- @Override
- public void setVideoPaused(boolean isVideoPaused) {
- mPauseVideoButton.setSelected(isVideoPaused);
-
- if (isVideoPaused) {
- mPauseVideoButton.setContentDescription(getText(R.string.onscreenTurnOnCameraText));
- } else {
- mPauseVideoButton.setContentDescription(getText(R.string.onscreenTurnOffCameraText));
- }
- }
-
- @Override
- public void setMute(boolean value) {
- if (mMuteButton.isSelected() != value) {
- mMuteButton.setSelected(value);
- mMuteButton.setContentDescription(getContext().getString(
- value ? R.string.onscreenMuteText_selected
- : R.string.onscreenMuteText_unselected));
- }
- }
-
- private void addToOverflowMenu(int id, View button, PopupMenu menu) {
- button.setVisibility(View.GONE);
- menu.getMenu().add(Menu.NONE, id, Menu.NONE, button.getContentDescription());
- mButtonVisibilityMap.put(id, BUTTON_MENU);
- }
-
- private PopupMenu getPopupMenu() {
- return new PopupMenu(new ContextThemeWrapper(getActivity(), R.style.InCallPopupMenuStyle),
- mOverflowButton);
- }
-
- /**
- * Iterates through the list of buttons and toggles their visibility depending on the
- * setting configured by the CallButtonPresenter. If there are more visible buttons than
- * the allowed maximum, the excess buttons are collapsed into a single overflow menu.
- */
- @Override
- public void updateButtonStates() {
- View prevVisibleButton = null;
- int prevVisibleId = -1;
- PopupMenu menu = null;
- int visibleCount = 0;
- for (int i = 0; i < BUTTON_COUNT; i++) {
- final int visibility = mButtonVisibilityMap.get(i);
- final View button = getButtonById(i);
- if (visibility == BUTTON_VISIBLE) {
- visibleCount++;
- if (visibleCount <= mButtonMaxVisible) {
- button.setVisibility(View.VISIBLE);
- prevVisibleButton = button;
- prevVisibleId = i;
- } else {
- if (menu == null) {
- menu = getPopupMenu();
- }
- // Collapse the current button into the overflow menu. If is the first visible
- // button that exceeds the threshold, also collapse the previous visible button
- // so that the total number of visible buttons will never exceed the threshold.
- if (prevVisibleButton != null) {
- addToOverflowMenu(prevVisibleId, prevVisibleButton, menu);
- prevVisibleButton = null;
- prevVisibleId = -1;
- }
- addToOverflowMenu(i, button, menu);
- }
- } else if (visibility == BUTTON_HIDDEN) {
- button.setVisibility(View.GONE);
- }
- }
-
- mOverflowButton.setVisibility(menu != null ? View.VISIBLE : View.GONE);
- if (menu != null) {
- mOverflowPopup = menu;
- mOverflowPopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- final int id = item.getItemId();
- getButtonById(id).performClick();
- return true;
- }
- });
- }
- }
-
- @Override
- public void setAudio(int mode) {
- updateAudioButtons();
- refreshAudioModePopup();
-
- if (mPrevAudioMode != mode) {
- updateAudioButtonContentDescription(mode);
- mPrevAudioMode = mode;
- }
- }
-
- @Override
- public void setSupportedAudio(int modeMask) {
- updateAudioButtons();
- refreshAudioModePopup();
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- Log.d(this, "- onMenuItemClick: " + item);
- Log.d(this, " id: " + item.getItemId());
- Log.d(this, " title: '" + item.getTitle() + "'");
-
- int mode = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
- int resId = item.getItemId();
-
- if (resId == R.id.audio_mode_speaker) {
- mode = CallAudioState.ROUTE_SPEAKER;
- } else if (resId == R.id.audio_mode_earpiece || resId == R.id.audio_mode_wired_headset) {
- // InCallCallAudioState.ROUTE_EARPIECE means either the handset earpiece,
- // or the wired headset (if connected.)
- mode = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
- } else if (resId == R.id.audio_mode_bluetooth) {
- mode = CallAudioState.ROUTE_BLUETOOTH;
- } else {
- Log.e(this, "onMenuItemClick: unexpected View ID " + item.getItemId()
- + " (MenuItem = '" + item + "')");
- }
-
- getPresenter().setAudioMode(mode);
-
- return true;
- }
-
- // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
- // This gets called when the PopupMenu gets dismissed for *any* reason, like
- // the user tapping outside its bounds, or pressing Back, or selecting one
- // of the menu items.
- @Override
- public void onDismiss(PopupMenu menu) {
- Log.d(this, "- onDismiss: " + menu);
- mAudioModePopupVisible = false;
- updateAudioButtons();
- }
-
- /**
- * Checks for supporting modes. If bluetooth is supported, it uses the audio
- * pop up menu. Otherwise, it toggles the speakerphone.
- */
- private void onAudioButtonClicked() {
- Log.d(this, "onAudioButtonClicked: " +
- CallAudioState.audioRouteToString(getPresenter().getSupportedAudio()));
-
- if (isSupported(CallAudioState.ROUTE_BLUETOOTH)) {
- showAudioModePopup();
- } else {
- getPresenter().toggleSpeakerphone();
- }
- }
-
- private void onManageVideoCallConferenceClicked() {
- Log.d(this, "onManageVideoCallConferenceClicked");
- InCallPresenter.getInstance().showConferenceCallManager(true);
- }
-
- /**
- * Refreshes the "Audio mode" popup if it's visible. This is useful
- * (for example) when a wired headset is plugged or unplugged,
- * since we need to switch back and forth between the "earpiece"
- * and "wired headset" items.
- *
- * This is safe to call even if the popup is already dismissed, or even if
- * you never called showAudioModePopup() in the first place.
- */
- public void refreshAudioModePopup() {
- if (mAudioModePopup != null && mAudioModePopupVisible) {
- // Dismiss the previous one
- mAudioModePopup.dismiss(); // safe even if already dismissed
- // And bring up a fresh PopupMenu
- showAudioModePopup();
- }
- }
-
- /**
- * Updates the audio button so that the appriopriate visual layers
- * are visible based on the supported audio formats.
- */
- private void updateAudioButtons() {
- final boolean bluetoothSupported = isSupported(CallAudioState.ROUTE_BLUETOOTH);
- final boolean speakerSupported = isSupported(CallAudioState.ROUTE_SPEAKER);
-
- boolean audioButtonEnabled = false;
- boolean audioButtonChecked = false;
- boolean showMoreIndicator = false;
-
- boolean showBluetoothIcon = false;
- boolean showSpeakerphoneIcon = false;
- boolean showHandsetIcon = false;
-
- boolean showToggleIndicator = false;
-
- if (bluetoothSupported) {
- Log.d(this, "updateAudioButtons - popup menu mode");
-
- audioButtonEnabled = true;
- audioButtonChecked = true;
- showMoreIndicator = true;
-
- // Update desired layers:
- if (isAudio(CallAudioState.ROUTE_BLUETOOTH)) {
- showBluetoothIcon = true;
- } else if (isAudio(CallAudioState.ROUTE_SPEAKER)) {
- showSpeakerphoneIcon = true;
- } else {
- showHandsetIcon = true;
- // TODO: if a wired headset is plugged in, that takes precedence
- // over the handset earpiece. If so, maybe we should show some
- // sort of "wired headset" icon here instead of the "handset
- // earpiece" icon. (Still need an asset for that, though.)
- }
-
- // The audio button is NOT a toggle in this state, so set selected to false.
- mAudioButton.setSelected(false);
- } else if (speakerSupported) {
- Log.d(this, "updateAudioButtons - speaker toggle mode");
-
- audioButtonEnabled = true;
-
- // The audio button *is* a toggle in this state, and indicated the
- // current state of the speakerphone.
- audioButtonChecked = isAudio(CallAudioState.ROUTE_SPEAKER);
- mAudioButton.setSelected(audioButtonChecked);
-
- // update desired layers:
- showToggleIndicator = true;
- showSpeakerphoneIcon = true;
- } else {
- Log.d(this, "updateAudioButtons - disabled...");
-
- // The audio button is a toggle in this state, but that's mostly
- // irrelevant since it's always disabled and unchecked.
- audioButtonEnabled = false;
- audioButtonChecked = false;
- mAudioButton.setSelected(false);
-
- // update desired layers:
- showToggleIndicator = true;
- showSpeakerphoneIcon = true;
- }
-
- // Finally, update it all!
-
- Log.v(this, "audioButtonEnabled: " + audioButtonEnabled);
- Log.v(this, "audioButtonChecked: " + audioButtonChecked);
- Log.v(this, "showMoreIndicator: " + showMoreIndicator);
- Log.v(this, "showBluetoothIcon: " + showBluetoothIcon);
- Log.v(this, "showSpeakerphoneIcon: " + showSpeakerphoneIcon);
- Log.v(this, "showHandsetIcon: " + showHandsetIcon);
-
- // Only enable the audio button if the fragment is enabled.
- mAudioButton.setEnabled(audioButtonEnabled && mIsEnabled);
- mAudioButton.setChecked(audioButtonChecked);
-
- final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
- Log.d(this, "'layers' drawable: " + layers);
-
- layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
- .setAlpha(showToggleIndicator ? VISIBLE : HIDDEN);
-
- layers.findDrawableByLayerId(R.id.moreIndicatorItem)
- .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
-
- layers.findDrawableByLayerId(R.id.bluetoothItem)
- .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN);
-
- layers.findDrawableByLayerId(R.id.handsetItem)
- .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
-
- layers.findDrawableByLayerId(R.id.speakerphoneItem)
- .setAlpha(showSpeakerphoneIcon ? VISIBLE : HIDDEN);
-
- }
-
- /**
- * Update the content description of the audio button.
- */
- private void updateAudioButtonContentDescription(int mode) {
- int stringId = 0;
-
- // If bluetooth is not supported, the audio buttion will toggle, so use the label "speaker".
- // Otherwise, use the label of the currently selected audio mode.
- if (!isSupported(CallAudioState.ROUTE_BLUETOOTH)) {
- stringId = R.string.audio_mode_speaker;
- } else {
- switch (mode) {
- case CallAudioState.ROUTE_EARPIECE:
- stringId = R.string.audio_mode_earpiece;
- break;
- case CallAudioState.ROUTE_BLUETOOTH:
- stringId = R.string.audio_mode_bluetooth;
- break;
- case CallAudioState.ROUTE_WIRED_HEADSET:
- stringId = R.string.audio_mode_wired_headset;
- break;
- case CallAudioState.ROUTE_SPEAKER:
- stringId = R.string.audio_mode_speaker;
- break;
- }
- }
-
- if (stringId != 0) {
- mAudioButton.setContentDescription(getResources().getString(stringId));
- }
- }
-
- private void showAudioModePopup() {
- Log.d(this, "showAudioPopup()...");
-
- final ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(),
- R.style.InCallPopupMenuStyle);
- mAudioModePopup = new PopupMenu(contextWrapper, mAudioButton /* anchorView */);
- mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu,
- mAudioModePopup.getMenu());
- mAudioModePopup.setOnMenuItemClickListener(this);
- mAudioModePopup.setOnDismissListener(this);
-
- final Menu menu = mAudioModePopup.getMenu();
-
- // TODO: Still need to have the "currently active" audio mode come
- // up pre-selected (or focused?) with a blue highlight. Still
- // need exact visual design, and possibly framework support for this.
- // See comments below for the exact logic.
-
- final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker);
- speakerItem.setEnabled(isSupported(CallAudioState.ROUTE_SPEAKER));
- // TODO: Show speakerItem as initially "selected" if
- // speaker is on.
-
- // We display *either* "earpiece" or "wired headset", never both,
- // depending on whether a wired headset is physically plugged in.
- final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece);
- final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset);
-
- final boolean usingHeadset = isSupported(CallAudioState.ROUTE_WIRED_HEADSET);
- earpieceItem.setVisible(!usingHeadset);
- earpieceItem.setEnabled(!usingHeadset);
- wiredHeadsetItem.setVisible(usingHeadset);
- wiredHeadsetItem.setEnabled(usingHeadset);
- // TODO: Show the above item (either earpieceItem or wiredHeadsetItem)
- // as initially "selected" if speakerOn and
- // bluetoothIndicatorOn are both false.
-
- final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth);
- bluetoothItem.setEnabled(isSupported(CallAudioState.ROUTE_BLUETOOTH));
- // TODO: Show bluetoothItem as initially "selected" if
- // bluetoothIndicatorOn is true.
-
- mAudioModePopup.show();
-
- // Unfortunately we need to manually keep track of the popup menu's
- // visiblity, since PopupMenu doesn't have an isShowing() method like
- // Dialogs do.
- mAudioModePopupVisible = true;
- }
-
- private boolean isSupported(int mode) {
- return (mode == (getPresenter().getSupportedAudio() & mode));
- }
-
- private boolean isAudio(int mode) {
- return (mode == getPresenter().getAudioMode());
- }
-
- @Override
- public void displayDialpad(boolean value, boolean animate) {
- if (getActivity() != null && getActivity() instanceof InCallActivity) {
- boolean changed = ((InCallActivity) getActivity()).showDialpadFragment(value, animate);
- if (changed) {
- mShowDialpadButton.setSelected(value);
- mShowDialpadButton.setContentDescription(getContext().getString(
- value /* show */ ? R.string.onscreenShowDialpadText_unselected
- : R.string.onscreenShowDialpadText_selected));
- }
- }
- }
-
- @Override
- public boolean isDialpadVisible() {
- if (getActivity() != null && getActivity() instanceof InCallActivity) {
- return ((InCallActivity) getActivity()).isDialpadVisible();
- }
- return false;
- }
-
- @Override
- public Context getContext() {
- return getActivity();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
deleted file mode 100644
index defafda99..000000000
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Copyright (C) 2013 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 static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_CALL;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_AUDIO;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_DIALPAD;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_DOWNGRADE_TO_AUDIO;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_HOLD;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MERGE;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MUTE;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_PAUSE_VIDEO;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWAP;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWITCH_CAMERA;
-import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_UPGRADE_TO_VIDEO;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.telecom.CallAudioState;
-import android.telecom.InCallService.VideoCall;
-import android.telecom.VideoProfile;
-
-import com.android.contacts.common.compat.CallSdkCompat;
-import com.android.contacts.common.compat.SdkVersionOverride;
-import com.android.dialer.compat.UserManagerCompat;
-import com.android.incallui.AudioModeProvider.AudioModeListener;
-import com.android.incallui.InCallCameraManager.Listener;
-import com.android.incallui.InCallPresenter.CanAddCallListener;
-import com.android.incallui.InCallPresenter.InCallDetailsListener;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.InCallPresenter.InCallStateListener;
-import com.android.incallui.InCallPresenter.IncomingCallListener;
-
-/**
- * Logic for call buttons.
- */
-public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
- implements InCallStateListener, AudioModeListener, IncomingCallListener,
- InCallDetailsListener, CanAddCallListener, Listener {
-
- private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted";
- private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state";
-
- private Call mCall;
- private boolean mAutomaticallyMuted = false;
- private boolean mPreviousMuteState = false;
-
- public CallButtonPresenter() {
- }
-
- @Override
- public void onUiReady(CallButtonUi ui) {
- super.onUiReady(ui);
-
- AudioModeProvider.getInstance().addListener(this);
-
- // register for call state changes last
- final InCallPresenter inCallPresenter = InCallPresenter.getInstance();
- inCallPresenter.addListener(this);
- inCallPresenter.addIncomingCallListener(this);
- inCallPresenter.addDetailsListener(this);
- inCallPresenter.addCanAddCallListener(this);
- inCallPresenter.getInCallCameraManager().addCameraSelectionListener(this);
-
- // Update the buttons state immediately for the current call
- onStateChange(InCallState.NO_CALLS, inCallPresenter.getInCallState(),
- CallList.getInstance());
- }
-
- @Override
- public void onUiUnready(CallButtonUi ui) {
- super.onUiUnready(ui);
-
- InCallPresenter.getInstance().removeListener(this);
- AudioModeProvider.getInstance().removeListener(this);
- InCallPresenter.getInstance().removeIncomingCallListener(this);
- InCallPresenter.getInstance().removeDetailsListener(this);
- InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
- InCallPresenter.getInstance().removeCanAddCallListener(this);
- }
-
- @Override
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
- CallButtonUi ui = getUi();
-
- if (newState == InCallState.OUTGOING) {
- mCall = callList.getOutgoingCall();
- } else if (newState == InCallState.INCALL) {
- mCall = callList.getActiveOrBackgroundCall();
-
- // When connected to voice mail, automatically shows the dialpad.
- // (On previous releases we showed it when in-call shows up, before waiting for
- // OUTGOING. We may want to do that once we start showing "Voice mail" label on
- // the dialpad too.)
- if (ui != null) {
- if (oldState == InCallState.OUTGOING && mCall != null) {
- if (CallerInfoUtils.isVoiceMailNumber(ui.getContext(), mCall)) {
- ui.displayDialpad(true /* show */, true /* animate */);
- }
- }
- }
- } else if (newState == InCallState.INCOMING) {
- if (ui != null) {
- ui.displayDialpad(false /* show */, true /* animate */);
- }
- mCall = callList.getIncomingCall();
- } else {
- mCall = null;
- }
- updateUi(newState, mCall);
- }
-
- /**
- * Updates the user interface in response to a change in the details of a call.
- * Currently handles changes to the call buttons in response to a change in the details for a
- * call. This is important to ensure changes to the active call are reflected in the available
- * buttons.
- *
- * @param call The active call.
- * @param details The call details.
- */
- @Override
- public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
- // Only update if the changes are for the currently active call
- if (getUi() != null && call != null && call.equals(mCall)) {
- updateButtonsState(call);
- }
- }
-
- @Override
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
- onStateChange(oldState, newState, CallList.getInstance());
- }
-
- @Override
- public void onCanAddCallChanged(boolean canAddCall) {
- if (getUi() != null && mCall != null) {
- updateButtonsState(mCall);
- }
- }
-
- @Override
- public void onAudioMode(int mode) {
- if (getUi() != null) {
- getUi().setAudio(mode);
- }
- }
-
- @Override
- public void onSupportedAudioMode(int mask) {
- if (getUi() != null) {
- getUi().setSupportedAudio(mask);
- }
- }
-
- @Override
- public void onMute(boolean muted) {
- if (getUi() != null && !mAutomaticallyMuted) {
- getUi().setMute(muted);
- }
- }
-
- public int getAudioMode() {
- return AudioModeProvider.getInstance().getAudioMode();
- }
-
- public int getSupportedAudio() {
- return AudioModeProvider.getInstance().getSupportedModes();
- }
-
- public void setAudioMode(int mode) {
-
- // TODO: Set a intermediate state in this presenter until we get
- // an update for onAudioMode(). This will make UI response immediate
- // if it turns out to be slow
-
- Log.d(this, "Sending new Audio Mode: " + CallAudioState.audioRouteToString(mode));
- TelecomAdapter.getInstance().setAudioRoute(mode);
- }
-
- /**
- * Function assumes that bluetooth is not supported.
- */
- public void toggleSpeakerphone() {
- // this function should not be called if bluetooth is available
- if (0 != (CallAudioState.ROUTE_BLUETOOTH & getSupportedAudio())) {
-
- // It's clear the UI is wrong, so update the supported mode once again.
- Log.e(this, "toggling speakerphone not allowed when bluetooth supported.");
- getUi().setSupportedAudio(getSupportedAudio());
- return;
- }
-
- int newMode = CallAudioState.ROUTE_SPEAKER;
-
- // if speakerphone is already on, change to wired/earpiece
- if (getAudioMode() == CallAudioState.ROUTE_SPEAKER) {
- newMode = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
- }
-
- setAudioMode(newMode);
- }
-
- public void muteClicked(boolean checked) {
- Log.d(this, "turning on mute: " + checked);
- TelecomAdapter.getInstance().mute(checked);
- }
-
- public void holdClicked(boolean checked) {
- if (mCall == null) {
- return;
- }
- if (checked) {
- Log.i(this, "Putting the call on hold: " + mCall);
- TelecomAdapter.getInstance().holdCall(mCall.getId());
- } else {
- Log.i(this, "Removing the call from hold: " + mCall);
- TelecomAdapter.getInstance().unholdCall(mCall.getId());
- }
- }
-
- public void swapClicked() {
- if (mCall == null) {
- return;
- }
-
- Log.i(this, "Swapping the call: " + mCall);
- TelecomAdapter.getInstance().swap(mCall.getId());
- }
-
- public void mergeClicked() {
- TelecomAdapter.getInstance().merge(mCall.getId());
- }
-
- public void addCallClicked() {
- // Automatically mute the current call
- mAutomaticallyMuted = true;
- mPreviousMuteState = AudioModeProvider.getInstance().getMute();
- // Simulate a click on the mute button
- muteClicked(true);
- TelecomAdapter.getInstance().addCall();
- }
-
- public void changeToVoiceClicked() {
- VideoCall videoCall = mCall.getVideoCall();
- if (videoCall == null) {
- return;
- }
-
- VideoProfile videoProfile = new VideoProfile(VideoProfile.STATE_AUDIO_ONLY);
- videoCall.sendSessionModifyRequest(videoProfile);
- }
-
- public void showDialpadClicked(boolean checked) {
- Log.v(this, "Show dialpad " + String.valueOf(checked));
- getUi().displayDialpad(checked /* show */, true /* animate */);
- }
-
- public void changeToVideoClicked() {
- VideoCall videoCall = mCall.getVideoCall();
- if (videoCall == null) {
- return;
- }
- int currVideoState = mCall.getVideoState();
- int currUnpausedVideoState = VideoUtils.getUnPausedVideoState(currVideoState);
- currUnpausedVideoState |= VideoProfile.STATE_BIDIRECTIONAL;
-
- VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState);
- videoCall.sendSessionModifyRequest(videoProfile);
- mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
- }
-
- /**
- * Switches the camera between the front-facing and back-facing camera.
- * @param useFrontFacingCamera True if we should switch to using the front-facing camera, or
- * false if we should switch to using the back-facing camera.
- */
- public void switchCameraClicked(boolean useFrontFacingCamera) {
- InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
- cameraManager.setUseFrontFacingCamera(useFrontFacingCamera);
-
- VideoCall videoCall = mCall.getVideoCall();
- if (videoCall == null) {
- return;
- }
-
- String cameraId = cameraManager.getActiveCameraId();
- if (cameraId != null) {
- final int cameraDir = cameraManager.isUsingFrontFacingCamera()
- ? Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING
- : Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING;
- mCall.getVideoSettings().setCameraDir(cameraDir);
- videoCall.setCamera(cameraId);
- videoCall.requestCameraCapabilities();
- }
- }
-
-
- /**
- * Stop or start client's video transmission.
- * @param pause True if pausing the local user's video, or false if starting the local user's
- * video.
- */
- public void pauseVideoClicked(boolean pause) {
- VideoCall videoCall = mCall.getVideoCall();
- if (videoCall == null) {
- return;
- }
-
- final int currUnpausedVideoState = VideoUtils.getUnPausedVideoState(mCall.getVideoState());
- if (pause) {
- videoCall.setCamera(null);
- VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState
- & ~VideoProfile.STATE_TX_ENABLED);
- videoCall.sendSessionModifyRequest(videoProfile);
- } else {
- InCallCameraManager cameraManager = InCallPresenter.getInstance().
- getInCallCameraManager();
- videoCall.setCamera(cameraManager.getActiveCameraId());
- VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState
- | VideoProfile.STATE_TX_ENABLED);
- videoCall.sendSessionModifyRequest(videoProfile);
- mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
- }
- getUi().setVideoPaused(pause);
- }
-
- private void updateUi(InCallState state, Call call) {
- Log.d(this, "Updating call UI for call: ", call);
-
- final CallButtonUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- final boolean isEnabled =
- state.isConnectingOrConnected() &&!state.isIncoming() && call != null;
- ui.setEnabled(isEnabled);
-
- if (call == null) {
- return;
- }
-
- updateButtonsState(call);
- }
-
- /**
- * Updates the buttons applicable for the UI.
- *
- * @param call The active call.
- */
- private void updateButtonsState(Call call) {
- Log.v(this, "updateButtonsState");
- final CallButtonUi ui = getUi();
- final boolean isVideo = VideoUtils.isVideoCall(call);
-
- // Common functionality (audio, hold, etc).
- // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available:
- // (1) If the device normally can hold, show HOLD in a disabled state.
- // (2) If the device doesn't have the concept of hold/swap, remove the button.
- final boolean showSwap = call.can(
- android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
- final boolean showHold = !showSwap
- && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD)
- && call.can(android.telecom.Call.Details.CAPABILITY_HOLD);
- final boolean isCallOnHold = call.getState() == Call.State.ONHOLD;
-
- final boolean showAddCall = TelecomAdapter.getInstance().canAddCall()
- && UserManagerCompat.isUserUnlocked(ui.getContext());
- final boolean showMerge = call.can(
- android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
- final boolean showUpgradeToVideo = !isVideo && hasVideoCallCapabilities(call);
- final boolean showDowngradeToAudio = isVideo && isDowngradeToAudioSupported(call);
- final boolean showMute = call.can(android.telecom.Call.Details.CAPABILITY_MUTE);
-
- ui.showButton(BUTTON_AUDIO, true);
- ui.showButton(BUTTON_SWAP, showSwap);
- ui.showButton(BUTTON_HOLD, showHold);
- ui.setHold(isCallOnHold);
- ui.showButton(BUTTON_MUTE, showMute);
- ui.showButton(BUTTON_ADD_CALL, showAddCall);
- ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
- ui.showButton(BUTTON_DOWNGRADE_TO_AUDIO, showDowngradeToAudio);
- ui.showButton(BUTTON_SWITCH_CAMERA, isVideo);
- ui.showButton(BUTTON_PAUSE_VIDEO, isVideo);
- if (isVideo) {
- getUi().setVideoPaused(!VideoUtils.isTransmissionEnabled(call));
- }
- ui.showButton(BUTTON_DIALPAD, true);
- ui.showButton(BUTTON_MERGE, showMerge);
-
- ui.updateButtonStates();
- }
-
- private boolean hasVideoCallCapabilities(Call call) {
- if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) {
- return call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)
- && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX);
- }
- // In L, this single flag represents both video transmitting and receiving capabilities
- return call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX);
- }
-
- /**
- * Determines if downgrading from a video call to an audio-only call is supported. In order to
- * support downgrade to audio, the SDK version must be >= N and the call should NOT have the
- * {@link android.telecom.Call.Details#CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO}.
- * @param call The call.
- * @return {@code true} if downgrading to an audio-only call from a video call is supported.
- */
- private boolean isDowngradeToAudioSupported(Call call) {
- return !call.can(CallSdkCompat.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO);
- }
-
- public void refreshMuteState() {
- // Restore the previous mute state
- if (mAutomaticallyMuted &&
- AudioModeProvider.getInstance().getMute() != mPreviousMuteState) {
- if (getUi() == null) {
- return;
- }
- muteClicked(mPreviousMuteState);
- }
- mAutomaticallyMuted = false;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted);
- outState.putBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState);
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- mAutomaticallyMuted =
- savedInstanceState.getBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted);
- mPreviousMuteState =
- savedInstanceState.getBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState);
- super.onRestoreInstanceState(savedInstanceState);
- }
-
- public interface CallButtonUi extends Ui {
- void showButton(int buttonId, boolean show);
- void enableButton(int buttonId, boolean enable);
- void setEnabled(boolean on);
- void setMute(boolean on);
- void setHold(boolean on);
- void setCameraSwitched(boolean isBackFacingCamera);
- void setVideoPaused(boolean isPaused);
- void setAudio(int mode);
- void setSupportedAudio(int mask);
- void displayDialpad(boolean on, boolean animate);
- boolean isDialpadVisible();
-
- /**
- * Once showButton() has been called on each of the individual buttons in the UI, call
- * this to configure the overflow menu appropriately.
- */
- void updateButtonStates();
- Context getContext();
- }
-
- @Override
- public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) {
- if (getUi() == null) {
- return;
- }
- getUi().setCameraSwitched(!isUsingFrontFacingCamera);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
deleted file mode 100644
index c2022d18c..000000000
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ /dev/null
@@ -1,1510 +0,0 @@
-/*
- * Copyright (C) 2013 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.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.AnimationDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Trace;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
-import android.telecom.DisconnectCause;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-import com.android.contacts.common.widget.FloatingActionButtonController;
-import com.android.dialer.R;
-import com.android.phone.common.animation.AnimUtils;
-
-import java.util.List;
-
-/**
- * Fragment for call card.
- */
-public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
- implements CallCardPresenter.CallCardUi {
- private static final String TAG = "CallCardFragment";
-
- /**
- * Internal class which represents the call state label which is to be applied.
- */
- private class CallStateLabel {
- private CharSequence mCallStateLabel;
- private boolean mIsAutoDismissing;
-
- public CallStateLabel(CharSequence callStateLabel, boolean isAutoDismissing) {
- mCallStateLabel = callStateLabel;
- mIsAutoDismissing = isAutoDismissing;
- }
-
- public CharSequence getCallStateLabel() {
- return mCallStateLabel;
- }
-
- /**
- * Determines if the call state label should auto-dismiss.
- *
- * @return {@code true} if the call state label should auto-dismiss.
- */
- public boolean isAutoDismissing() {
- return mIsAutoDismissing;
- }
- };
-
- private static final String IS_DIALPAD_SHOWING_KEY = "is_dialpad_showing";
-
- /**
- * The duration of time (in milliseconds) a call state label should remain visible before
- * resetting to its previous value.
- */
- private static final long CALL_STATE_LABEL_RESET_DELAY_MS = 3000;
- /**
- * Amount of time to wait before sending an announcement via the accessibility manager.
- * When the call state changes to an outgoing or incoming state for the first time, the
- * UI can often be changing due to call updates or contact lookup. This allows the UI
- * to settle to a stable state to ensure that the correct information is announced.
- */
- private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS = 500;
-
- private AnimatorSet mAnimatorSet;
- private int mShrinkAnimationDuration;
- private int mFabNormalDiameter;
- private int mFabSmallDiameter;
- private boolean mIsLandscape;
- private boolean mHasLargePhoto;
- private boolean mIsDialpadShowing;
-
- // Primary caller info
- private TextView mPhoneNumber;
- private TextView mNumberLabel;
- private TextView mPrimaryName;
- private View mCallStateButton;
- private ImageView mCallStateIcon;
- private ImageView mCallStateVideoCallIcon;
- private TextView mCallStateLabel;
- private TextView mCallTypeLabel;
- private ImageView mHdAudioIcon;
- private ImageView mForwardIcon;
- private ImageView mSpamIcon;
- private View mCallNumberAndLabel;
- private TextView mElapsedTime;
- private Drawable mPrimaryPhotoDrawable;
- private TextView mCallSubject;
- private ImageView mWorkProfileIcon;
-
- // Container view that houses the entire primary call card, including the call buttons
- private View mPrimaryCallCardContainer;
- // Container view that houses the primary call information
- private ViewGroup mPrimaryCallInfo;
- private View mCallButtonsContainer;
- private ImageView mPhotoSmall;
-
- // Secondary caller info
- private View mSecondaryCallInfo;
- private TextView mSecondaryCallName;
- private View mSecondaryCallProviderInfo;
- private TextView mSecondaryCallProviderLabel;
- private View mSecondaryCallConferenceCallIcon;
- private View mSecondaryCallVideoCallIcon;
- private View mProgressSpinner;
-
- // Call card content
- private View mCallCardContent;
- private ImageView mPhotoLarge;
- private View mContactContext;
- private TextView mContactContextTitle;
- private ListView mContactContextListView;
- private LinearLayout mContactContextListHeaders;
-
- private View mManageConferenceCallButton;
-
- // Dark number info bar
- private TextView mInCallMessageLabel;
-
- private FloatingActionButtonController mFloatingActionButtonController;
- private View mFloatingActionButtonContainer;
- private ImageButton mFloatingActionButton;
- private int mFloatingActionButtonVerticalOffset;
-
- private float mTranslationOffset;
- private Animation mPulseAnimation;
-
- private int mVideoAnimationDuration;
- // Whether or not the call card is currently in the process of an animation
- private boolean mIsAnimating;
-
- private MaterialPalette mCurrentThemeColors;
-
- /**
- * Call state label to set when an auto-dismissing call state label is dismissed.
- */
- private CharSequence mPostResetCallStateLabel;
- private boolean mCallStateLabelResetPending = false;
- private Handler mHandler;
-
- /**
- * Determines if secondary call info is populated in the secondary call info UI.
- */
- private boolean mHasSecondaryCallInfo = false;
-
- @Override
- public CallCardPresenter.CallCardUi getUi() {
- return this;
- }
-
- @Override
- public CallCardPresenter createPresenter() {
- return new CallCardPresenter();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mHandler = new Handler(Looper.getMainLooper());
- mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration);
- mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
- mFloatingActionButtonVerticalOffset = getResources().getDimensionPixelOffset(
- R.dimen.floating_action_button_vertical_offset);
- mFabNormalDiameter = getResources().getDimensionPixelOffset(
- R.dimen.end_call_floating_action_button_diameter);
- mFabSmallDiameter = getResources().getDimensionPixelOffset(
- R.dimen.end_call_floating_action_button_small_diameter);
-
- if (savedInstanceState != null) {
- mIsDialpadShowing = savedInstanceState.getBoolean(IS_DIALPAD_SHOWING_KEY, false);
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final CallList calls = CallList.getInstance();
- final Call call = calls.getFirstCall();
- getPresenter().init(getActivity(), call);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(IS_DIALPAD_SHOWING_KEY, mIsDialpadShowing);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- Trace.beginSection(TAG + " onCreate");
- mTranslationOffset =
- getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset);
- final View view = inflater.inflate(R.layout.call_card_fragment, container, false);
- Trace.endSection();
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- mPulseAnimation =
- AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse);
-
- mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
- mPrimaryName = (TextView) view.findViewById(R.id.name);
- mNumberLabel = (TextView) view.findViewById(R.id.label);
- mSecondaryCallInfo = view.findViewById(R.id.secondary_call_info);
- mSecondaryCallProviderInfo = view.findViewById(R.id.secondary_call_provider_info);
- mCallCardContent = view.findViewById(R.id.call_card_content);
- mPhotoLarge = (ImageView) view.findViewById(R.id.photoLarge);
- mPhotoLarge.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().onContactPhotoClick();
- }
- });
-
- mContactContext = view.findViewById(R.id.contact_context);
- mContactContextTitle = (TextView) view.findViewById(R.id.contactContextTitle);
- mContactContextListView = (ListView) view.findViewById(R.id.contactContextInfo);
- // This layout stores all the list header layouts so they can be easily removed.
- mContactContextListHeaders = new LinearLayout(getView().getContext());
- mContactContextListView.addHeaderView(mContactContextListHeaders);
-
- mCallStateIcon = (ImageView) view.findViewById(R.id.callStateIcon);
- mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
- mWorkProfileIcon = (ImageView) view.findViewById(R.id.workProfileIcon);
- mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
- mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
- mForwardIcon = (ImageView) view.findViewById(R.id.forwardIcon);
- mSpamIcon = (ImageView) view.findViewById(R.id.spamIcon);
- mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
- mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
- mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
- mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container);
- mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner);
- mCallButtonsContainer = view.findViewById(R.id.callButtonFragment);
- mPhotoSmall = (ImageView) view.findViewById(R.id.photoSmall);
- mPhotoSmall.setVisibility(View.GONE);
- mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage);
- mProgressSpinner = view.findViewById(R.id.progressSpinner);
-
- mFloatingActionButtonContainer = view.findViewById(
- R.id.floating_end_call_action_button_container);
- mFloatingActionButton = (ImageButton) view.findViewById(
- R.id.floating_end_call_action_button);
- mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().endCallClicked();
- }
- });
- mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
- mFloatingActionButtonContainer, mFloatingActionButton);
-
- mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().secondaryInfoClicked();
- updateFabPositionForSecondaryCallInfo();
- }
- });
-
- mCallStateButton = view.findViewById(R.id.callStateButton);
- mCallStateButton.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- getPresenter().onCallStateButtonTouched();
- return false;
- }
- });
-
- mManageConferenceCallButton = view.findViewById(R.id.manage_conference_call_button);
- mManageConferenceCallButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- InCallActivity activity = (InCallActivity) getActivity();
- activity.showConferenceFragment(true);
- }
- });
-
- mPrimaryName.setElegantTextHeight(false);
- mCallStateLabel.setElegantTextHeight(false);
- mCallSubject = (TextView) view.findViewById(R.id.callSubject);
- }
-
- @Override
- public void setVisible(boolean on) {
- if (on) {
- getView().setVisibility(View.VISIBLE);
- } else {
- getView().setVisibility(View.INVISIBLE);
- }
- }
-
- /**
- * Hides or shows the progress spinner.
- *
- * @param visible {@code True} if the progress spinner should be visible.
- */
- @Override
- public void setProgressSpinnerVisible(boolean visible) {
- mProgressSpinner.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public void setContactContextTitle(View headerView) {
- mContactContextListHeaders.removeAllViews();
- mContactContextListHeaders.addView(headerView);
- }
-
- @Override
- public void setContactContextContent(ListAdapter listAdapter) {
- mContactContextListView.setAdapter(listAdapter);
- }
-
- @Override
- public void showContactContext(boolean show) {
- showImageView(mPhotoLarge, !show);
- showImageView(mPhotoSmall, show);
- mPrimaryCallCardContainer.setElevation(
- show ? 0 : getResources().getDimension(R.dimen.primary_call_elevation));
- mContactContext.setVisibility(show ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Sets the visibility of the primary call card.
- * Ensures that when the primary call card is hidden, the video surface slides over to fill the
- * entire screen.
- *
- * @param visible {@code True} if the primary call card should be visible.
- */
- @Override
- public void setCallCardVisible(final boolean visible) {
- Log.v(this, "setCallCardVisible : isVisible = " + visible);
- // When animating the hide/show of the views in a landscape layout, we need to take into
- // account whether we are in a left-to-right locale or a right-to-left locale and adjust
- // the animations accordingly.
- final boolean isLayoutRtl = InCallPresenter.isRtl();
-
- // Retrieve here since at fragment creation time the incoming video view is not inflated.
- final View videoView = getView().findViewById(R.id.incomingVideo);
- if (videoView == null) {
- return;
- }
-
- // Determine how much space there is below or to the side of the call card.
- final float spaceBesideCallCard = getSpaceBesideCallCard();
-
- // We need to translate the video surface, but we need to know its position after the layout
- // has occurred so use a {@code ViewTreeObserver}.
- final ViewTreeObserver observer = getView().getViewTreeObserver();
- observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // We don't want to continue getting called.
- getView().getViewTreeObserver().removeOnPreDrawListener(this);
-
- float videoViewTranslation = 0f;
-
- // Translate the call card to its pre-animation state.
- if (!mIsLandscape) {
- mPrimaryCallCardContainer.setTranslationY(visible ?
- -mPrimaryCallCardContainer.getHeight() : 0);
-
- ViewGroup.LayoutParams p = videoView.getLayoutParams();
- videoViewTranslation = p.height / 2 - spaceBesideCallCard / 2;
- }
-
- // Perform animation of video view.
- ViewPropertyAnimator videoViewAnimator = videoView.animate()
- .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
- .setDuration(mVideoAnimationDuration);
- if (mIsLandscape) {
- videoViewAnimator
- .translationX(visible ? videoViewTranslation : 0);
- } else {
- videoViewAnimator
- .translationY(visible ? videoViewTranslation : 0);
- }
- videoViewAnimator.start();
-
- // Animate the call card sliding.
- ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate()
- .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
- .setDuration(mVideoAnimationDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (!visible) {
- mPrimaryCallCardContainer.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- if (visible) {
- mPrimaryCallCardContainer.setVisibility(View.VISIBLE);
- }
- }
- });
-
- if (mIsLandscape) {
- float translationX = mPrimaryCallCardContainer.getWidth();
- translationX *= isLayoutRtl ? 1 : -1;
- callCardAnimator
- .translationX(visible ? 0 : translationX)
- .start();
- } else {
- callCardAnimator
- .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight())
- .start();
- }
-
- return true;
- }
- });
- }
-
- /**
- * Determines the amount of space below the call card for portrait layouts), or beside the
- * call card for landscape layouts.
- *
- * @return The amount of space below or beside the call card.
- */
- public float getSpaceBesideCallCard() {
- if (mIsLandscape) {
- return getView().getWidth() - mPrimaryCallCardContainer.getWidth();
- } else {
- final int callCardHeight;
- // Retrieve the actual height of the call card, independent of whether or not the
- // outgoing call animation is in progress. The animation does not run in landscape mode
- // so this only needs to be done for portrait.
- if (mPrimaryCallCardContainer.getTag(R.id.view_tag_callcard_actual_height) != null) {
- callCardHeight = (int) mPrimaryCallCardContainer.getTag(
- R.id.view_tag_callcard_actual_height);
- } else {
- callCardHeight = mPrimaryCallCardContainer.getHeight();
- }
- return getView().getHeight() - callCardHeight;
- }
- }
-
- @Override
- public void setPrimaryName(String name, boolean nameIsNumber) {
- if (TextUtils.isEmpty(name)) {
- mPrimaryName.setText(null);
- } else {
- mPrimaryName.setText(nameIsNumber
- ? PhoneNumberUtilsCompat.createTtsSpannable(name)
- : name);
-
- // Set direction of the name field
- int nameDirection = View.TEXT_DIRECTION_INHERIT;
- if (nameIsNumber) {
- nameDirection = View.TEXT_DIRECTION_LTR;
- }
- mPrimaryName.setTextDirection(nameDirection);
- }
- }
-
- /**
- * Sets the primary image for the contact photo.
- *
- * @param image The drawable to set.
- * @param isVisible Whether the contact photo should be visible after being set.
- */
- @Override
- public void setPrimaryImage(Drawable image, boolean isVisible) {
- if (image != null) {
- setDrawableToImageViews(image);
- showImageView(mPhotoLarge, isVisible);
- }
- }
-
- @Override
- public void setPrimaryPhoneNumber(String number) {
- // Set the number
- if (TextUtils.isEmpty(number)) {
- mPhoneNumber.setText(null);
- mPhoneNumber.setVisibility(View.GONE);
- } else {
- mPhoneNumber.setText(PhoneNumberUtilsCompat.createTtsSpannable(number));
- mPhoneNumber.setVisibility(View.VISIBLE);
- mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
- }
- }
-
- @Override
- public void setPrimaryLabel(String label) {
- if (!TextUtils.isEmpty(label)) {
- mNumberLabel.setText(label);
- mNumberLabel.setVisibility(View.VISIBLE);
- } else {
- mNumberLabel.setVisibility(View.GONE);
- }
-
- }
-
- /**
- * Sets the primary caller information.
- *
- * @param number The caller phone number.
- * @param name The caller name.
- * @param nameIsNumber {@code true} if the name should be shown in place of the phone number.
- * @param label The label.
- * @param photo The contact photo drawable.
- * @param isSipCall {@code true} if this is a SIP call.
- * @param isContactPhotoShown {@code true} if the contact photo should be shown (it will be
- * updated even if it is not shown).
- * @param isWorkCall Whether the call is placed through a work phone account or caller is a work
- contact.
- */
- @Override
- public void setPrimary(String number, String name, boolean nameIsNumber, String label,
- Drawable photo, boolean isSipCall, boolean isContactPhotoShown, boolean isWorkCall) {
- Log.d(this, "Setting primary call");
- // set the name field.
- setPrimaryName(name, nameIsNumber);
-
- if (TextUtils.isEmpty(number) && TextUtils.isEmpty(label)) {
- mCallNumberAndLabel.setVisibility(View.GONE);
- mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- } else {
- mCallNumberAndLabel.setVisibility(View.VISIBLE);
- mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
- }
-
- setPrimaryPhoneNumber(number);
-
- // Set the label (Mobile, Work, etc)
- setPrimaryLabel(label);
-
- showInternetCallLabel(isSipCall);
-
- setDrawableToImageViews(photo);
- showImageView(mPhotoLarge, isContactPhotoShown);
- showImageView(mWorkProfileIcon, isWorkCall);
- }
-
- @Override
- public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
- String providerLabel, boolean isConference, boolean isVideoCall, boolean isFullscreen) {
-
- if (show) {
- mHasSecondaryCallInfo = true;
- boolean hasProvider = !TextUtils.isEmpty(providerLabel);
- initializeSecondaryCallInfo(hasProvider);
-
- // Do not show the secondary caller info in fullscreen mode, but ensure it is populated
- // in case fullscreen mode is exited in the future.
- setSecondaryInfoVisible(!isFullscreen);
-
- mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE);
- mSecondaryCallVideoCallIcon.setVisibility(isVideoCall ? View.VISIBLE : View.GONE);
-
- mSecondaryCallName.setText(nameIsNumber
- ? PhoneNumberUtilsCompat.createTtsSpannable(name)
- : name);
- if (hasProvider) {
- mSecondaryCallProviderLabel.setText(providerLabel);
- }
-
- int nameDirection = View.TEXT_DIRECTION_INHERIT;
- if (nameIsNumber) {
- nameDirection = View.TEXT_DIRECTION_LTR;
- }
- mSecondaryCallName.setTextDirection(nameDirection);
- } else {
- mHasSecondaryCallInfo = false;
- setSecondaryInfoVisible(false);
- }
- }
-
- /**
- * Sets the visibility of the secondary caller info box. Note, if the {@code visible} parameter
- * is passed in {@code true}, and there is no secondary caller info populated (as determined by
- * {@code mHasSecondaryCallInfo}, the secondary caller info box will not be shown.
- *
- * @param visible {@code true} if the secondary caller info should be shown, {@code false}
- * otherwise.
- */
- @Override
- public void setSecondaryInfoVisible(final boolean visible) {
- boolean wasVisible = mSecondaryCallInfo.isShown();
- final boolean isVisible = visible && mHasSecondaryCallInfo;
- Log.v(this, "setSecondaryInfoVisible: wasVisible = " + wasVisible + " isVisible = "
- + isVisible);
-
- // If visibility didn't change, nothing to do.
- if (wasVisible == isVisible) {
- return;
- }
-
- // If we are showing the secondary info, we need to show it before animating so that its
- // height will be determined on layout.
- if (isVisible) {
- mSecondaryCallInfo.setVisibility(View.VISIBLE);
- } else {
- mSecondaryCallInfo.setVisibility(View.GONE);
- }
-
- updateFabPositionForSecondaryCallInfo();
- // We need to translate the secondary caller info, but we need to know its position after
- // the layout has occurred so use a {@code ViewTreeObserver}.
- final ViewTreeObserver observer = getView().getViewTreeObserver();
-
- observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // We don't want to continue getting called.
- getView().getViewTreeObserver().removeOnPreDrawListener(this);
-
- // Get the height of the secondary call info now, and then re-hide the view prior
- // to doing the actual animation.
- int secondaryHeight = mSecondaryCallInfo.getHeight();
- if (isVisible) {
- mSecondaryCallInfo.setVisibility(View.GONE);
- } else {
- mSecondaryCallInfo.setVisibility(View.VISIBLE);
- }
- Log.v(this, "setSecondaryInfoVisible: secondaryHeight = " + secondaryHeight);
-
- // Set the position of the secondary call info card to its starting location.
- mSecondaryCallInfo.setTranslationY(visible ? secondaryHeight : 0);
-
- // Animate the secondary card info slide up/down as it appears and disappears.
- ViewPropertyAnimator secondaryInfoAnimator = mSecondaryCallInfo.animate()
- .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
- .setDuration(mVideoAnimationDuration)
- .translationY(isVisible ? 0 : secondaryHeight)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!isVisible) {
- mSecondaryCallInfo.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- if (isVisible) {
- mSecondaryCallInfo.setVisibility(View.VISIBLE);
- }
- }
- });
- secondaryInfoAnimator.start();
-
- // Notify listeners of a change in the visibility of the secondary info. This is
- // important when in a video call so that the video call presenter can shift the
- // video preview up or down to accommodate the secondary caller info.
- InCallPresenter.getInstance().notifySecondaryCallerInfoVisibilityChanged(visible,
- secondaryHeight);
-
- return true;
- }
- });
- }
-
- @Override
- public void setCallState(
- int state,
- int videoState,
- int sessionModificationState,
- DisconnectCause disconnectCause,
- String connectionLabel,
- Drawable callStateIcon,
- String gatewayNumber,
- boolean isWifi,
- boolean isConference,
- boolean isWorkCall) {
- boolean isGatewayCall = !TextUtils.isEmpty(gatewayNumber);
- CallStateLabel callStateLabel = getCallStateLabelFromState(state, videoState,
- sessionModificationState, disconnectCause, connectionLabel, isGatewayCall, isWifi,
- isConference, isWorkCall);
-
- Log.v(this, "setCallState " + callStateLabel.getCallStateLabel());
- Log.v(this, "AutoDismiss " + callStateLabel.isAutoDismissing());
- Log.v(this, "DisconnectCause " + disconnectCause.toString());
- Log.v(this, "gateway " + connectionLabel + gatewayNumber);
-
- // Check for video state change and update the visibility of the contact photo. The contact
- // photo is hidden when the incoming video surface is shown.
- // The contact photo visibility can also change in setPrimary().
- boolean showContactPhoto = !VideoCallPresenter.showIncomingVideo(videoState, state);
- mPhotoLarge.setVisibility(showContactPhoto ? View.VISIBLE : View.GONE);
-
- // Check if the call subject is showing -- if it is, we want to bypass showing the call
- // state.
- boolean isSubjectShowing = mCallSubject.getVisibility() == View.VISIBLE;
-
- if (TextUtils.equals(callStateLabel.getCallStateLabel(), mCallStateLabel.getText()) &&
- !isSubjectShowing) {
- // Nothing to do if the labels are the same
- if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
- mCallStateLabel.clearAnimation();
- mCallStateIcon.clearAnimation();
- }
- return;
- }
-
- if (isSubjectShowing) {
- changeCallStateLabel(null);
- callStateIcon = null;
- } else {
- // Update the call state label and icon.
- setCallStateLabel(callStateLabel);
- }
-
- if (!TextUtils.isEmpty(callStateLabel.getCallStateLabel())) {
- if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
- mCallStateLabel.clearAnimation();
- } else {
- mCallStateLabel.startAnimation(mPulseAnimation);
- }
- } else {
- mCallStateLabel.clearAnimation();
- }
-
- if (callStateIcon != null) {
- mCallStateIcon.setVisibility(View.VISIBLE);
- // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
- // needed because the pulse animation operates on the view alpha.
- mCallStateIcon.setAlpha(1.0f);
- mCallStateIcon.setImageDrawable(callStateIcon);
-
- if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED
- || TextUtils.isEmpty(callStateLabel.getCallStateLabel())) {
- mCallStateIcon.clearAnimation();
- } else {
- mCallStateIcon.startAnimation(mPulseAnimation);
- }
-
- if (callStateIcon instanceof AnimationDrawable) {
- ((AnimationDrawable) callStateIcon).start();
- }
- } else {
- mCallStateIcon.clearAnimation();
-
- // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
- // needed because the pulse animation operates on the view alpha.
- mCallStateIcon.setAlpha(0.0f);
- mCallStateIcon.setVisibility(View.GONE);
- }
-
- if (VideoUtils.isVideoCall(videoState)
- || (state == Call.State.ACTIVE && sessionModificationState
- == Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
- mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
- } else {
- mCallStateVideoCallIcon.setVisibility(View.GONE);
- }
- }
-
- private void setCallStateLabel(CallStateLabel callStateLabel) {
- Log.v(this, "setCallStateLabel : label = " + callStateLabel.getCallStateLabel());
-
- if (callStateLabel.isAutoDismissing()) {
- mCallStateLabelResetPending = true;
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- Log.v(this, "restoringCallStateLabel : label = " +
- mPostResetCallStateLabel);
- changeCallStateLabel(mPostResetCallStateLabel);
- mCallStateLabelResetPending = false;
- }
- }, CALL_STATE_LABEL_RESET_DELAY_MS);
-
- changeCallStateLabel(callStateLabel.getCallStateLabel());
- } else {
- // Keep track of the current call state label; used when resetting auto dismissing
- // call state labels.
- mPostResetCallStateLabel = callStateLabel.getCallStateLabel();
-
- if (!mCallStateLabelResetPending) {
- changeCallStateLabel(callStateLabel.getCallStateLabel());
- }
- }
- }
-
- private void changeCallStateLabel(CharSequence callStateLabel) {
- Log.v(this, "changeCallStateLabel : label = " + callStateLabel);
- if (!TextUtils.isEmpty(callStateLabel)) {
- mCallStateLabel.setText(callStateLabel);
- mCallStateLabel.setAlpha(1);
- mCallStateLabel.setVisibility(View.VISIBLE);
- } else {
- Animation callStateLabelAnimation = mCallStateLabel.getAnimation();
- if (callStateLabelAnimation != null) {
- callStateLabelAnimation.cancel();
- }
- mCallStateLabel.setText(null);
- mCallStateLabel.setAlpha(0);
- mCallStateLabel.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void setCallbackNumber(String callbackNumber, boolean isEmergencyCall) {
- if (mInCallMessageLabel == null) {
- return;
- }
-
- if (TextUtils.isEmpty(callbackNumber)) {
- mInCallMessageLabel.setVisibility(View.GONE);
- return;
- }
-
- // TODO: The new Locale-specific methods don't seem to be working. Revisit this.
- callbackNumber = PhoneNumberUtils.formatNumber(callbackNumber);
-
- int stringResourceId = isEmergencyCall ? R.string.card_title_callback_number_emergency
- : R.string.card_title_callback_number;
-
- String text = getString(stringResourceId, callbackNumber);
- mInCallMessageLabel.setText(text);
-
- mInCallMessageLabel.setVisibility(View.VISIBLE);
- }
-
- /**
- * Sets and shows the call subject if it is not empty. Hides the call subject otherwise.
- *
- * @param callSubject The call subject.
- */
- @Override
- public void setCallSubject(String callSubject) {
- boolean showSubject = !TextUtils.isEmpty(callSubject);
-
- mCallSubject.setVisibility(showSubject ? View.VISIBLE : View.GONE);
- if (showSubject) {
- mCallSubject.setText(callSubject);
- } else {
- mCallSubject.setText(null);
- }
- }
-
- public boolean isAnimating() {
- return mIsAnimating;
- }
-
- private void showInternetCallLabel(boolean show) {
- if (show) {
- final String label = getView().getContext().getString(
- R.string.incall_call_type_label_sip);
- mCallTypeLabel.setVisibility(View.VISIBLE);
- mCallTypeLabel.setText(label);
- } else {
- mCallTypeLabel.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void setPrimaryCallElapsedTime(boolean show, long duration) {
- if (show) {
- if (mElapsedTime.getVisibility() != View.VISIBLE) {
- AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
- }
- String callTimeElapsed = DateUtils.formatElapsedTime(duration / 1000);
- mElapsedTime.setText(callTimeElapsed);
-
- String durationDescription =
- InCallDateUtils.formatDuration(duration);
- mElapsedTime.setContentDescription(
- !TextUtils.isEmpty(durationDescription) ? durationDescription : null);
- } else {
- // hide() animation has no effect if it is already hidden.
- AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION);
- }
- }
-
- /**
- * Set all the ImageViews to the same photo. Currently there are 2 photo views: the large one
- * (which fills about the bottom half of the screen) and the small one, which displays as a
- * circle next to the primary contact info. This method does not handle whether the ImageView
- * is shown or not.
- *
- * @param photo The photo to set for the image views.
- */
- private void setDrawableToImageViews(Drawable photo) {
- if (photo == null) {
- photo = ContactInfoCache.getInstance(getView().getContext())
- .getDefaultContactPhotoDrawable();
- }
-
- if (mPrimaryPhotoDrawable == photo){
- return;
- }
- mPrimaryPhotoDrawable = photo;
-
- mPhotoLarge.setImageDrawable(photo);
-
- // Modify the drawable to be round for the smaller ImageView.
- Bitmap bitmap = drawableToBitmap(photo);
- if (bitmap != null) {
- final RoundedBitmapDrawable drawable =
- RoundedBitmapDrawableFactory.create(getResources(), bitmap);
- drawable.setAntiAlias(true);
- drawable.setCornerRadius(bitmap.getHeight() / 2);
- photo = drawable;
- }
- mPhotoSmall.setImageDrawable(photo);
- }
-
- /**
- * Helper method for image view to handle animations.
- *
- * @param view The image view to show or hide.
- * @param isVisible {@code true} if we want to show the image, {@code false} to hide it.
- */
- private void showImageView(ImageView view, boolean isVisible) {
- if (view.getDrawable() == null) {
- if (isVisible) {
- AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
- }
- } else {
- // Cross fading is buggy and not noticeable due to the multiple calls to this method
- // that switch drawables in the middle of the cross-fade animations. Just show the
- // photo directly instead.
- view.setVisibility(isVisible ? View.VISIBLE : View.GONE);
- }
- }
-
- /**
- * Converts a drawable into a bitmap.
- *
- * @param drawable the drawable to be converted.
- */
- public static Bitmap drawableToBitmap(Drawable drawable) {
- Bitmap bitmap;
- if (drawable instanceof BitmapDrawable) {
- bitmap = ((BitmapDrawable) drawable).getBitmap();
- } else {
- if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
- // Needed for drawables that are just a colour.
- bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- } else {
- bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- }
-
- Log.i(TAG, "Created bitmap with width " + bitmap.getWidth() + ", height "
- + bitmap.getHeight());
-
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
- }
- return bitmap;
- }
-
- /**
- * Gets the call state label based on the state of the call or cause of disconnect.
- *
- * Additional labels are applied as follows:
- * 1. All outgoing calls with display "Calling via [Provider]".
- * 2. Ongoing calls will display the name of the provider.
- * 3. Incoming calls will only display "Incoming via..." for accounts.
- * 4. Video calls, and session modification states (eg. requesting video).
- * 5. Incoming and active Wi-Fi calls will show label provided by hint.
- *
- * TODO: Move this to the CallCardPresenter.
- */
- private CallStateLabel getCallStateLabelFromState(int state, int videoState,
- int sessionModificationState, DisconnectCause disconnectCause, String label,
- boolean isGatewayCall, boolean isWifi, boolean isConference, boolean isWorkCall) {
- final Context context = getView().getContext();
- CharSequence callStateLabel = null; // Label to display as part of the call banner
-
- boolean hasSuggestedLabel = label != null;
- boolean isAccount = hasSuggestedLabel && !isGatewayCall;
- boolean isAutoDismissing = false;
-
- switch (state) {
- case Call.State.IDLE:
- // "Call state" is meaningless in this state.
- break;
- case Call.State.ACTIVE:
- // We normally don't show a "call state label" at all in this state
- // (but we can use the call state label to display the provider name).
- if ((isAccount || isWifi || isConference) && hasSuggestedLabel) {
- callStateLabel = label;
- } else if (sessionModificationState
- == Call.SessionModificationState.REQUEST_REJECTED) {
- callStateLabel = context.getString(R.string.card_title_video_call_rejected);
- isAutoDismissing = true;
- } else if (sessionModificationState
- == Call.SessionModificationState.REQUEST_FAILED) {
- callStateLabel = context.getString(R.string.card_title_video_call_error);
- isAutoDismissing = true;
- } else if (sessionModificationState
- == Call.SessionModificationState.WAITING_FOR_RESPONSE) {
- callStateLabel = context.getString(R.string.card_title_video_call_requesting);
- } else if (sessionModificationState
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- callStateLabel = context.getString(R.string.card_title_video_call_requesting);
- } else if (VideoUtils.isVideoCall(videoState)) {
- callStateLabel = context.getString(R.string.card_title_video_call);
- }
- break;
- case Call.State.ONHOLD:
- callStateLabel = context.getString(R.string.card_title_on_hold);
- break;
- case Call.State.CONNECTING:
- case Call.State.DIALING:
- if (hasSuggestedLabel && !isWifi) {
- callStateLabel = context.getString(R.string.calling_via_template, label);
- } else {
- callStateLabel = context.getString(R.string.card_title_dialing);
- }
- break;
- case Call.State.REDIALING:
- callStateLabel = context.getString(R.string.card_title_redialing);
- break;
- case Call.State.INCOMING:
- case Call.State.CALL_WAITING:
- if (isWifi && hasSuggestedLabel) {
- callStateLabel = label;
- } else if (isAccount) {
- callStateLabel = context.getString(R.string.incoming_via_template, label);
- } else if (VideoUtils.isVideoCall(videoState)) {
- callStateLabel = context.getString(R.string.notification_incoming_video_call);
- } else {
- callStateLabel =
- context.getString(isWorkCall ? R.string.card_title_incoming_work_call
- : R.string.card_title_incoming_call);
- }
- break;
- case Call.State.DISCONNECTING:
- // While in the DISCONNECTING state we display a "Hanging up"
- // message in order to make the UI feel more responsive. (In
- // GSM it's normal to see a delay of a couple of seconds while
- // negotiating the disconnect with the network, so the "Hanging
- // up" state at least lets the user know that we're doing
- // something. This state is currently not used with CDMA.)
- callStateLabel = context.getString(R.string.card_title_hanging_up);
- break;
- case Call.State.DISCONNECTED:
- callStateLabel = disconnectCause.getLabel();
- if (TextUtils.isEmpty(callStateLabel)) {
- callStateLabel = context.getString(R.string.card_title_call_ended);
- }
- break;
- case Call.State.CONFERENCED:
- callStateLabel = context.getString(R.string.card_title_conf_call);
- break;
- default:
- Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
- }
- return new CallStateLabel(callStateLabel, isAutoDismissing);
- }
-
- private void initializeSecondaryCallInfo(boolean hasProvider) {
- // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccessible
- // until mSecondaryCallInfo is inflated in the call above.
- if (mSecondaryCallName == null) {
- mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
- mSecondaryCallConferenceCallIcon =
- getView().findViewById(R.id.secondaryCallConferenceCallIcon);
- mSecondaryCallVideoCallIcon =
- getView().findViewById(R.id.secondaryCallVideoCallIcon);
- }
-
- if (mSecondaryCallProviderLabel == null && hasProvider) {
- mSecondaryCallProviderInfo.setVisibility(View.VISIBLE);
- mSecondaryCallProviderLabel = (TextView) getView()
- .findViewById(R.id.secondaryCallProviderLabel);
- }
- }
-
- public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_ANNOUNCEMENT) {
- // Indicate this call is in active if no label is provided. The label is empty when
- // the call is in active, not in other status such as onhold or dialing etc.
- if (!mCallStateLabel.isShown() || TextUtils.isEmpty(mCallStateLabel.getText())) {
- event.getText().add(
- TextUtils.expandTemplate(
- getResources().getText(R.string.accessibility_call_is_active),
- mPrimaryName.getText()));
- } else {
- dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
- dispatchPopulateAccessibilityEvent(event, mPrimaryName);
- dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
- dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
- }
- return;
- }
- dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
- dispatchPopulateAccessibilityEvent(event, mPrimaryName);
- dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
- dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
- dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
- dispatchPopulateAccessibilityEvent(event, mSecondaryCallProviderLabel);
-
- return;
- }
-
- @Override
- public void sendAccessibilityAnnouncement() {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (getView() != null && getView().getParent() != null &&
- isAccessibilityEnabled(getContext())) {
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_ANNOUNCEMENT);
- dispatchPopulateAccessibilityEvent(event);
- getView().getParent().requestSendAccessibilityEvent(getView(), event);
- }
- }
-
- private boolean isAccessibilityEnabled(Context context) {
- AccessibilityManager accessibilityManager =
- (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- return accessibilityManager != null && accessibilityManager.isEnabled();
-
- }
- }, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS);
- }
-
- @Override
- public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
- if (enabled != mFloatingActionButton.isEnabled()) {
- if (animate) {
- if (enabled) {
- mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
- } else {
- mFloatingActionButtonController.scaleOut();
- }
- } else {
- if (enabled) {
- mFloatingActionButtonContainer.setScaleX(1);
- mFloatingActionButtonContainer.setScaleY(1);
- mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
- } else {
- mFloatingActionButtonContainer.setVisibility(View.GONE);
- }
- }
- mFloatingActionButton.setEnabled(enabled);
- updateFabPosition();
- }
- }
-
- /**
- * Changes the visibility of the HD audio icon.
- *
- * @param visible {@code true} if the UI should show the HD audio icon.
- */
- @Override
- public void showHdAudioIndicator(boolean visible) {
- mHdAudioIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Changes the visibility of the forward icon.
- *
- * @param visible {@code true} if the UI should show the forward icon.
- */
- @Override
- public void showForwardIndicator(boolean visible) {
- mForwardIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Changes the visibility of the spam icon.
- *
- * @param visible {@code true} if the UI should show the spam icon.
- */
- @Override
- public void showSpamIndicator(boolean visible) {
- if (visible) {
- mSpamIcon.setVisibility(View.VISIBLE);
- mNumberLabel.setText(R.string.label_spam_caller);
- mPhoneNumber.setVisibility(View.GONE);
- }
- }
-
- /**
- * Changes the visibility of the "manage conference call" button.
- *
- * @param visible Whether to set the button to be visible or not.
- */
- @Override
- public void showManageConferenceCallButton(boolean visible) {
- mManageConferenceCallButton.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Determines the current visibility of the manage conference button.
- *
- * @return {@code true} if the button is visible.
- */
- @Override
- public boolean isManageConferenceVisible() {
- return mManageConferenceCallButton.getVisibility() == View.VISIBLE;
- }
-
- /**
- * Determines the current visibility of the call subject.
- *
- * @return {@code true} if the subject is visible.
- */
- @Override
- public boolean isCallSubjectVisible() {
- return mCallSubject.getVisibility() == View.VISIBLE;
- }
-
- /**
- * Get the overall InCallUI background colors and apply to call card.
- */
- public void updateColors() {
- MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
-
- if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) {
- return;
- }
-
- if (getResources().getBoolean(R.bool.is_layout_landscape)) {
- final GradientDrawable drawable =
- (GradientDrawable) mPrimaryCallCardContainer.getBackground();
- drawable.setColor(themeColors.mPrimaryColor);
- } else {
- mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor);
- }
- mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor);
- mCallSubject.setTextColor(themeColors.mPrimaryColor);
- mContactContext.setBackgroundColor(themeColors.mPrimaryColor);
- //TODO: set color of message text in call context "recent messages" to be the theme color.
-
- mCurrentThemeColors = themeColors;
- }
-
- private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
- if (view == null) return;
- final List<CharSequence> eventText = event.getText();
- int size = eventText.size();
- view.dispatchPopulateAccessibilityEvent(event);
- // if no text added write null to keep relative position
- if (size == eventText.size()) {
- eventText.add(null);
- }
- }
-
- @Override
- public void animateForNewOutgoingCall() {
- final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent();
-
- final ViewTreeObserver observer = getView().getViewTreeObserver();
-
- mIsAnimating = true;
-
- observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- final ViewTreeObserver observer = getView().getViewTreeObserver();
- if (!observer.isAlive()) {
- return;
- }
- observer.removeOnGlobalLayoutListener(this);
-
- final LayoutIgnoringListener listener = new LayoutIgnoringListener();
- mPrimaryCallCardContainer.addOnLayoutChangeListener(listener);
-
- // Prepare the state of views before the slide animation
- final int originalHeight = mPrimaryCallCardContainer.getHeight();
- mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
- originalHeight);
- mPrimaryCallCardContainer.setBottom(parent.getHeight());
-
- // Set up FAB.
- mFloatingActionButtonContainer.setVisibility(View.GONE);
- mFloatingActionButtonController.setScreenWidth(parent.getWidth());
-
- mCallButtonsContainer.setAlpha(0);
- mCallStateLabel.setAlpha(0);
- mPrimaryName.setAlpha(0);
- mCallTypeLabel.setAlpha(0);
- mCallNumberAndLabel.setAlpha(0);
-
- assignTranslateAnimation(mCallStateLabel, 1);
- assignTranslateAnimation(mCallStateIcon, 1);
- assignTranslateAnimation(mPrimaryName, 2);
- assignTranslateAnimation(mCallNumberAndLabel, 3);
- assignTranslateAnimation(mCallTypeLabel, 4);
- assignTranslateAnimation(mCallButtonsContainer, 5);
-
- final Animator animator = getShrinkAnimator(parent.getHeight(), originalHeight);
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
- null);
- setViewStatePostAnimation(listener);
- mIsAnimating = false;
- InCallPresenter.getInstance().onShrinkAnimationComplete();
- if (animator != null) {
- animator.removeListener(this);
- }
- }
- });
- animator.start();
- }
- });
- }
-
- @Override
- public void showNoteSentToast() {
- Toast.makeText(getContext(), R.string.note_sent, Toast.LENGTH_LONG).show();
- }
-
- public void onDialpadVisibilityChange(boolean isShown) {
- mIsDialpadShowing = isShown;
- updateFabPosition();
- }
-
- private void updateFabPosition() {
- int offsetY = 0;
- if (!mIsDialpadShowing) {
- offsetY = mFloatingActionButtonVerticalOffset;
- if (mSecondaryCallInfo.isShown() && mHasLargePhoto) {
- offsetY -= mSecondaryCallInfo.getHeight();
- }
- }
-
- mFloatingActionButtonController.align(
- FloatingActionButtonController.ALIGN_MIDDLE,
- 0 /* offsetX */,
- offsetY,
- true);
-
- mFloatingActionButtonController.resize(
- mIsDialpadShowing ? mFabSmallDiameter : mFabNormalDiameter, true);
- }
-
- @Override
- public Context getContext() {
- return getActivity();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // If the previous launch animation is still running, cancel it so that we don't get
- // stuck in an intermediate animation state.
- if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
- mAnimatorSet.cancel();
- }
-
- mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape);
- mHasLargePhoto = getResources().getBoolean(R.bool.has_large_photo);
-
- final ViewGroup parent = ((ViewGroup) mPrimaryCallCardContainer.getParent());
- final ViewTreeObserver observer = parent.getViewTreeObserver();
- parent.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- ViewTreeObserver viewTreeObserver = observer;
- if (!viewTreeObserver.isAlive()) {
- viewTreeObserver = parent.getViewTreeObserver();
- }
- viewTreeObserver.removeOnGlobalLayoutListener(this);
- mFloatingActionButtonController.setScreenWidth(parent.getWidth());
- updateFabPosition();
- }
- });
-
- updateColors();
- }
-
- /**
- * Adds a global layout listener to update the FAB's positioning on the next layout. This allows
- * us to position the FAB after the secondary call info's height has been calculated.
- */
- private void updateFabPositionForSecondaryCallInfo() {
- mSecondaryCallInfo.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- final ViewTreeObserver observer = mSecondaryCallInfo.getViewTreeObserver();
- if (!observer.isAlive()) {
- return;
- }
- observer.removeOnGlobalLayoutListener(this);
-
- onDialpadVisibilityChange(mIsDialpadShowing);
- }
- });
- }
-
- /**
- * Animator that performs the upwards shrinking animation of the blue call card scrim.
- * At the start of the animation, each child view is moved downwards by a pre-specified amount
- * and then translated upwards together with the scrim.
- */
- private Animator getShrinkAnimator(int startHeight, int endHeight) {
- final ObjectAnimator shrinkAnimator =
- ObjectAnimator.ofInt(mPrimaryCallCardContainer, "bottom", startHeight, endHeight);
- shrinkAnimator.setDuration(mShrinkAnimationDuration);
- shrinkAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mFloatingActionButton.setEnabled(true);
- }
- });
- shrinkAnimator.setInterpolator(AnimUtils.EASE_IN);
- return shrinkAnimator;
- }
-
- private void assignTranslateAnimation(View view, int offset) {
- view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- view.buildLayer();
- view.setTranslationY(mTranslationOffset * offset);
- view.animate().translationY(0).alpha(1).withLayer()
- .setDuration(mShrinkAnimationDuration).setInterpolator(AnimUtils.EASE_IN);
- }
-
- private void setViewStatePostAnimation(View view) {
- view.setTranslationY(0);
- view.setAlpha(1);
- }
-
- private void setViewStatePostAnimation(OnLayoutChangeListener layoutChangeListener) {
- setViewStatePostAnimation(mCallButtonsContainer);
- setViewStatePostAnimation(mCallStateLabel);
- setViewStatePostAnimation(mPrimaryName);
- setViewStatePostAnimation(mCallTypeLabel);
- setViewStatePostAnimation(mCallNumberAndLabel);
- setViewStatePostAnimation(mCallStateIcon);
-
- mPrimaryCallCardContainer.removeOnLayoutChangeListener(layoutChangeListener);
-
- mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
- }
-
- private final class LayoutIgnoringListener implements View.OnLayoutChangeListener {
- @Override
- public void onLayoutChange(View v,
- int left,
- int top,
- int right,
- int bottom,
- int oldLeft,
- int oldTop,
- int oldRight,
- int oldBottom) {
- v.setLeft(oldLeft);
- v.setRight(oldRight);
- v.setTop(oldTop);
- v.setBottom(oldBottom);
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
deleted file mode 100644
index 1ad0c11f1..000000000
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ /dev/null
@@ -1,1181 +0,0 @@
-/*
- * Copyright (C) 2013 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 com.google.common.base.Preconditions;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.telecom.Call.Details;
-import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.StatusHints;
-import android.telecom.TelecomManager;
-import android.telecom.VideoProfile;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.ListAdapter;
-
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.dialer.R;
-import com.android.incallui.Call.State;
-import com.android.incallui.ContactInfoCache.ContactCacheEntry;
-import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
-import com.android.incallui.InCallPresenter.InCallDetailsListener;
-import com.android.incallui.InCallPresenter.InCallEventListener;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.InCallPresenter.InCallStateListener;
-import com.android.incallui.InCallPresenter.IncomingCallListener;
-import com.android.incalluibind.ObjectFactory;
-
-import java.lang.ref.WeakReference;
-
-import static com.android.contacts.common.compat.CallSdkCompat.Details.PROPERTY_ENTERPRISE_CALL;
-/**
- * Presenter for the Call Card Fragment.
- * <p>
- * This class listens for changes to InCallState and passes it along to the fragment.
- */
-public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
- implements InCallStateListener, IncomingCallListener, InCallDetailsListener,
- InCallEventListener, CallList.CallUpdateListener, DistanceHelper.Listener {
-
- public interface EmergencyCallListener {
- public void onCallUpdated(BaseFragment fragment, boolean isEmergency);
- }
-
- private static final String TAG = CallCardPresenter.class.getSimpleName();
- private static final long CALL_TIME_UPDATE_INTERVAL_MS = 1000;
-
- private final EmergencyCallListener mEmergencyCallListener =
- ObjectFactory.newEmergencyCallListener();
- private DistanceHelper mDistanceHelper;
-
- private Call mPrimary;
- private Call mSecondary;
- private ContactCacheEntry mPrimaryContactInfo;
- private ContactCacheEntry mSecondaryContactInfo;
- private CallTimer mCallTimer;
- private Context mContext;
- @Nullable private ContactsPreferences mContactsPreferences;
- private boolean mSpinnerShowing = false;
- private boolean mHasShownToast = false;
- private InCallContactInteractions mInCallContactInteractions;
- private boolean mIsFullscreen = false;
-
- public static class ContactLookupCallback implements ContactInfoCacheCallback {
- private final WeakReference<CallCardPresenter> mCallCardPresenter;
- private final boolean mIsPrimary;
-
- public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
- mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
- mIsPrimary = isPrimary;
- }
-
- @Override
- public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
- CallCardPresenter presenter = mCallCardPresenter.get();
- if (presenter != null) {
- presenter.onContactInfoComplete(callId, entry, mIsPrimary);
- }
- }
-
- @Override
- public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
- CallCardPresenter presenter = mCallCardPresenter.get();
- if (presenter != null) {
- presenter.onImageLoadComplete(callId, entry);
- }
- }
-
- @Override
- public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) {
- CallCardPresenter presenter = mCallCardPresenter.get();
- if (presenter != null) {
- presenter.onContactInteractionsInfoComplete(callId, entry);
- }
- }
- }
-
- public CallCardPresenter() {
- // create the call timer
- mCallTimer = new CallTimer(new Runnable() {
- @Override
- public void run() {
- updateCallTime();
- }
- });
- }
-
- public void init(Context context, Call call) {
- mContext = Preconditions.checkNotNull(context);
- mDistanceHelper = ObjectFactory.newDistanceHelper(mContext, this);
- mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
-
- // Call may be null if disconnect happened already.
- if (call != null) {
- mPrimary = call;
- if (shouldShowNoteSentToast(mPrimary)) {
- final CallCardUi ui = getUi();
- if (ui != null) {
- ui.showNoteSentToast();
- }
- }
- CallList.getInstance().addCallUpdateListener(call.getId(), this);
-
- // start processing lookups right away.
- if (!call.isConferenceCall()) {
- startContactInfoSearch(call, true, call.getState() == Call.State.INCOMING);
- } else {
- updateContactEntry(null, true);
- }
- }
-
- onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
- }
-
- @Override
- public void onUiReady(CallCardUi ui) {
- super.onUiReady(ui);
-
- if (mContactsPreferences != null) {
- mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
- }
-
- // Contact search may have completed before ui is ready.
- if (mPrimaryContactInfo != null) {
- updatePrimaryDisplayInfo();
- }
-
- // Register for call state changes last
- InCallPresenter.getInstance().addListener(this);
- InCallPresenter.getInstance().addIncomingCallListener(this);
- InCallPresenter.getInstance().addDetailsListener(this);
- InCallPresenter.getInstance().addInCallEventListener(this);
- }
-
- @Override
- public void onUiUnready(CallCardUi ui) {
- super.onUiUnready(ui);
-
- // stop getting call state changes
- InCallPresenter.getInstance().removeListener(this);
- InCallPresenter.getInstance().removeIncomingCallListener(this);
- InCallPresenter.getInstance().removeDetailsListener(this);
- InCallPresenter.getInstance().removeInCallEventListener(this);
- if (mPrimary != null) {
- CallList.getInstance().removeCallUpdateListener(mPrimary.getId(), this);
- }
-
- if (mDistanceHelper != null) {
- mDistanceHelper.cleanUp();
- }
-
- mPrimary = null;
- mPrimaryContactInfo = null;
- mSecondaryContactInfo = null;
- }
-
- @Override
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
- // same logic should happen as with onStateChange()
- onStateChange(oldState, newState, CallList.getInstance());
- }
-
- @Override
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
- Log.d(this, "onStateChange() " + newState);
- final CallCardUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- Call primary = null;
- Call secondary = null;
-
- if (newState == InCallState.INCOMING) {
- primary = callList.getIncomingCall();
- } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
- primary = callList.getOutgoingCall();
- if (primary == null) {
- primary = callList.getPendingOutgoingCall();
- }
-
- // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
- // highest priority call to display as the secondary call.
- secondary = getCallToDisplay(callList, null, true);
- } else if (newState == InCallState.INCALL) {
- primary = getCallToDisplay(callList, null, false);
- secondary = getCallToDisplay(callList, primary, true);
- }
-
- if (mInCallContactInteractions != null &&
- (oldState == InCallState.INCOMING || newState == InCallState.INCOMING)) {
- ui.showContactContext(newState != InCallState.INCOMING);
- }
-
- Log.d(this, "Primary call: " + primary);
- Log.d(this, "Secondary call: " + secondary);
-
- final boolean primaryChanged = !(Call.areSame(mPrimary, primary) &&
- Call.areSameNumber(mPrimary, primary));
- final boolean secondaryChanged = !(Call.areSame(mSecondary, secondary) &&
- Call.areSameNumber(mSecondary, secondary));
-
- mSecondary = secondary;
- Call previousPrimary = mPrimary;
- mPrimary = primary;
-
- if (primaryChanged && shouldShowNoteSentToast(primary)) {
- ui.showNoteSentToast();
- }
-
- // Refresh primary call information if either:
- // 1. Primary call changed.
- // 2. The call's ability to manage conference has changed.
- // 3. The call subject should be shown or hidden.
- if (shouldRefreshPrimaryInfo(primaryChanged, ui, shouldShowCallSubject(mPrimary))) {
- // primary call has changed
- if (previousPrimary != null) {
- //clear progess spinner (if any) related to previous primary call
- maybeShowProgressSpinner(previousPrimary.getState(),
- Call.SessionModificationState.NO_REQUEST);
- CallList.getInstance().removeCallUpdateListener(previousPrimary.getId(), this);
- }
- CallList.getInstance().addCallUpdateListener(mPrimary.getId(), this);
-
- mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary,
- mPrimary.getState() == Call.State.INCOMING);
- updatePrimaryDisplayInfo();
- maybeStartSearch(mPrimary, true);
- maybeClearSessionModificationState(mPrimary);
- }
-
- if (previousPrimary != null && mPrimary == null) {
- //clear progess spinner (if any) related to previous primary call
- maybeShowProgressSpinner(previousPrimary.getState(),
- Call.SessionModificationState.NO_REQUEST);
- CallList.getInstance().removeCallUpdateListener(previousPrimary.getId(), this);
- }
-
- if (mSecondary == null) {
- // Secondary call may have ended. Update the ui.
- mSecondaryContactInfo = null;
- updateSecondaryDisplayInfo();
- } else if (secondaryChanged) {
- // secondary call has changed
- mSecondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mSecondary,
- mSecondary.getState() == Call.State.INCOMING);
- updateSecondaryDisplayInfo();
- maybeStartSearch(mSecondary, false);
- maybeClearSessionModificationState(mSecondary);
- }
-
- // Start/stop timers.
- if (isPrimaryCallActive()) {
- Log.d(this, "Starting the calltime timer");
- mCallTimer.start(CALL_TIME_UPDATE_INTERVAL_MS);
- } else {
- Log.d(this, "Canceling the calltime timer");
- mCallTimer.cancel();
- ui.setPrimaryCallElapsedTime(false, 0);
- }
-
- // Set the call state
- int callState = Call.State.IDLE;
- if (mPrimary != null) {
- callState = mPrimary.getState();
- updatePrimaryCallState();
- } else {
- getUi().setCallState(
- callState,
- VideoProfile.STATE_AUDIO_ONLY,
- Call.SessionModificationState.NO_REQUEST,
- new DisconnectCause(DisconnectCause.UNKNOWN),
- null,
- null,
- null,
- false /* isWifi */,
- false /* isConference */,
- false /* isWorkCall */);
- getUi().showHdAudioIndicator(false);
- }
-
- maybeShowManageConferenceCallButton();
-
- // Hide the end call button instantly if we're receiving an incoming call.
- getUi().setEndCallButtonEnabled(shouldShowEndCallButton(mPrimary, callState),
- callState != Call.State.INCOMING /* animate */);
-
- maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
- }
-
- @Override
- public void onDetailsChanged(Call call, Details details) {
- updatePrimaryCallState();
-
- if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE) !=
- details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
- maybeShowManageConferenceCallButton();
- }
- }
-
- @Override
- public void onCallChanged(Call call) {
- // No-op; specific call updates handled elsewhere.
- }
-
- /**
- * Handles a change to the session modification state for a call. Triggers showing the progress
- * spinner, as well as updating the call state label.
- *
- * @param sessionModificationState The new session modification state.
- */
- @Override
- public void onSessionModificationStateChange(int sessionModificationState) {
- Log.d(this, "onSessionModificationStateChange : sessionModificationState = " +
- sessionModificationState);
-
- if (mPrimary == null) {
- return;
- }
- maybeShowProgressSpinner(mPrimary.getState(), sessionModificationState);
- getUi().setEndCallButtonEnabled(sessionModificationState !=
- Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
- true /* shouldAnimate */);
- updatePrimaryCallState();
- }
-
- /**
- * Handles a change to the last forwarding number by refreshing the primary call info.
- */
- @Override
- public void onLastForwardedNumberChange() {
- Log.v(this, "onLastForwardedNumberChange");
-
- if (mPrimary == null) {
- return;
- }
- updatePrimaryDisplayInfo();
- }
-
- /**
- * Handles a change to the child number by refreshing the primary call info.
- */
- @Override
- public void onChildNumberChange() {
- Log.v(this, "onChildNumberChange");
-
- if (mPrimary == null) {
- return;
- }
- updatePrimaryDisplayInfo();
- }
-
- private boolean shouldRefreshPrimaryInfo(boolean primaryChanged, CallCardUi ui,
- boolean shouldShowCallSubject) {
- if (mPrimary == null) {
- return false;
- }
- return primaryChanged ||
- ui.isManageConferenceVisible() != shouldShowManageConference() ||
- ui.isCallSubjectVisible() != shouldShowCallSubject;
- }
-
- private String getSubscriptionNumber() {
- // If it's an emergency call, and they're not populating the callback number,
- // then try to fall back to the phone sub info (to hopefully get the SIM's
- // number directly from the telephony layer).
- PhoneAccountHandle accountHandle = mPrimary.getAccountHandle();
- if (accountHandle != null) {
- TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
- PhoneAccount account = TelecomManagerCompat.getPhoneAccount(mgr, accountHandle);
- if (account != null) {
- return getNumberFromHandle(account.getSubscriptionAddress());
- }
- }
- return null;
- }
-
- private void updatePrimaryCallState() {
- if (getUi() != null && mPrimary != null) {
- boolean isWorkCall = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
- || (mPrimaryContactInfo == null ? false
- : mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
- getUi().setCallState(
- mPrimary.getState(),
- mPrimary.getVideoState(),
- mPrimary.getSessionModificationState(),
- mPrimary.getDisconnectCause(),
- getConnectionLabel(),
- getCallStateIcon(),
- getGatewayNumber(),
- mPrimary.hasProperty(Details.PROPERTY_WIFI),
- mPrimary.isConferenceCall(),
- isWorkCall);
-
- maybeShowHdAudioIcon();
- setCallbackNumber();
- }
- }
-
- /**
- * Show the HD icon if the call is active and has {@link Details#PROPERTY_HIGH_DEF_AUDIO},
- * except if the call has a last forwarded number (we will show that icon instead).
- */
- private void maybeShowHdAudioIcon() {
- boolean showHdAudioIndicator =
- isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO) &&
- TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
- getUi().showHdAudioIndicator(showHdAudioIndicator);
- }
-
- private void maybeShowSpamIconAndLabel() {
- getUi().showSpamIndicator(mPrimary.isSpam());
- }
-
- /**
- * Only show the conference call button if we can manage the conference.
- */
- private void maybeShowManageConferenceCallButton() {
- getUi().showManageConferenceCallButton(shouldShowManageConference());
- }
-
- /**
- * Determines if a pending session modification exists for the current call. If so, the
- * progress spinner is shown, and the call state is updated.
- *
- * @param callState The call state.
- * @param sessionModificationState The session modification state.
- */
- private void maybeShowProgressSpinner(int callState, int sessionModificationState) {
- final boolean show = sessionModificationState ==
- Call.SessionModificationState.WAITING_FOR_RESPONSE
- && callState == Call.State.ACTIVE;
- if (show != mSpinnerShowing) {
- getUi().setProgressSpinnerVisible(show);
- mSpinnerShowing = show;
- }
- }
-
- /**
- * Determines if the manage conference button should be visible, based on the current primary
- * call.
- *
- * @return {@code True} if the manage conference button should be visible.
- */
- private boolean shouldShowManageConference() {
- if (mPrimary == null) {
- return false;
- }
-
- return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
- && !mIsFullscreen;
- }
-
- private void setCallbackNumber() {
- String callbackNumber = null;
-
- // Show the emergency callback number if either:
- // 1. This is an emergency call.
- // 2. The phone is in Emergency Callback Mode, which means we should show the callback
- // number.
- boolean showCallbackNumber = mPrimary.hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
-
- if (mPrimary.isEmergencyCall() || showCallbackNumber) {
- callbackNumber = getSubscriptionNumber();
- } else {
- StatusHints statusHints = mPrimary.getTelecomCall().getDetails().getStatusHints();
- if (statusHints != null) {
- Bundle extras = statusHints.getExtras();
- if (extras != null) {
- callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER);
- }
- }
- }
-
- final String simNumber = TelecomManagerCompat.getLine1Number(
- InCallPresenter.getInstance().getTelecomManager(),
- InCallPresenter.getInstance().getTelephonyManager(),
- mPrimary.getAccountHandle());
- if (!showCallbackNumber && PhoneNumberUtils.compare(callbackNumber, simNumber)) {
- Log.d(this, "Numbers are the same (and callback number is not being forced to show);" +
- " not showing the callback number");
- callbackNumber = null;
- }
-
- getUi().setCallbackNumber(callbackNumber, mPrimary.isEmergencyCall() || showCallbackNumber);
- }
-
- public void updateCallTime() {
- final CallCardUi ui = getUi();
-
- if (ui == null) {
- mCallTimer.cancel();
- } else if (!isPrimaryCallActive()) {
- ui.setPrimaryCallElapsedTime(false, 0);
- mCallTimer.cancel();
- } else {
- final long callStart = mPrimary.getConnectTimeMillis();
- if (callStart > 0) {
- final long duration = System.currentTimeMillis() - callStart;
- ui.setPrimaryCallElapsedTime(true, duration);
- }
- }
- }
-
- public void onCallStateButtonTouched() {
- Intent broadcastIntent = ObjectFactory.getCallStateButtonBroadcastIntent(mContext);
- if (broadcastIntent != null) {
- Log.d(this, "Sending call state button broadcast: ", broadcastIntent);
- mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
- }
- }
-
- /**
- * Handles click on the contact photo by toggling fullscreen mode if the current call is a video
- * call.
- */
- public void onContactPhotoClick() {
- if (mPrimary != null && mPrimary.isVideoCall(mContext)) {
- InCallPresenter.getInstance().toggleFullscreenMode();
- }
- }
-
- private void maybeStartSearch(Call call, boolean isPrimary) {
- // no need to start search for conference calls which show generic info.
- if (call != null && !call.isConferenceCall()) {
- startContactInfoSearch(call, isPrimary, call.getState() == Call.State.INCOMING);
- }
- }
-
- private void maybeClearSessionModificationState(Call call) {
- if (call.getSessionModificationState() !=
- Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
- }
-
- /**
- * Starts a query for more contact data for the save primary and secondary calls.
- */
- private void startContactInfoSearch(final Call call, final boolean isPrimary,
- boolean isIncoming) {
- final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
-
- cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
- }
-
- private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
- final boolean entryMatchesExistingCall =
- (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId())) ||
- (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
- if (entryMatchesExistingCall) {
- updateContactEntry(entry, isPrimary);
- } else {
- Log.w(this, "Dropping stale contact lookup info for " + callId);
- }
-
- final Call call = CallList.getInstance().getCallById(callId);
- if (call != null) {
- call.getLogState().contactLookupResult = entry.contactLookupResult;
- }
- if (entry.contactUri != null) {
- CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
- }
- }
-
- private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
- if (getUi() == null) {
- return;
- }
-
- if (entry.photo != null) {
- if (mPrimary != null && callId.equals(mPrimary.getId())) {
- boolean showContactPhoto = !VideoCallPresenter.showIncomingVideo(
- mPrimary.getVideoState(), mPrimary.getState());
- getUi().setPrimaryImage(entry.photo, showContactPhoto);
- }
- }
- }
-
- private void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) {
- if (getUi() == null) {
- return;
- }
-
- if (mPrimary != null && callId.equals(mPrimary.getId())) {
- mPrimaryContactInfo.locationAddress = entry.locationAddress;
- updateContactInteractions();
- }
- }
-
- @Override
- public void onLocationReady() {
- // This will only update the contacts interactions data if the location returns after
- // the contact information is found.
- updateContactInteractions();
- }
-
- private void updateContactInteractions() {
- if (mPrimary != null && mPrimaryContactInfo != null
- && (mPrimaryContactInfo.locationAddress != null
- || mPrimaryContactInfo.openingHours != null)) {
- // TODO: This is hardcoded to "isBusiness" because functionality to differentiate
- // between business and personal has not yet been added.
- if (setInCallContactInteractionsType(true /* isBusiness */)) {
- getUi().setContactContextTitle(
- mInCallContactInteractions.getBusinessListHeaderView());
- }
-
- mInCallContactInteractions.setBusinessInfo(
- mPrimaryContactInfo.locationAddress,
- mDistanceHelper.calculateDistance(mPrimaryContactInfo.locationAddress),
- mPrimaryContactInfo.openingHours);
- getUi().setContactContextContent(mInCallContactInteractions.getListAdapter());
- getUi().showContactContext(mPrimary.getState() != State.INCOMING);
- } else {
- getUi().showContactContext(false);
- }
- }
-
- /**
- * Update the contact interactions type so that the correct UI is shown.
- *
- * @param isBusiness {@code true} if the interaction is a business interaction, {@code false} if
- * it is a personal contact.
- *
- * @return {@code true} if this is a new type of contact interaction (business or personal).
- * {@code false} if it hasn't changed.
- */
- private boolean setInCallContactInteractionsType(boolean isBusiness) {
- if (mInCallContactInteractions == null) {
- mInCallContactInteractions =
- new InCallContactInteractions(mContext, isBusiness);
- return true;
- }
-
- return mInCallContactInteractions.switchContactType(isBusiness);
- }
-
- private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
- if (isPrimary) {
- mPrimaryContactInfo = entry;
- updatePrimaryDisplayInfo();
- } else {
- mSecondaryContactInfo = entry;
- updateSecondaryDisplayInfo();
- }
- }
-
- /**
- * Get the highest priority call to display.
- * Goes through the calls and chooses which to return based on priority of which type of call
- * to display to the user. Callers can use the "ignore" feature to get the second best call
- * by passing a previously found primary call as ignore.
- *
- * @param ignore A call to ignore if found.
- */
- private Call getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected) {
- // Active calls come second. An active call always gets precedent.
- Call retval = callList.getActiveCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
-
- // Sometimes there is intemediate state that two calls are in active even one is about
- // to be on hold.
- retval = callList.getSecondActiveCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
-
- // Disconnected calls get primary position if there are no active calls
- // to let user know quickly what call has disconnected. Disconnected
- // calls are very short lived.
- if (!skipDisconnected) {
- retval = callList.getDisconnectingCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
- retval = callList.getDisconnectedCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
- }
-
- // Then we go to background call (calls on hold)
- retval = callList.getBackgroundCall();
- if (retval != null && retval != ignore) {
- return retval;
- }
-
- // Lastly, we go to a second background call.
- retval = callList.getSecondBackgroundCall();
-
- return retval;
- }
-
- private void updatePrimaryDisplayInfo() {
- final CallCardUi ui = getUi();
- if (ui == null) {
- // TODO: May also occur if search result comes back after ui is destroyed. Look into
- // removing that case completely.
- Log.d(TAG, "updatePrimaryDisplayInfo called but ui is null!");
- return;
- }
-
- if (mPrimary == null) {
- // Clear the primary display info.
- ui.setPrimary(null, null, false, null, null, false, false, false);
- return;
- }
-
- // Hide the contact photo if we are in a video call and the incoming video surface is
- // showing.
- boolean showContactPhoto = !VideoCallPresenter
- .showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
-
- // Call placed through a work phone account.
- boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
-
- if (mPrimary.isConferenceCall()) {
- Log.d(TAG, "Update primary display info for conference call.");
-
- ui.setPrimary(
- null /* number */,
- getConferenceString(mPrimary),
- false /* nameIsNumber */,
- null /* label */,
- getConferencePhoto(mPrimary),
- false /* isSipCall */,
- showContactPhoto,
- hasWorkCallProperty);
- } else if (mPrimaryContactInfo != null) {
- Log.d(TAG, "Update primary display info for " + mPrimaryContactInfo);
-
- String name = getNameForCall(mPrimaryContactInfo);
- String number;
-
- boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
- boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
- boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
-
- if (isCallSubjectShown) {
- ui.setCallSubject(mPrimary.getCallSubject());
- } else {
- ui.setCallSubject(null);
- }
-
- if (isCallSubjectShown) {
- number = null;
- } else if (isChildNumberShown) {
- number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
- } else if (isForwardedNumberShown) {
- // Use last forwarded number instead of second line, if present.
- number = mPrimary.getLastForwardedNumber();
- } else {
- number = getNumberForCall(mPrimaryContactInfo);
- }
-
- ui.showForwardIndicator(isForwardedNumberShown);
- maybeShowHdAudioIcon();
-
- boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
- // Call with caller that is a work contact.
- boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
- ui.setPrimary(
- number,
- name,
- nameIsNumber,
- isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
- mPrimaryContactInfo.photo,
- mPrimaryContactInfo.isSipCall,
- showContactPhoto,
- hasWorkCallProperty || isWorkContact);
-
- updateContactInteractions();
- } else {
- // Clear the primary display info.
- ui.setPrimary(null, null, false, null, null, false, false, false);
- }
-
- if (mEmergencyCallListener != null) {
- boolean isEmergencyCall = mPrimary.isEmergencyCall();
- mEmergencyCallListener.onCallUpdated((BaseFragment) ui, isEmergencyCall);
- }
- maybeShowSpamIconAndLabel();
- }
-
- private void updateSecondaryDisplayInfo() {
- final CallCardUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- if (mSecondary == null) {
- // Clear the secondary display info.
- ui.setSecondary(false, null, false, null, null, false /* isConference */,
- false /* isVideoCall */, mIsFullscreen);
- return;
- }
-
- if (mSecondary.isConferenceCall()) {
- ui.setSecondary(
- true /* show */,
- getConferenceString(mSecondary),
- false /* nameIsNumber */,
- null /* label */,
- getCallProviderLabel(mSecondary),
- true /* isConference */,
- mSecondary.isVideoCall(mContext),
- mIsFullscreen);
- } else if (mSecondaryContactInfo != null) {
- Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
- String name = getNameForCall(mSecondaryContactInfo);
- boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
- ui.setSecondary(
- true /* show */,
- name,
- nameIsNumber,
- mSecondaryContactInfo.label,
- getCallProviderLabel(mSecondary),
- false /* isConference */,
- mSecondary.isVideoCall(mContext),
- mIsFullscreen);
- } else {
- // Clear the secondary display info.
- ui.setSecondary(false, null, false, null, null, false /* isConference */,
- false /* isVideoCall */, mIsFullscreen);
- }
- }
-
-
- /**
- * Gets the phone account to display for a call.
- */
- private PhoneAccount getAccountForCall(Call call) {
- PhoneAccountHandle accountHandle = call.getAccountHandle();
- if (accountHandle == null) {
- return null;
- }
- return TelecomManagerCompat.getPhoneAccount(
- InCallPresenter.getInstance().getTelecomManager(),
- accountHandle);
- }
-
- /**
- * Returns the gateway number for any existing outgoing call.
- */
- private String getGatewayNumber() {
- if (hasOutgoingGatewayCall()) {
- return getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
- }
- return null;
- }
-
- /**
- * Return the string label to represent the call provider
- */
- private String getCallProviderLabel(Call call) {
- PhoneAccount account = getAccountForCall(call);
- TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
- if (account != null && !TextUtils.isEmpty(account.getLabel())
- && TelecomManagerCompat.getCallCapablePhoneAccounts(mgr).size() > 1) {
- return account.getLabel().toString();
- }
- return null;
- }
-
- /**
- * Returns the label (line of text above the number/name) for any given call.
- * For example, "calling via [Account/Google Voice]" for outgoing calls.
- */
- private String getConnectionLabel() {
- StatusHints statusHints = mPrimary.getTelecomCall().getDetails().getStatusHints();
- if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
- return statusHints.getLabel().toString();
- }
-
- if (hasOutgoingGatewayCall() && getUi() != null) {
- // Return the label for the gateway app on outgoing calls.
- final PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(
- mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
- return pm.getApplicationLabel(info).toString();
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(this, "Gateway Application Not Found.", e);
- return null;
- }
- }
- return getCallProviderLabel(mPrimary);
- }
-
- private Drawable getCallStateIcon() {
- // Return connection icon if one exists.
- StatusHints statusHints = mPrimary.getTelecomCall().getDetails().getStatusHints();
- if (statusHints != null && statusHints.getIcon() != null) {
- Drawable icon = statusHints.getIcon().loadDrawable(mContext);
- if (icon != null) {
- return icon;
- }
- }
-
- return null;
- }
-
- private boolean hasOutgoingGatewayCall() {
- // We only display the gateway information while STATE_DIALING so return false for any other
- // call state.
- // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
- // is also called after a contact search completes (call is not present yet). Split the
- // UI update so it can receive independent updates.
- if (mPrimary == null) {
- return false;
- }
- return Call.State.isDialing(mPrimary.getState()) && mPrimary.getGatewayInfo() != null &&
- !mPrimary.getGatewayInfo().isEmpty();
- }
-
- /**
- * Gets the name to display for the call.
- */
- @NeededForTesting
- String getNameForCall(ContactCacheEntry contactInfo) {
- String preferredName = ContactDisplayUtils.getPreferredDisplayName(
- contactInfo.namePrimary,
- contactInfo.nameAlternative,
- mContactsPreferences);
- if (TextUtils.isEmpty(preferredName)) {
- return contactInfo.number;
- }
- return preferredName;
- }
-
- /**
- * Gets the number to display for a call.
- */
- @NeededForTesting
- String getNumberForCall(ContactCacheEntry contactInfo) {
- // If the name is empty, we use the number for the name...so don't show a second
- // number in the number field
- String preferredName = ContactDisplayUtils.getPreferredDisplayName(
- contactInfo.namePrimary,
- contactInfo.nameAlternative,
- mContactsPreferences);
- if (TextUtils.isEmpty(preferredName)) {
- return contactInfo.location;
- }
- return contactInfo.number;
- }
-
- public void secondaryInfoClicked() {
- if (mSecondary == null) {
- Log.w(this, "Secondary info clicked but no secondary call.");
- return;
- }
-
- Log.i(this, "Swapping call to foreground: " + mSecondary);
- TelecomAdapter.getInstance().unholdCall(mSecondary.getId());
- }
-
- public void endCallClicked() {
- if (mPrimary == null) {
- return;
- }
-
- Log.i(this, "Disconnecting call: " + mPrimary);
- final String callId = mPrimary.getId();
- mPrimary.setState(Call.State.DISCONNECTING);
- CallList.getInstance().onUpdate(mPrimary);
- TelecomAdapter.getInstance().disconnectCall(callId);
- }
-
- private String getNumberFromHandle(Uri handle) {
- return handle == null ? "" : handle.getSchemeSpecificPart();
- }
-
- /**
- * Handles a change to the fullscreen mode of the in-call UI.
- *
- * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
- */
- @Override
- public void onFullscreenModeChanged(boolean isFullscreenMode) {
- mIsFullscreen = isFullscreenMode;
- final CallCardUi ui = getUi();
- if (ui == null) {
- return;
- }
- ui.setCallCardVisible(!isFullscreenMode);
- ui.setSecondaryInfoVisible(!isFullscreenMode);
- maybeShowManageConferenceCallButton();
- }
-
- @Override
- public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
- // No-op - the Call Card is the origin of this event.
- }
-
- private boolean isPrimaryCallActive() {
- return mPrimary != null && mPrimary.getState() == Call.State.ACTIVE;
- }
-
- private String getConferenceString(Call call) {
- boolean isGenericConference = call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE);
- Log.v(this, "getConferenceString: " + isGenericConference);
-
- final int resId = isGenericConference
- ? R.string.card_title_in_call : R.string.card_title_conf_call;
- return mContext.getResources().getString(resId);
- }
-
- private Drawable getConferencePhoto(Call call) {
- boolean isGenericConference = call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE);
- Log.v(this, "getConferencePhoto: " + isGenericConference);
-
- final int resId = isGenericConference
- ? R.drawable.img_phone : R.drawable.img_conference;
- Drawable photo = mContext.getResources().getDrawable(resId);
- photo.setAutoMirrored(true);
- return photo;
- }
-
- private boolean shouldShowEndCallButton(Call primary, int callState) {
- if (primary == null) {
- return false;
- }
- if ((!Call.State.isConnectingOrConnected(callState)
- && callState != Call.State.DISCONNECTING) || callState == Call.State.INCOMING) {
- return false;
- }
- if (mPrimary.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- return false;
- }
- return true;
- }
-
- private void maybeSendAccessibilityEvent(InCallState oldState, InCallState newState,
- boolean primaryChanged) {
- if (mContext == null) {
- return;
- }
- final AccessibilityManager am = (AccessibilityManager) mContext.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- if (!am.isEnabled()) {
- return;
- }
- // Announce the current call if it's new incoming/outgoing call or primary call is changed
- // due to switching calls between two ongoing calls (one is on hold).
- if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
- || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
- || primaryChanged) {
- if (getUi() != null) {
- getUi().sendAccessibilityAnnouncement();
- }
- }
- }
-
- /**
- * Determines whether the call subject should be visible on the UI. For the call subject to be
- * visible, the call has to be in an incoming or waiting state, and the subject must not be
- * empty.
- *
- * @param call The call.
- * @return {@code true} if the subject should be shown, {@code false} otherwise.
- */
- private boolean shouldShowCallSubject(Call call) {
- if (call == null) {
- return false;
- }
-
- boolean isIncomingOrWaiting = mPrimary.getState() == Call.State.INCOMING ||
- mPrimary.getState() == Call.State.CALL_WAITING;
- return isIncomingOrWaiting && !TextUtils.isEmpty(call.getCallSubject()) &&
- call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED &&
- call.isCallSubjectSupported();
- }
-
- /**
- * Determines whether the "note sent" toast should be shown. It should be shown for a new
- * outgoing call with a subject.
- *
- * @param call The call
- * @return {@code true} if the toast should be shown, {@code false} otherwise.
- */
- private boolean shouldShowNoteSentToast(Call call) {
- return call != null && hasCallSubject(call) && (call.getState() == Call.State.DIALING
- || call.getState() == Call.State.CONNECTING);
- }
-
- private static boolean hasCallSubject(Call call) {
- return !TextUtils.isEmpty(call.getTelecomCall().getDetails().getIntentExtras()
- .getString(TelecomManager.EXTRA_CALL_SUBJECT));
- }
-
- public interface CallCardUi extends Ui {
- void setVisible(boolean on);
- void setContactContextTitle(View listHeaderView);
- void setContactContextContent(ListAdapter listAdapter);
- void showContactContext(boolean show);
- void setCallCardVisible(boolean visible);
- void setPrimary(String number, String name, boolean nameIsNumber, String label,
- Drawable photo, boolean isSipCall, boolean isContactPhotoShown, boolean isWorkCall);
- void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
- String providerLabel, boolean isConference, boolean isVideoCall,
- boolean isFullscreen);
- void setSecondaryInfoVisible(boolean visible);
- void setCallState(int state, int videoState, int sessionModificationState,
- DisconnectCause disconnectCause, String connectionLabel,
- Drawable connectionIcon, String gatewayNumber, boolean isWifi,
- boolean isConference, boolean isWorkCall);
- void setPrimaryCallElapsedTime(boolean show, long duration);
- void setPrimaryName(String name, boolean nameIsNumber);
- void setPrimaryImage(Drawable image, boolean isVisible);
- void setPrimaryPhoneNumber(String phoneNumber);
- void setPrimaryLabel(String label);
- void setEndCallButtonEnabled(boolean enabled, boolean animate);
- void setCallbackNumber(String number, boolean isEmergencyCalls);
- void setCallSubject(String callSubject);
- void setProgressSpinnerVisible(boolean visible);
- void showHdAudioIndicator(boolean visible);
- void showForwardIndicator(boolean visible);
- void showSpamIndicator(boolean visible);
- void showManageConferenceCallButton(boolean visible);
- boolean isManageConferenceVisible();
- boolean isCallSubjectVisible();
- void animateForNewOutgoingCall();
- void sendAccessibilityAnnouncement();
- void showNoteSentToast();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
deleted file mode 100644
index 48870f68a..000000000
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ /dev/null
@@ -1,695 +0,0 @@
-/*
- * Copyright (C) 2013 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.os.Handler;
-import android.os.Message;
-import android.os.Trace;
-import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.service.ExtendedCallInfoService;
-import com.android.incallui.util.TelecomCallUtil;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Maintains the list of active calls and notifies interested classes of changes to the call list
- * as they are received from the telephony stack. Primary listener of changes to this class is
- * InCallPresenter.
- */
-public class CallList {
-
- private static final int DISCONNECTED_CALL_SHORT_TIMEOUT_MS = 200;
- private static final int DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS = 2000;
- private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000;
-
- private static final int EVENT_DISCONNECTED_TIMEOUT = 1;
- private static final long BLOCK_QUERY_TIMEOUT_MS = 1000;
-
- private static CallList sInstance = new CallList();
-
- private final HashMap<String, Call> mCallById = new HashMap<>();
- private final HashMap<android.telecom.Call, Call> mCallByTelecomCall = new HashMap<>();
- private final HashMap<String, List<String>> mCallTextReponsesMap = Maps.newHashMap();
- /**
- * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
- * load factor before resizing, 1 means we only expect a single thread to
- * access the map so make only a single shard
- */
- private final Set<Listener> mListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
- private final HashMap<String, List<CallUpdateListener>> mCallUpdateListenerMap = Maps
- .newHashMap();
- private final Set<Call> mPendingDisconnectCalls = Collections.newSetFromMap(
- new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
- private ExtendedCallInfoService mExtendedCallInfoService;
-
- /**
- * Static singleton accessor method.
- */
- public static CallList getInstance() {
- return sInstance;
- }
-
- /**
- * USED ONLY FOR TESTING
- * Testing-only constructor. Instance should only be acquired through getInstance().
- */
- @NeededForTesting
- CallList() {
- }
-
- public void onCallAdded(final android.telecom.Call telecomCall, LatencyReport latencyReport) {
- Trace.beginSection("onCallAdded");
- final Call call = new Call(telecomCall, latencyReport);
- Log.d(this, "onCallAdded: callState=" + call.getState());
-
- if (call.getState() == Call.State.INCOMING ||
- call.getState() == Call.State.CALL_WAITING) {
- onIncoming(call, call.getCannedSmsResponses());
- if (mExtendedCallInfoService != null) {
- String number = TelecomCallUtil.getNumber(telecomCall);
- mExtendedCallInfoService.getExtendedCallInfo(number, null,
- new ExtendedCallInfoService.Listener() {
- @Override
- public void onComplete(boolean isSpam) {
- call.setSpam(isSpam);
- onUpdate(call);
- }
- });
- }
- } else {
- onUpdate(call);
- }
-
- call.logCallInitiationType();
- Trace.endSection();
- }
-
- public void onCallRemoved(android.telecom.Call telecomCall) {
- if (mCallByTelecomCall.containsKey(telecomCall)) {
- Call call = mCallByTelecomCall.get(telecomCall);
- Logger.logCall(call);
- if (updateCallInMap(call)) {
- Log.w(this, "Removing call not previously disconnected " + call.getId());
- }
- updateCallTextMap(call, null);
- }
- }
-
- /**
- * Called when a single call disconnects.
- */
- public void onDisconnect(Call call) {
- if (updateCallInMap(call)) {
- Log.i(this, "onDisconnect: " + call);
- // notify those listening for changes on this specific change
- notifyCallUpdateListeners(call);
- // notify those listening for all disconnects
- notifyListenersOfDisconnect(call);
- }
- }
-
- /**
- * Called when a single call has changed.
- */
- public void onIncoming(Call call, List<String> textMessages) {
- if (updateCallInMap(call)) {
- Log.i(this, "onIncoming - " + call);
- }
- updateCallTextMap(call, textMessages);
-
- for (Listener listener : mListeners) {
- listener.onIncomingCall(call);
- }
- }
-
- public void onUpgradeToVideo(Call call){
- Log.d(this, "onUpgradeToVideo call=" + call);
- for (Listener listener : mListeners) {
- listener.onUpgradeToVideo(call);
- }
- }
- /**
- * Called when a single call has changed.
- */
- public void onUpdate(Call call) {
- Trace.beginSection("onUpdate");
- onUpdateCall(call);
- notifyGenericListeners();
- Trace.endSection();
- }
-
- /**
- * Called when a single call has changed session modification state.
- *
- * @param call The call.
- * @param sessionModificationState The new session modification state.
- */
- public void onSessionModificationStateChange(Call call, int sessionModificationState) {
- final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
- if (listeners != null) {
- for (CallUpdateListener listener : listeners) {
- listener.onSessionModificationStateChange(sessionModificationState);
- }
- }
- }
-
- /**
- * Called when the last forwarded number changes for a call. With IMS, the last forwarded
- * number changes due to a supplemental service notification, so it is not pressent at the
- * start of the call.
- *
- * @param call The call.
- */
- public void onLastForwardedNumberChange(Call call) {
- final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
- if (listeners != null) {
- for (CallUpdateListener listener : listeners) {
- listener.onLastForwardedNumberChange();
- }
- }
- }
-
- /**
- * Called when the child number changes for a call. The child number can be received after a
- * call is initially set up, so we need to be able to inform listeners of the change.
- *
- * @param call The call.
- */
- public void onChildNumberChange(Call call) {
- final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
- if (listeners != null) {
- for (CallUpdateListener listener : listeners) {
- listener.onChildNumberChange();
- }
- }
- }
-
- public void notifyCallUpdateListeners(Call call) {
- final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
- if (listeners != null) {
- for (CallUpdateListener listener : listeners) {
- listener.onCallChanged(call);
- }
- }
- }
-
- /**
- * Add a call update listener for a call id.
- *
- * @param callId The call id to get updates for.
- * @param listener The listener to add.
- */
- public void addCallUpdateListener(String callId, CallUpdateListener listener) {
- List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
- if (listeners == null) {
- listeners = new CopyOnWriteArrayList<CallUpdateListener>();
- mCallUpdateListenerMap.put(callId, listeners);
- }
- listeners.add(listener);
- }
-
- /**
- * Remove a call update listener for a call id.
- *
- * @param callId The call id to remove the listener for.
- * @param listener The listener to remove.
- */
- public void removeCallUpdateListener(String callId, CallUpdateListener listener) {
- List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
- if (listeners != null) {
- listeners.remove(listener);
- }
- }
-
- public void addListener(Listener listener) {
- Preconditions.checkNotNull(listener);
-
- mListeners.add(listener);
-
- // Let the listener know about the active calls immediately.
- listener.onCallListChange(this);
- }
-
- public void removeListener(Listener listener) {
- if (listener != null) {
- mListeners.remove(listener);
- }
- }
-
- /**
- * TODO: Change so that this function is not needed. Instead of assuming there is an active
- * call, the code should rely on the status of a specific Call and allow the presenters to
- * update the Call object when the active call changes.
- */
- public Call getIncomingOrActive() {
- Call retval = getIncomingCall();
- if (retval == null) {
- retval = getActiveCall();
- }
- return retval;
- }
-
- public Call getOutgoingOrActive() {
- Call retval = getOutgoingCall();
- if (retval == null) {
- retval = getActiveCall();
- }
- return retval;
- }
-
- /**
- * A call that is waiting for {@link PhoneAccount} selection
- */
- public Call getWaitingForAccountCall() {
- return getFirstCallWithState(Call.State.SELECT_PHONE_ACCOUNT);
- }
-
- public Call getPendingOutgoingCall() {
- return getFirstCallWithState(Call.State.CONNECTING);
- }
-
- public Call getOutgoingCall() {
- Call call = getFirstCallWithState(Call.State.DIALING);
- if (call == null) {
- call = getFirstCallWithState(Call.State.REDIALING);
- }
- return call;
- }
-
- public Call getActiveCall() {
- return getFirstCallWithState(Call.State.ACTIVE);
- }
-
- public Call getSecondActiveCall() {
- return getCallWithState(Call.State.ACTIVE, 1);
- }
-
- public Call getBackgroundCall() {
- return getFirstCallWithState(Call.State.ONHOLD);
- }
-
- public Call getDisconnectedCall() {
- return getFirstCallWithState(Call.State.DISCONNECTED);
- }
-
- public Call getDisconnectingCall() {
- return getFirstCallWithState(Call.State.DISCONNECTING);
- }
-
- public Call getSecondBackgroundCall() {
- return getCallWithState(Call.State.ONHOLD, 1);
- }
-
- public Call getActiveOrBackgroundCall() {
- Call call = getActiveCall();
- if (call == null) {
- call = getBackgroundCall();
- }
- return call;
- }
-
- public Call getIncomingCall() {
- Call call = getFirstCallWithState(Call.State.INCOMING);
- if (call == null) {
- call = getFirstCallWithState(Call.State.CALL_WAITING);
- }
-
- return call;
- }
-
- public Call getFirstCall() {
- Call result = getIncomingCall();
- if (result == null) {
- result = getPendingOutgoingCall();
- }
- if (result == null) {
- result = getOutgoingCall();
- }
- if (result == null) {
- result = getFirstCallWithState(Call.State.ACTIVE);
- }
- if (result == null) {
- result = getDisconnectingCall();
- }
- if (result == null) {
- result = getDisconnectedCall();
- }
- return result;
- }
-
- public boolean hasLiveCall() {
- Call call = getFirstCall();
- if (call == null) {
- return false;
- }
- return call != getDisconnectingCall() && call != getDisconnectedCall();
- }
-
- /**
- * Returns the first call found in the call map with the specified call modification state.
- * @param state The session modification state to search for.
- * @return The first call with the specified state.
- */
- public Call getVideoUpgradeRequestCall() {
- for(Call call : mCallById.values()) {
- if (call.getSessionModificationState() ==
- Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- return call;
- }
- }
- return null;
- }
-
- public Call getCallById(String callId) {
- return mCallById.get(callId);
- }
-
- public Call getCallByTelecomCall(android.telecom.Call telecomCall) {
- return mCallByTelecomCall.get(telecomCall);
- }
-
- public List<String> getTextResponses(String callId) {
- return mCallTextReponsesMap.get(callId);
- }
-
- /**
- * Returns first call found in the call map with the specified state.
- */
- public Call getFirstCallWithState(int state) {
- return getCallWithState(state, 0);
- }
-
- /**
- * Returns the [position]th call found in the call map with the specified state.
- * TODO: Improve this logic to sort by call time.
- */
- public Call getCallWithState(int state, int positionToFind) {
- Call retval = null;
- int position = 0;
- for (Call call : mCallById.values()) {
- if (call.getState() == state) {
- if (position >= positionToFind) {
- retval = call;
- break;
- } else {
- position++;
- }
- }
- }
-
- return retval;
- }
-
- /**
- * This is called when the service disconnects, either expectedly or unexpectedly.
- * For the expected case, it's because we have no calls left. For the unexpected case,
- * it is likely a crash of phone and we need to clean up our calls manually. Without phone,
- * there can be no active calls, so this is relatively safe thing to do.
- */
- public void clearOnDisconnect() {
- for (Call call : mCallById.values()) {
- final int state = call.getState();
- if (state != Call.State.IDLE &&
- state != Call.State.INVALID &&
- state != Call.State.DISCONNECTED) {
-
- call.setState(Call.State.DISCONNECTED);
- call.setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN));
- updateCallInMap(call);
- }
- }
- notifyGenericListeners();
- }
-
- /**
- * Called when the user has dismissed an error dialog. This indicates acknowledgement of
- * the disconnect cause, and that any pending disconnects should immediately occur.
- */
- public void onErrorDialogDismissed() {
- final Iterator<Call> iterator = mPendingDisconnectCalls.iterator();
- while (iterator.hasNext()) {
- Call call = iterator.next();
- iterator.remove();
- finishDisconnectedCall(call);
- }
- }
-
- /**
- * Processes an update for a single call.
- *
- * @param call The call to update.
- */
- private void onUpdateCall(Call call) {
- Log.d(this, "\t" + call);
- if (updateCallInMap(call)) {
- Log.i(this, "onUpdate - " + call);
- }
- updateCallTextMap(call, call.getCannedSmsResponses());
- notifyCallUpdateListeners(call);
- }
-
- /**
- * Sends a generic notification to all listeners that something has changed.
- * It is up to the listeners to call back to determine what changed.
- */
- private void notifyGenericListeners() {
- for (Listener listener : mListeners) {
- listener.onCallListChange(this);
- }
- }
-
- private void notifyListenersOfDisconnect(Call call) {
- for (Listener listener : mListeners) {
- listener.onDisconnect(call);
- }
- }
-
- /**
- * Updates the call entry in the local map.
- * @return false if no call previously existed and no call was added, otherwise true.
- */
- private boolean updateCallInMap(Call call) {
- Preconditions.checkNotNull(call);
-
- boolean updated = false;
-
- if (call.getState() == Call.State.DISCONNECTED) {
- // update existing (but do not add!!) disconnected calls
- if (mCallById.containsKey(call.getId())) {
- // For disconnected calls, we want to keep them alive for a few seconds so that the
- // UI has a chance to display anything it needs when a call is disconnected.
-
- // Set up a timer to destroy the call after X seconds.
- final Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call);
- mHandler.sendMessageDelayed(msg, getDelayForDisconnect(call));
- mPendingDisconnectCalls.add(call);
-
- mCallById.put(call.getId(), call);
- mCallByTelecomCall.put(call.getTelecomCall(), call);
- updated = true;
- }
- } else if (!isCallDead(call)) {
- mCallById.put(call.getId(), call);
- mCallByTelecomCall.put(call.getTelecomCall(), call);
- updated = true;
- } else if (mCallById.containsKey(call.getId())) {
- mCallById.remove(call.getId());
- mCallByTelecomCall.remove(call.getTelecomCall());
- updated = true;
- }
-
- return updated;
- }
-
- private int getDelayForDisconnect(Call call) {
- Preconditions.checkState(call.getState() == Call.State.DISCONNECTED);
-
-
- final int cause = call.getDisconnectCause().getCode();
- final int delay;
- switch (cause) {
- case DisconnectCause.LOCAL:
- delay = DISCONNECTED_CALL_SHORT_TIMEOUT_MS;
- break;
- case DisconnectCause.REMOTE:
- case DisconnectCause.ERROR:
- delay = DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS;
- break;
- case DisconnectCause.REJECTED:
- case DisconnectCause.MISSED:
- case DisconnectCause.CANCELED:
- // no delay for missed/rejected incoming calls and canceled outgoing calls.
- delay = 0;
- break;
- default:
- delay = DISCONNECTED_CALL_LONG_TIMEOUT_MS;
- break;
- }
-
- return delay;
- }
-
- private void updateCallTextMap(Call call, List<String> textResponses) {
- Preconditions.checkNotNull(call);
-
- if (!isCallDead(call)) {
- if (textResponses != null) {
- mCallTextReponsesMap.put(call.getId(), textResponses);
- }
- } else if (mCallById.containsKey(call.getId())) {
- mCallTextReponsesMap.remove(call.getId());
- }
- }
-
- private boolean isCallDead(Call call) {
- final int state = call.getState();
- return Call.State.IDLE == state || Call.State.INVALID == state;
- }
-
- /**
- * Sets up a call for deletion and notifies listeners of change.
- */
- private void finishDisconnectedCall(Call call) {
- if (mPendingDisconnectCalls.contains(call)) {
- mPendingDisconnectCalls.remove(call);
- }
- call.setState(Call.State.IDLE);
- updateCallInMap(call);
- notifyGenericListeners();
- }
-
- /**
- * Notifies all video calls of a change in device orientation.
- *
- * @param rotation The new rotation angle (in degrees).
- */
- public void notifyCallsOfDeviceRotation(int rotation) {
- for (Call call : mCallById.values()) {
- // First, ensure that the call videoState has video enabled (there is no need to set
- // device orientation on a voice call which has not yet been upgraded to video).
- // Second, ensure a VideoCall is set on the call so that the change can be sent to the
- // provider (a VideoCall can be present for a call that does not currently have video,
- // but can be upgraded to video).
-
- // NOTE: is it necessary to use this order because getVideoCall references the class
- // VideoProfile which is not available on APIs <23 (M).
- if (VideoUtils.isVideoCall(call) && call.getVideoCall() != null) {
- call.getVideoCall().setDeviceOrientation(rotation);
- }
- }
- }
-
- /**
- * Handles the timeout for destroying disconnected calls.
- */
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_DISCONNECTED_TIMEOUT:
- Log.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj);
- finishDisconnectedCall((Call) msg.obj);
- break;
- default:
- Log.wtf(this, "Message not expected: " + msg.what);
- break;
- }
- }
- };
-
- public void setExtendedCallInfoService(ExtendedCallInfoService service) {
- mExtendedCallInfoService = service;
- }
-
- public void onInCallUiShown(boolean forFullScreenIntent) {
- for (Call call : mCallById.values()) {
- call.getLatencyReport().onInCallUiShown(forFullScreenIntent);
- }
- }
-
- /**
- * Listener interface for any class that wants to be notified of changes
- * to the call list.
- */
- public interface Listener {
- /**
- * Called when a new incoming call comes in.
- * This is the only method that gets called for incoming calls. Listeners
- * that want to perform an action on incoming call should respond in this method
- * because {@link #onCallListChange} does not automatically get called for
- * incoming calls.
- */
- public void onIncomingCall(Call call);
- /**
- * Called when a new modify call request comes in
- * This is the only method that gets called for modify requests.
- */
- public void onUpgradeToVideo(Call call);
- /**
- * Called anytime there are changes to the call list. The change can be switching call
- * states, updating information, etc. This method will NOT be called for new incoming
- * calls and for calls that switch to disconnected state. Listeners must add actions
- * to those method implementations if they want to deal with those actions.
- */
- public void onCallListChange(CallList callList);
-
- /**
- * Called when a call switches to the disconnected state. This is the only method
- * that will get called upon disconnection.
- */
- public void onDisconnect(Call call);
-
-
- }
-
- public interface CallUpdateListener {
- // TODO: refactor and limit arg to be call state. Caller info is not needed.
- public void onCallChanged(Call call);
-
- /**
- * Notifies of a change to the session modification state for a call.
- *
- * @param sessionModificationState The new session modification state.
- */
- public void onSessionModificationStateChange(int sessionModificationState);
-
- /**
- * Notifies of a change to the last forwarded number for a call.
- */
- public void onLastForwardedNumberChange();
-
- /**
- * Notifies of a change to the child number for a call.
- */
- public void onChildNumberChange();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallTimer.java b/InCallUI/src/com/android/incallui/CallTimer.java
deleted file mode 100644
index d65e63373..000000000
--- a/InCallUI/src/com/android/incallui/CallTimer.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2013 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 com.google.common.base.Preconditions;
-
-import android.os.Handler;
-import android.os.SystemClock;
-
-/**
- * Helper class used to keep track of events requiring regular intervals.
- */
-public class CallTimer extends Handler {
- private Runnable mInternalCallback;
- private Runnable mCallback;
- private long mLastReportedTime;
- private long mInterval;
- private boolean mRunning;
-
- public CallTimer(Runnable callback) {
- Preconditions.checkNotNull(callback);
-
- mInterval = 0;
- mLastReportedTime = 0;
- mRunning = false;
- mCallback = callback;
- mInternalCallback = new CallTimerCallback();
- }
-
- public boolean start(long interval) {
- if (interval <= 0) {
- return false;
- }
-
- // cancel any previous timer
- cancel();
-
- mInterval = interval;
- mLastReportedTime = SystemClock.uptimeMillis();
-
- mRunning = true;
- periodicUpdateTimer();
-
- return true;
- }
-
- public void cancel() {
- removeCallbacks(mInternalCallback);
- mRunning = false;
- }
-
- private void periodicUpdateTimer() {
- if (!mRunning) {
- return;
- }
-
- final long now = SystemClock.uptimeMillis();
- long nextReport = mLastReportedTime + mInterval;
- while (now >= nextReport) {
- nextReport += mInterval;
- }
-
- postAtTime(mInternalCallback, nextReport);
- mLastReportedTime = nextReport;
-
- // Run the callback
- mCallback.run();
- }
-
- private class CallTimerCallback implements Runnable {
- @Override
- public void run() {
- periodicUpdateTimer();
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallerInfo.java b/InCallUI/src/com/android/incallui/CallerInfo.java
deleted file mode 100644
index f3d0e0763..000000000
--- a/InCallUI/src/com/android/incallui/CallerInfo.java
+++ /dev/null
@@ -1,585 +0,0 @@
-/*
- * Copyright (C) 2006 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 com.android.dialer.util.PhoneLookupUtil;
-import com.google.common.primitives.Longs;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.RawContacts;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.contacts.common.compat.PhoneLookupSdkCompat;
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.ContactsUtils.UserType;
-import com.android.contacts.common.util.PhoneNumberHelper;
-import com.android.contacts.common.util.TelephonyManagerUtils;
-import com.android.dialer.R;
-import com.android.dialer.calllog.ContactInfoHelper;
-
-/**
- * Looks up caller information for the given phone number.
- */
-public class CallerInfo {
- private static final String TAG = "CallerInfo";
-
- // We should always use this projection starting from NYC onward.
- private static final String[] DEFAULT_PHONELOOKUP_PROJECTION = new String[] {
- PhoneLookupSdkCompat.CONTACT_ID,
- PhoneLookup.DISPLAY_NAME,
- PhoneLookup.LOOKUP_KEY,
- PhoneLookup.NUMBER,
- PhoneLookup.NORMALIZED_NUMBER,
- PhoneLookup.LABEL,
- PhoneLookup.TYPE,
- PhoneLookup.PHOTO_URI,
- PhoneLookup.CUSTOM_RINGTONE,
- PhoneLookup.SEND_TO_VOICEMAIL
- };
-
- // In pre-N, contact id is stored in {@link PhoneLookup._ID} in non-sip query.
- private static final String[] BACKWARD_COMPATIBLE_NON_SIP_DEFAULT_PHONELOOKUP_PROJECTION =
- new String[] {
- PhoneLookup._ID,
- PhoneLookup.DISPLAY_NAME,
- PhoneLookup.LOOKUP_KEY,
- PhoneLookup.NUMBER,
- PhoneLookup.NORMALIZED_NUMBER,
- PhoneLookup.LABEL,
- PhoneLookup.TYPE,
- PhoneLookup.PHOTO_URI,
- PhoneLookup.CUSTOM_RINGTONE,
- PhoneLookup.SEND_TO_VOICEMAIL
- };
-
- public static String[] getDefaultPhoneLookupProjection(Uri phoneLookupUri) {
- if (CompatUtils.isNCompatible()) {
- return DEFAULT_PHONELOOKUP_PROJECTION;
- }
- // Pre-N
- boolean isSip = phoneLookupUri.getBooleanQueryParameter(
- ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, false);
- return (isSip) ? DEFAULT_PHONELOOKUP_PROJECTION
- : BACKWARD_COMPATIBLE_NON_SIP_DEFAULT_PHONELOOKUP_PROJECTION;
- }
-
- /**
- * Please note that, any one of these member variables can be null,
- * and any accesses to them should be prepared to handle such a case.
- *
- * Also, it is implied that phoneNumber is more often populated than
- * name is, (think of calls being dialed/received using numbers where
- * names are not known to the device), so phoneNumber should serve as
- * a dependable fallback when name is unavailable.
- *
- * One other detail here is that this CallerInfo object reflects
- * information found on a connection, it is an OUTPUT that serves
- * mainly to display information to the user. In no way is this object
- * used as input to make a connection, so we can choose to display
- * whatever human-readable text makes sense to the user for a
- * connection. This is especially relevant for the phone number field,
- * since it is the one field that is most likely exposed to the user.
- *
- * As an example:
- * 1. User dials "911"
- * 2. Device recognizes that this is an emergency number
- * 3. We use the "Emergency Number" string instead of "911" in the
- * phoneNumber field.
- *
- * What we're really doing here is treating phoneNumber as an essential
- * field here, NOT name. We're NOT always guaranteed to have a name
- * for a connection, but the number should be displayable.
- */
- public String name;
- public String nameAlternative;
- public String phoneNumber;
- public String normalizedNumber;
- public String forwardingNumber;
- public String geoDescription;
-
- public String cnapName;
- public int numberPresentation;
- public int namePresentation;
- public boolean contactExists;
-
- public String phoneLabel;
- /* Split up the phoneLabel into number type and label name */
- public int numberType;
- public String numberLabel;
-
- public int photoResource;
-
- // Contact ID, which will be 0 if a contact comes from the corp CP2.
- public long contactIdOrZero;
- public String lookupKeyOrNull;
- public boolean needUpdate;
- public Uri contactRefUri;
- public @UserType long userType;
-
- /**
- * Contact display photo URI. If a contact has no display photo but a thumbnail, it'll be
- * the thumbnail URI instead.
- */
- public Uri contactDisplayPhotoUri;
-
- // fields to hold individual contact preference data,
- // including the send to voicemail flag and the ringtone
- // uri reference.
- public Uri contactRingtoneUri;
- public boolean shouldSendToVoicemail;
-
- /**
- * Drawable representing the caller image. This is essentially
- * a cache for the image data tied into the connection /
- * callerinfo object.
- *
- * This might be a high resolution picture which is more suitable
- * for full-screen image view than for smaller icons used in some
- * kinds of notifications.
- *
- * The {@link #isCachedPhotoCurrent} flag indicates if the image
- * data needs to be reloaded.
- */
- public Drawable cachedPhoto;
- /**
- * Bitmap representing the caller image which has possibly lower
- * resolution than {@link #cachedPhoto} and thus more suitable for
- * icons (like notification icons).
- *
- * In usual cases this is just down-scaled image of {@link #cachedPhoto}.
- * If the down-scaling fails, this will just become null.
- *
- * The {@link #isCachedPhotoCurrent} flag indicates if the image
- * data needs to be reloaded.
- */
- public Bitmap cachedPhotoIcon;
- /**
- * Boolean which indicates if {@link #cachedPhoto} and
- * {@link #cachedPhotoIcon} is fresh enough. If it is false,
- * those images aren't pointing to valid objects.
- */
- public boolean isCachedPhotoCurrent;
-
- /**
- * String which holds the call subject sent as extra from the lower layers for this call. This
- * is used to display the no-caller ID reason for restricted/unknown number presentation.
- */
- public String callSubject;
-
- private boolean mIsEmergency;
- private boolean mIsVoiceMail;
-
- public CallerInfo() {
- // TODO: Move all the basic initialization here?
- mIsEmergency = false;
- mIsVoiceMail = false;
- userType = ContactsUtils.USER_TYPE_CURRENT;
- }
-
- /**
- * getCallerInfo given a Cursor.
- * @param context the context used to retrieve string constants
- * @param contactRef the URI to attach to this CallerInfo object
- * @param cursor the first object in the cursor is used to build the CallerInfo object.
- * @return the CallerInfo which contains the caller id for the given
- * number. The returned CallerInfo is null if no number is supplied.
- */
- public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
- CallerInfo info = new CallerInfo();
- info.photoResource = 0;
- info.phoneLabel = null;
- info.numberType = 0;
- info.numberLabel = null;
- info.cachedPhoto = null;
- info.isCachedPhotoCurrent = false;
- info.contactExists = false;
- info.userType = ContactsUtils.USER_TYPE_CURRENT;
-
- Log.v(TAG, "getCallerInfo() based on cursor...");
-
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- // TODO: photo_id is always available but not taken
- // care of here. Maybe we should store it in the
- // CallerInfo object as well.
-
- long contactId = 0L;
- int columnIndex;
-
- // Look for the name
- columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
- if (columnIndex != -1) {
- info.name = cursor.getString(columnIndex);
- }
-
- // Look for the number
- columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
- if (columnIndex != -1) {
- info.phoneNumber = cursor.getString(columnIndex);
- }
-
- // Look for the normalized number
- columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
- if (columnIndex != -1) {
- info.normalizedNumber = cursor.getString(columnIndex);
- }
-
- // Look for the label/type combo
- columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
- if (columnIndex != -1) {
- int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
- if (typeColumnIndex != -1) {
- info.numberType = cursor.getInt(typeColumnIndex);
- info.numberLabel = cursor.getString(columnIndex);
- info.phoneLabel = Phone.getTypeLabel(context.getResources(),
- info.numberType, info.numberLabel)
- .toString();
- }
- }
-
- // Look for the person_id.
- columnIndex = getColumnIndexForPersonId(contactRef, cursor);
- if (columnIndex != -1) {
- contactId = cursor.getLong(columnIndex);
- // QuickContacts in M doesn't support enterprise contact id
- if (contactId != 0 && (ContactsUtils.FLAG_N_FEATURE
- || !Contacts.isEnterpriseContactId(contactId))) {
- info.contactIdOrZero = contactId;
- Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
-
- // cache the lookup key for later use with person_id to create lookup URIs
- columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
- if (columnIndex != -1) {
- info.lookupKeyOrNull = cursor.getString(columnIndex);
- }
- }
- } else {
- // No valid columnIndex, so we can't look up person_id.
- Log.v(TAG, "Couldn't find contactId column for " + contactRef);
- // Watch out: this means that anything that depends on
- // person_id will be broken (like contact photo lookups in
- // the in-call UI, for example.)
- }
-
- // Display photo URI.
- columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
- if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
- info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
- } else {
- info.contactDisplayPhotoUri = null;
- }
-
- // look for the custom ringtone, create from the string stored
- // in the database.
- columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
- if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
- if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
- // make it consistent with frameworks/base/.../CallerInfo.java
- info.contactRingtoneUri = Uri.EMPTY;
- } else {
- info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
- }
- } else {
- info.contactRingtoneUri = null;
- }
-
- // look for the send to voicemail flag, set it to true only
- // under certain circumstances.
- columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
- info.shouldSendToVoicemail = (columnIndex != -1) &&
- ((cursor.getInt(columnIndex)) == 1);
- info.contactExists = true;
-
- // Determine userType by directoryId and contactId
- final String directory = contactRef == null ? null
- : contactRef.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
- final Long directoryId = directory == null ? null : Longs.tryParse(directory);
- info.userType = ContactsUtils.determineUserType(directoryId, contactId);
-
- info.nameAlternative = ContactInfoHelper.lookUpDisplayNameAlternative(
- context, info.lookupKeyOrNull, info.userType, directoryId);
- }
- cursor.close();
- }
-
- info.needUpdate = false;
- info.name = normalize(info.name);
- info.contactRefUri = contactRef;
-
- return info;
- }
-
- /**
- * getCallerInfo given a URI, look up in the call-log database
- * for the uri unique key.
- * @param context the context used to get the ContentResolver
- * @param contactRef the URI used to lookup caller id
- * @return the CallerInfo which contains the caller id for the given
- * number. The returned CallerInfo is null if no number is supplied.
- */
- private static CallerInfo getCallerInfo(Context context, Uri contactRef) {
-
- return getCallerInfo(context, contactRef,
- context.getContentResolver().query(contactRef, null, null, null, null));
- }
-
- /**
- * Performs another lookup if previous lookup fails and it's a SIP call
- * and the peer's username is all numeric. Look up the username as it
- * could be a PSTN number in the contact database.
- *
- * @param context the query context
- * @param number the original phone number, could be a SIP URI
- * @param previousResult the result of previous lookup
- * @return previousResult if it's not the case
- */
- static CallerInfo doSecondaryLookupIfNecessary(Context context,
- String number, CallerInfo previousResult) {
- if (!previousResult.contactExists
- && PhoneNumberHelper.isUriNumber(number)) {
- String username = PhoneNumberHelper.getUsernameFromUriNumber(number);
- if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
- previousResult = getCallerInfo(context,
- Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
- Uri.encode(username)));
- }
- }
- return previousResult;
- }
-
- // Accessors
-
- /**
- * @return true if the caller info is an emergency number.
- */
- public boolean isEmergencyNumber() {
- return mIsEmergency;
- }
-
- /**
- * @return true if the caller info is a voicemail number.
- */
- public boolean isVoiceMailNumber() {
- return mIsVoiceMail;
- }
-
- /**
- * Mark this CallerInfo as an emergency call.
- * @param context To lookup the localized 'Emergency Number' string.
- * @return this instance.
- */
- /* package */ CallerInfo markAsEmergency(Context context) {
- name = context.getString(R.string.emergency_call_dialog_number_for_display);
- phoneNumber = null;
-
- photoResource = R.drawable.img_phone;
- mIsEmergency = true;
- return this;
- }
-
-
- /**
- * Mark this CallerInfo as a voicemail call. The voicemail label
- * is obtained from the telephony manager. Caller must hold the
- * READ_PHONE_STATE permission otherwise the phoneNumber will be
- * set to null.
- * @return this instance.
- */
- /* package */ CallerInfo markAsVoiceMail(Context context) {
- mIsVoiceMail = true;
-
- try {
- // For voicemail calls, we display the voice mail tag
- // instead of the real phone number in the "number"
- // field.
- name = TelephonyManagerUtils.getVoiceMailAlphaTag(context);
- phoneNumber = null;
- } catch (SecurityException se) {
- // Should never happen: if this process does not have
- // permission to retrieve VM tag, it should not have
- // permission to retrieve VM number and would not call
- // this method.
- // Leave phoneNumber untouched.
- Log.e(TAG, "Cannot access VoiceMail.", se);
- }
- // TODO: There is no voicemail picture?
- // FIXME: FIND ANOTHER ICON
- // photoResource = android.R.drawable.badge_voicemail;
- return this;
- }
-
- private static String normalize(String s) {
- if (s == null || s.length() > 0) {
- return s;
- } else {
- return null;
- }
- }
-
- /**
- * Returns the column index to use to find the "person_id" field in
- * the specified cursor, based on the contact URI that was originally
- * queried.
- *
- * This is a helper function for the getCallerInfo() method that takes
- * a Cursor. Looking up the person_id is nontrivial (compared to all
- * the other CallerInfo fields) since the column we need to use
- * depends on what query we originally ran.
- *
- * Watch out: be sure to not do any database access in this method, since
- * it's run from the UI thread (see comments below for more info.)
- *
- * @return the columnIndex to use (with cursor.getLong()) to get the
- * person_id, or -1 if we couldn't figure out what colum to use.
- *
- * TODO: Add a unittest for this method. (This is a little tricky to
- * test, since we'll need a live contacts database to test against,
- * preloaded with at least some phone numbers and SIP addresses. And
- * we'll probably have to hardcode the column indexes we expect, so
- * the test might break whenever the contacts schema changes. But we
- * can at least make sure we handle all the URI patterns we claim to,
- * and that the mime types match what we expect...)
- */
- private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) {
- // TODO: This is pretty ugly now, see bug 2269240 for
- // more details. The column to use depends upon the type of URL:
- // - content://com.android.contacts/data/phones ==> use the "contact_id" column
- // - content://com.android.contacts/phone_lookup ==> use the "_ID" column
- // - content://com.android.contacts/data ==> use the "contact_id" column
- // If it's none of the above, we leave columnIndex=-1 which means
- // that the person_id field will be left unset.
- //
- // The logic here *used* to be based on the mime type of contactRef
- // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the
- // RawContacts.CONTACT_ID column). But looking up the mime type requires
- // a call to context.getContentResolver().getType(contactRef), which
- // isn't safe to do from the UI thread since it can cause an ANR if
- // the contacts provider is slow or blocked (like during a sync.)
- //
- // So instead, figure out the column to use for person_id by just
- // looking at the URI itself.
-
- Log.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '"
- + contactRef + "'...");
- // Warning: Do not enable the following logging (due to ANR risk.)
- // if (VDBG) Rlog.v(TAG, "- MIME type: "
- // + context.getContentResolver().getType(contactRef));
-
- String url = contactRef.toString();
- String columnName = null;
- if (url.startsWith("content://com.android.contacts/data/phones")) {
- // Direct lookup in the Phone table.
- // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2")
- Log.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID");
- columnName = RawContacts.CONTACT_ID;
- } else if (url.startsWith("content://com.android.contacts/data")) {
- // Direct lookup in the Data table.
- // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data")
- Log.v(TAG, "'data' URI; using Data.CONTACT_ID");
- // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.)
- columnName = Data.CONTACT_ID;
- } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
- // Lookup in the PhoneLookup table, which provides "fuzzy matching"
- // for phone numbers.
- // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup")
- Log.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID");
- columnName = PhoneLookupUtil.getContactIdColumnNameForUri(contactRef);
- } else {
- Log.v(TAG, "Unexpected prefix for contactRef '" + url + "'");
- }
- int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1;
- Log.v(TAG, "==> Using column '" + columnName
- + "' (columnIndex = " + columnIndex + ") for person_id lookup...");
- return columnIndex;
- }
-
- /**
- * Updates this CallerInfo's geoDescription field, based on the raw
- * phone number in the phoneNumber field.
- *
- * (Note that the various getCallerInfo() methods do *not* set the
- * geoDescription automatically; you need to call this method
- * explicitly to get it.)
- *
- * @param context the context used to look up the current locale / country
- * @param fallbackNumber if this CallerInfo's phoneNumber field is empty,
- * this specifies a fallback number to use instead.
- */
- public void updateGeoDescription(Context context, String fallbackNumber) {
- String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber;
- geoDescription = com.android.dialer.util.PhoneNumberUtil.getGeoDescription(context, number);
- }
-
- /**
- * @return a string debug representation of this instance.
- */
- @Override
- public String toString() {
- // Warning: never check in this file with VERBOSE_DEBUG = true
- // because that will result in PII in the system log.
- final boolean VERBOSE_DEBUG = false;
-
- if (VERBOSE_DEBUG) {
- return new StringBuilder(384)
- .append(super.toString() + " { ")
- .append("\nname: " + name)
- .append("\nphoneNumber: " + phoneNumber)
- .append("\nnormalizedNumber: " + normalizedNumber)
- .append("\forwardingNumber: " + forwardingNumber)
- .append("\ngeoDescription: " + geoDescription)
- .append("\ncnapName: " + cnapName)
- .append("\nnumberPresentation: " + numberPresentation)
- .append("\nnamePresentation: " + namePresentation)
- .append("\ncontactExists: " + contactExists)
- .append("\nphoneLabel: " + phoneLabel)
- .append("\nnumberType: " + numberType)
- .append("\nnumberLabel: " + numberLabel)
- .append("\nphotoResource: " + photoResource)
- .append("\ncontactIdOrZero: " + contactIdOrZero)
- .append("\nneedUpdate: " + needUpdate)
- .append("\ncontactRefUri: " + contactRefUri)
- .append("\ncontactRingtoneUri: " + contactRingtoneUri)
- .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
- .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
- .append("\ncachedPhoto: " + cachedPhoto)
- .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
- .append("\nemergency: " + mIsEmergency)
- .append("\nvoicemail: " + mIsVoiceMail)
- .append("\nuserType: " + userType)
- .append(" }")
- .toString();
- } else {
- return new StringBuilder(128)
- .append(super.toString() + " { ")
- .append("name " + ((name == null) ? "null" : "non-null"))
- .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null"))
- .append(" }")
- .toString();
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java b/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java
deleted file mode 100644
index f7f0cbb5d..000000000
--- a/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2006 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 com.google.common.primitives.Longs;
-
-import android.Manifest;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Directory;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.compat.DirectoryCompat;
-import com.android.contacts.common.util.PermissionsUtil;
-import com.android.contacts.common.util.TelephonyManagerUtils;
-import com.android.dialer.R;
-import com.android.dialer.calllog.ContactInfoHelper;
-import com.android.dialer.service.CachedNumberLookupService;
-import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
-import com.android.dialerbind.ObjectFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
-/**
- * Helper class to make it easier to run asynchronous caller-id lookup queries.
- * @see CallerInfo
- *
- */
-public class CallerInfoAsyncQuery {
- private static final boolean DBG = false;
- private static final String LOG_TAG = "CallerInfoAsyncQuery";
-
- private static final int EVENT_NEW_QUERY = 1;
- private static final int EVENT_ADD_LISTENER = 2;
- private static final int EVENT_END_OF_QUEUE = 3;
- private static final int EVENT_EMERGENCY_NUMBER = 4;
- private static final int EVENT_VOICEMAIL_NUMBER = 5;
-
- private CallerInfoAsyncQueryHandler mHandler;
-
- // If the CallerInfo query finds no contacts, should we use the
- // PhoneNumberOfflineGeocoder to look up a "geo description"?
- // (TODO: This could become a flag in config.xml if it ever needs to be
- // configured on a per-product basis.)
- private static final boolean ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION = true;
-
- /**
- * Interface for a CallerInfoAsyncQueryHandler result return.
- */
- public interface OnQueryCompleteListener {
- /**
- * Called when the query is complete.
- */
- public void onQueryComplete(int token, Object cookie, CallerInfo ci);
- }
-
-
- /**
- * Wrap the cookie from the WorkerArgs with additional information needed by our
- * classes.
- */
- private static final class CookieWrapper {
- public OnQueryCompleteListener listener;
- public Object cookie;
- public int event;
- public String number;
- }
-
- /**
- * Simple exception used to communicate problems with the query pool.
- */
- public static class QueryPoolException extends SQLException {
- public QueryPoolException(String error) {
- super(error);
- }
- }
-
- /**
- * Our own implementation of the AsyncQueryHandler.
- */
- private class CallerInfoAsyncQueryHandler extends AsyncQueryHandler {
-
- @Override
- public void startQuery(int token, Object cookie, Uri uri, String[] projection,
- String selection, String[] selectionArgs, String orderBy) {
- if (DBG) {
- // Show stack trace with the arguments.
- android.util.Log.d(LOG_TAG, "InCall: startQuery: url=" + uri +
- " projection=[" + Arrays.toString(projection) + "]" +
- " selection=" + selection + " " +
- " args=[" + Arrays.toString(selectionArgs) + "]",
- new RuntimeException("STACKTRACE"));
- }
- super.startQuery(token, cookie, uri, projection, selection, selectionArgs, orderBy);
- }
-
- /**
- * The information relevant to each CallerInfo query. Each query may have multiple
- * listeners, so each AsyncCursorInfo is associated with 2 or more CookieWrapper
- * objects in the queue (one with a new query event, and one with a end event, with
- * 0 or more additional listeners in between).
- */
- private Context mQueryContext;
- private Uri mQueryUri;
- private CallerInfo mCallerInfo;
-
- /**
- * Our own query worker thread.
- *
- * This thread handles the messages enqueued in the looper. The normal sequence
- * of events is that a new query shows up in the looper queue, followed by 0 or
- * more add listener requests, and then an end request. Of course, these requests
- * can be interlaced with requests from other tokens, but is irrelevant to this
- * handler since the handler has no state.
- *
- * Note that we depend on the queue to keep things in order; in other words, the
- * looper queue must be FIFO with respect to input from the synchronous startQuery
- * calls and output to this handleMessage call.
- *
- * This use of the queue is required because CallerInfo objects may be accessed
- * multiple times before the query is complete. All accesses (listeners) must be
- * queued up and informed in order when the query is complete.
- */
- protected class CallerInfoWorkerHandler extends WorkerHandler {
- public CallerInfoWorkerHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- WorkerArgs args = (WorkerArgs) msg.obj;
- CookieWrapper cw = (CookieWrapper) args.cookie;
-
- if (cw == null) {
- // Normally, this should never be the case for calls originating
- // from within this code.
- // However, if there is any code that this Handler calls (such as in
- // super.handleMessage) that DOES place unexpected messages on the
- // queue, then we need pass these messages on.
- Log.d(this, "Unexpected command (CookieWrapper is null): " + msg.what +
- " ignored by CallerInfoWorkerHandler, passing onto parent.");
-
- super.handleMessage(msg);
- } else {
- Log.d(this, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
- " command: " + msg.what + " query URI: " +
- sanitizeUriToString(args.uri));
-
- switch (cw.event) {
- case EVENT_NEW_QUERY:
- //start the sql command.
- super.handleMessage(msg);
- break;
-
- // shortcuts to avoid query for recognized numbers.
- case EVENT_EMERGENCY_NUMBER:
- case EVENT_VOICEMAIL_NUMBER:
-
- case EVENT_ADD_LISTENER:
- case EVENT_END_OF_QUEUE:
- // query was already completed, so just send the reply.
- // passing the original token value back to the caller
- // on top of the event values in arg1.
- Message reply = args.handler.obtainMessage(msg.what);
- reply.obj = args;
- reply.arg1 = msg.arg1;
-
- reply.sendToTarget();
-
- break;
- default:
- }
- }
- }
- }
-
-
- /**
- * Asynchronous query handler class for the contact / callerinfo object.
- */
- private CallerInfoAsyncQueryHandler(Context context) {
- super(context.getContentResolver());
- }
-
- @Override
- protected Handler createHandler(Looper looper) {
- return new CallerInfoWorkerHandler(looper);
- }
-
- /**
- * Overrides onQueryComplete from AsyncQueryHandler.
- *
- * This method takes into account the state of this class; we construct the CallerInfo
- * object only once for each set of listeners. When the query thread has done its work
- * and calls this method, we inform the remaining listeners in the queue, until we're
- * out of listeners. Once we get the message indicating that we should expect no new
- * listeners for this CallerInfo object, we release the AsyncCursorInfo back into the
- * pool.
- */
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- try {
- Log.d(this, "##### onQueryComplete() ##### query complete for token: " + token);
-
- //get the cookie and notify the listener.
- CookieWrapper cw = (CookieWrapper) cookie;
- if (cw == null) {
- // Normally, this should never be the case for calls originating
- // from within this code.
- // However, if there is any code that calls this method, we should
- // check the parameters to make sure they're viable.
- Log.d(this, "Cookie is null, ignoring onQueryComplete() request.");
- return;
- }
-
- if (cw.event == EVENT_END_OF_QUEUE) {
- release();
- return;
- }
-
- // check the token and if needed, create the callerinfo object.
- if (mCallerInfo == null) {
- if ((mQueryContext == null) || (mQueryUri == null)) {
- throw new QueryPoolException
- ("Bad context or query uri, or CallerInfoAsyncQuery already released.");
- }
-
- // adjust the callerInfo data as needed, and only if it was set from the
- // initial query request.
- // Change the callerInfo number ONLY if it is an emergency number or the
- // voicemail number, and adjust other data (including photoResource)
- // accordingly.
- if (cw.event == EVENT_EMERGENCY_NUMBER) {
- // Note we're setting the phone number here (refer to javadoc
- // comments at the top of CallerInfo class).
- mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext);
- } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
- mCallerInfo = new CallerInfo().markAsVoiceMail(mQueryContext);
- } else {
- mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
- Log.d(this, "==> Got mCallerInfo: " + mCallerInfo);
-
- CallerInfo newCallerInfo = CallerInfo.doSecondaryLookupIfNecessary(
- mQueryContext, cw.number, mCallerInfo);
- if (newCallerInfo != mCallerInfo) {
- mCallerInfo = newCallerInfo;
- Log.d(this, "#####async contact look up with numeric username"
- + mCallerInfo);
- }
-
- // Final step: look up the geocoded description.
- if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) {
- // Note we do this only if we *don't* have a valid name (i.e. if
- // no contacts matched the phone number of the incoming call),
- // since that's the only case where the incoming-call UI cares
- // about this field.
- //
- // (TODO: But if we ever want the UI to show the geoDescription
- // even when we *do* match a contact, we'll need to either call
- // updateGeoDescription() unconditionally here, or possibly add a
- // new parameter to CallerInfoAsyncQuery.startQuery() to force
- // the geoDescription field to be populated.)
-
- if (TextUtils.isEmpty(mCallerInfo.name)) {
- // Actually when no contacts match the incoming phone number,
- // the CallerInfo object is totally blank here (i.e. no name
- // *or* phoneNumber). So we need to pass in cw.number as
- // a fallback number.
- mCallerInfo.updateGeoDescription(mQueryContext, cw.number);
- }
- }
-
- // Use the number entered by the user for display.
- if (!TextUtils.isEmpty(cw.number)) {
- mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number,
- mCallerInfo.normalizedNumber,
- TelephonyManagerUtils.getCurrentCountryIso(mQueryContext,
- Locale.getDefault()));
- }
- }
-
- Log.d(this, "constructing CallerInfo object for token: " + token);
-
- //notify that we can clean up the queue after this.
- CookieWrapper endMarker = new CookieWrapper();
- endMarker.event = EVENT_END_OF_QUEUE;
- startQuery(token, endMarker, null, null, null, null, null);
- }
-
- //notify the listener that the query is complete.
- if (cw.listener != null) {
- Log.d(this, "notifying listener: " + cw.listener.getClass().toString() +
- " for token: " + token + mCallerInfo);
- cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
- }
- } finally {
- // The cursor may have been closed in CallerInfo.getCallerInfo()
- if (cursor != null && !cursor.isClosed()) {
- cursor.close();
- }
- }
- }
- }
-
- /**
- * Private constructor for factory methods.
- */
- private CallerInfoAsyncQuery() {
- }
-
- public static void startQuery(final int token, final Context context, final CallerInfo info,
- final OnQueryCompleteListener listener, final Object cookie) {
- Log.d(LOG_TAG, "##### CallerInfoAsyncQuery startContactProviderQuery()... #####");
- Log.d(LOG_TAG, "- number: " + info.phoneNumber);
- Log.d(LOG_TAG, "- cookie: " + cookie);
- if (!PermissionsUtil.hasPermission(context, Manifest.permission.READ_CONTACTS)) {
- Log.w(LOG_TAG, "Dialer doesn't have permission to read contacts.");
- listener.onQueryComplete(token, cookie, info);
- return;
- }
-
- OnQueryCompleteListener contactsProviderQueryCompleteListener =
- new OnQueryCompleteListener() {
- @Override
- public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
- Log.d(LOG_TAG, "contactsProviderQueryCompleteListener done");
- // If there are no other directory queries, make sure that the listener is
- // notified of this result. see b/27621628
- if ((ci != null && ci.contactExists) ||
- !startOtherDirectoriesQuery(token, context, info, listener, cookie)) {
- if (listener != null && ci != null) {
- listener.onQueryComplete(token, cookie, ci);
- }
- }
- }
- };
- startDefaultDirectoryQuery(token, context, info, contactsProviderQueryCompleteListener,
- cookie);
- }
-
- // Private methods
- private static CallerInfoAsyncQuery startDefaultDirectoryQuery(int token, Context context,
- CallerInfo info, OnQueryCompleteListener listener, Object cookie) {
- // Construct the URI object and query params, and start the query.
- Uri uri = ContactInfoHelper.getContactInfoLookupUri(info.phoneNumber);
- return startQueryInternal(token, context, info, listener, cookie, uri);
- }
-
- /**
- * Factory method to start the query based on a CallerInfo object.
- *
- * Note: if the number contains an "@" character we treat it
- * as a SIP address, and look it up directly in the Data table
- * rather than using the PhoneLookup table.
- * TODO: But eventually we should expose two separate methods, one for
- * numbers and one for SIP addresses, and then have
- * PhoneUtils.startGetCallerInfo() decide which one to call based on
- * the phone type of the incoming connection.
- */
- private static CallerInfoAsyncQuery startQueryInternal(int token, Context context,
- CallerInfo info, OnQueryCompleteListener listener, Object cookie, Uri contactRef) {
- if (DBG) {
- Log.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef));
- }
-
- CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
- c.allocate(context, contactRef);
-
- //create cookieWrapper, start query
- CookieWrapper cw = new CookieWrapper();
- cw.listener = listener;
- cw.cookie = cookie;
- cw.number = info.phoneNumber;
-
- // check to see if these are recognized numbers, and use shortcuts if we can.
- if (PhoneNumberUtils.isLocalEmergencyNumber(context, info.phoneNumber)) {
- cw.event = EVENT_EMERGENCY_NUMBER;
- } else if (info.isVoiceMailNumber()) {
- cw.event = EVENT_VOICEMAIL_NUMBER;
- } else {
- cw.event = EVENT_NEW_QUERY;
- }
-
-
- String[] proejection = CallerInfo.getDefaultPhoneLookupProjection(contactRef);
- c.mHandler.startQuery(token,
- cw, // cookie
- contactRef, // uri
- proejection, // projection
- null, // selection
- null, // selectionArgs
- null); // orderBy
- return c;
- }
-
- // Return value indicates if listener was notified.
- private static boolean startOtherDirectoriesQuery(int token, Context context, CallerInfo info,
- OnQueryCompleteListener listener, Object cookie) {
- long[] directoryIds = getDirectoryIds(context);
- int size = directoryIds.length;
- if (size == 0) {
- return false;
- }
-
- DirectoryQueryCompleteListenerFactory listenerFactory =
- new DirectoryQueryCompleteListenerFactory(context, size, listener);
-
- // The current implementation of multiple async query runs in single handler thread
- // in AsyncQueryHandler.
- // intermediateListener.onQueryComplete is also called from the same caller thread.
- // TODO(b/26019872): use thread pool instead of single thread.
- for (int i = 0; i < size; i++) {
- long directoryId = directoryIds[i];
- Uri uri = ContactInfoHelper.getContactInfoLookupUri(info.phoneNumber, directoryId);
- if (DBG) {
- Log.d(LOG_TAG, "directoryId: " + directoryId + " uri: " + uri);
- }
- OnQueryCompleteListener intermediateListener =
- listenerFactory.newListener(directoryId);
- startQueryInternal(token, context, info, intermediateListener, cookie, uri);
- }
- return true;
- }
-
- /* Directory lookup related code - START */
- private static final String[] DIRECTORY_PROJECTION = new String[] {Directory._ID};
-
- private static long[] getDirectoryIds(Context context) {
- ArrayList<Long> results = new ArrayList<>();
-
- Uri uri = Directory.CONTENT_URI;
- if (ContactsUtils.FLAG_N_FEATURE) {
- uri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "directories_enterprise");
- }
-
- ContentResolver cr = context.getContentResolver();
- Cursor cursor = cr.query(uri, DIRECTORY_PROJECTION, null, null, null);
- addDirectoryIdsFromCursor(cursor, results);
-
- return Longs.toArray(results);
- }
-
- private static void addDirectoryIdsFromCursor(Cursor cursor, ArrayList<Long> results) {
- if (cursor != null) {
- int idIndex = cursor.getColumnIndex(Directory._ID);
- while (cursor.moveToNext()) {
- long id = cursor.getLong(idIndex);
- if (DirectoryCompat.isRemoteDirectoryId(id)) {
- results.add(id);
- }
- }
- cursor.close();
- }
- }
-
- private static final class DirectoryQueryCompleteListenerFactory {
- // Make sure listener to be called once and only once
- private int mCount;
- private boolean mIsListenerCalled;
- private final OnQueryCompleteListener mListener;
- private final Context mContext;
- private final CachedNumberLookupService mCachedNumberLookupService =
- ObjectFactory.newCachedNumberLookupService();
-
- private class DirectoryQueryCompleteListener implements OnQueryCompleteListener {
- private final long mDirectoryId;
-
- DirectoryQueryCompleteListener(long directoryId) {
- mDirectoryId = directoryId;
- }
-
- @Override
- public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
- onDirectoryQueryComplete(token, cookie, ci, mDirectoryId);
- }
- }
-
- DirectoryQueryCompleteListenerFactory(Context context, int size,
- OnQueryCompleteListener listener) {
- mCount = size;
- mListener = listener;
- mIsListenerCalled = false;
- mContext = context;
- }
-
- private void onDirectoryQueryComplete(int token, Object cookie, CallerInfo ci,
- long directoryId) {
- boolean shouldCallListener = false;
- synchronized (this) {
- mCount = mCount - 1;
- if (!mIsListenerCalled && (ci.contactExists || mCount == 0)) {
- mIsListenerCalled = true;
- shouldCallListener = true;
- }
- }
-
- // Don't call callback in synchronized block because mListener.onQueryComplete may
- // take long time to complete
- if (shouldCallListener && mListener != null) {
- addCallerInfoIntoCache(ci, directoryId);
- mListener.onQueryComplete(token, cookie, ci);
- }
- }
-
- private void addCallerInfoIntoCache(CallerInfo ci, long directoryId) {
- if (ci.contactExists && mCachedNumberLookupService != null) {
- // 1. Cache caller info
- CachedContactInfo cachedContactInfo = CallerInfoUtils
- .buildCachedContactInfo(mCachedNumberLookupService, ci);
- String directoryLabel = mContext.getString(R.string.directory_search_label);
- cachedContactInfo.setDirectorySource(directoryLabel, directoryId);
- mCachedNumberLookupService.addContact(mContext, cachedContactInfo);
-
- // 2. Cache photo
- if (ci.contactDisplayPhotoUri != null && ci.normalizedNumber != null) {
- try (InputStream in = mContext.getContentResolver()
- .openInputStream(ci.contactDisplayPhotoUri)) {
- if (in != null) {
- mCachedNumberLookupService.addPhoto(mContext, ci.normalizedNumber, in);
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "failed to fetch directory contact photo", e);
- }
-
- }
- }
- }
-
- public OnQueryCompleteListener newListener(long directoryId) {
- return new DirectoryQueryCompleteListener(directoryId);
- }
- }
- /* Directory lookup related code - END */
-
- /**
- * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct
- * state of context and uri.
- */
- private void allocate(Context context, Uri contactRef) {
- if ((context == null) || (contactRef == null)){
- throw new QueryPoolException("Bad context or query uri.");
- }
- mHandler = new CallerInfoAsyncQueryHandler(context);
- mHandler.mQueryContext = context;
- mHandler.mQueryUri = contactRef;
- }
-
- /**
- * Releases the relevant data.
- */
- private void release() {
- mHandler.mQueryContext = null;
- mHandler.mQueryUri = null;
- mHandler.mCallerInfo = null;
- mHandler = null;
- }
-
- private static String sanitizeUriToString(Uri uri) {
- if (uri != null) {
- String uriString = uri.toString();
- int indexOfLastSlash = uriString.lastIndexOf('/');
- if (indexOfLastSlash > 0) {
- return uriString.substring(0, indexOfLastSlash) + "/xxxxxxx";
- } else {
- return uriString;
- }
- } else {
- return "";
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CallerInfoUtils.java b/InCallUI/src/com/android/incallui/CallerInfoUtils.java
deleted file mode 100644
index 289b652fc..000000000
--- a/InCallUI/src/com/android/incallui/CallerInfoUtils.java
+++ /dev/null
@@ -1,234 +0,0 @@
-package com.android.incallui;
-
-import android.content.Context;
-import android.content.Loader;
-import android.content.Loader.OnLoadCompleteListener;
-import android.net.Uri;
-import android.telecom.PhoneAccount;
-import android.telecom.TelecomManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.contacts.common.model.Contact;
-import com.android.contacts.common.model.ContactLoader;
-import com.android.dialer.R;
-import com.android.dialer.calllog.ContactInfo;
-import com.android.dialer.service.CachedNumberLookupService;
-import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
-import com.android.dialer.util.TelecomUtil;
-
-import java.util.Arrays;
-
-/**
- * Utility methods for contact and caller info related functionality
- */
-public class CallerInfoUtils {
-
- private static final String TAG = CallerInfoUtils.class.getSimpleName();
-
- /** Define for not a special CNAP string */
- private static final int CNAP_SPECIAL_CASE_NO = -1;
-
- public CallerInfoUtils() {
- }
-
- private static final int QUERY_TOKEN = -1;
-
- /**
- * This is called to get caller info for a call. This will return a CallerInfo
- * object immediately based off information in the call, but
- * more information is returned to the OnQueryCompleteListener (which contains
- * information about the phone number label, user's name, etc).
- */
- public static CallerInfo getCallerInfoForCall(Context context, Call call,
- CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
- CallerInfo info = buildCallerInfo(context, call);
-
- // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.
-
- if (info.numberPresentation == TelecomManager.PRESENTATION_ALLOWED) {
- // Start the query with the number provided from the call.
- Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()...");
- CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, info, listener, call);
- }
- return info;
- }
-
- public static CallerInfo buildCallerInfo(Context context, Call call) {
- CallerInfo info = new CallerInfo();
-
- // Store CNAP information retrieved from the Connection (we want to do this
- // here regardless of whether the number is empty or not).
- info.cnapName = call.getCnapName();
- info.name = info.cnapName;
- info.numberPresentation = call.getNumberPresentation();
- info.namePresentation = call.getCnapNamePresentation();
- info.callSubject = call.getCallSubject();
-
- String number = call.getNumber();
- if (!TextUtils.isEmpty(number)) {
- final String[] numbers = number.split("&");
- number = numbers[0];
- if (numbers.length > 1) {
- info.forwardingNumber = numbers[1];
- }
-
- number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation);
- info.phoneNumber = number;
- }
-
- // Because the InCallUI is immediately launched before the call is connected, occasionally
- // a voicemail call will be passed to InCallUI as a "voicemail:" URI without a number.
- // This call should still be handled as a voicemail call.
- if ((call.getHandle() != null &&
- PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())) ||
- isVoiceMailNumber(context, call)) {
- info.markAsVoiceMail(context);
- }
-
- ContactInfoCache.getInstance(context).maybeInsertCnapInformationIntoCache(context, call,
- info);
-
- return info;
- }
-
- /**
- * Creates a new {@link CachedContactInfo} from a {@link CallerInfo}
- *
- * @param lookupService the {@link CachedNumberLookupService} used to build a
- * new {@link CachedContactInfo}
- * @param {@link CallerInfo} object
- * @return a CachedContactInfo object created from this CallerInfo
- * @throws NullPointerException if lookupService or ci are null
- */
- public static CachedContactInfo buildCachedContactInfo(CachedNumberLookupService lookupService,
- CallerInfo ci) {
- ContactInfo info = new ContactInfo();
- info.name = ci.name;
- info.type = ci.numberType;
- info.label = ci.phoneLabel;
- info.number = ci.phoneNumber;
- info.normalizedNumber = ci.normalizedNumber;
- info.photoUri = ci.contactDisplayPhotoUri;
- info.userType = ci.userType;
-
- CachedContactInfo cacheInfo = lookupService.buildCachedContactInfo(info);
- cacheInfo.setLookupKey(ci.lookupKeyOrNull);
- return cacheInfo;
- }
-
- public static boolean isVoiceMailNumber(Context context, Call call) {
- return TelecomUtil.isVoicemailNumber(context,
- call.getTelecomCall().getDetails().getAccountHandle(),
- call.getNumber());
- }
-
- /**
- * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
- * from the network to indicate different number presentations, convert them to
- * expected number and presentation values within the CallerInfo object.
- * @param number number we use to verify if we are in a corner case
- * @param presentation presentation value used to verify if we are in a corner case
- * @return the new String that should be used for the phone number
- */
- /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
- String number, int presentation) {
- // Obviously we return number if ci == null, but still return number if
- // number == null, because in these cases the correct string will still be
- // displayed/logged after this function returns based on the presentation value.
- if (ci == null || number == null) return number;
-
- Log.d(TAG, "modifyForSpecialCnapCases: initially, number="
- + toLogSafePhoneNumber(number)
- + ", presentation=" + presentation + " ci " + ci);
-
- // "ABSENT NUMBER" is a possible value we could get from the network as the
- // phone number, so if this happens, change it to "Unknown" in the CallerInfo
- // and fix the presentation to be the same.
- final String[] absentNumberValues =
- context.getResources().getStringArray(R.array.absent_num);
- if (Arrays.asList(absentNumberValues).contains(number)
- && presentation == TelecomManager.PRESENTATION_ALLOWED) {
- number = context.getString(R.string.unknown);
- ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
- }
-
- // Check for other special "corner cases" for CNAP and fix them similarly. Corner
- // cases only apply if we received an allowed presentation from the network, so check
- // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
- // match the presentation passed in for verification (meaning we changed it previously
- // because it's a corner case and we're being called from a different entry point).
- if (ci.numberPresentation == TelecomManager.PRESENTATION_ALLOWED
- || (ci.numberPresentation != presentation
- && presentation == TelecomManager.PRESENTATION_ALLOWED)) {
- // For all special strings, change number & numberPrentation.
- if (isCnapSpecialCaseRestricted(number)) {
- number = context.getString(R.string.private_num);
- ci.numberPresentation = TelecomManager.PRESENTATION_RESTRICTED;
- } else if (isCnapSpecialCaseUnknown(number)) {
- number = context.getString(R.string.unknown);
- ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
- }
- Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number)
- + "; presentation now=" + ci.numberPresentation);
- }
- Log.d(TAG, "modifyForSpecialCnapCases: returning number string="
- + toLogSafePhoneNumber(number));
- return number;
- }
-
- private static boolean isCnapSpecialCaseRestricted(String n) {
- return n.equals("PRIVATE") || n.equals("P") || n.equals("RES");
- }
-
- private static boolean isCnapSpecialCaseUnknown(String n) {
- return n.equals("UNAVAILABLE") || n.equals("UNKNOWN") || n.equals("UNA") || n.equals("U");
- }
-
- /* package */static String toLogSafePhoneNumber(String number) {
- // For unknown number, log empty string.
- if (number == null) {
- return "";
- }
-
- // Todo: Figure out an equivalent for VDBG
- if (false) {
- // When VDBG is true we emit PII.
- return number;
- }
-
- // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
- // sanitized phone numbers.
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < number.length(); i++) {
- char c = number.charAt(i);
- if (c == '-' || c == '@' || c == '.' || c == '&') {
- builder.append(c);
- } else {
- builder.append('x');
- }
- }
- return builder.toString();
- }
-
- /**
- * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are
- * viewing a particular contact, so that it can download the high-res photo.
- */
- public static void sendViewNotification(Context context, Uri contactUri) {
- final ContactLoader loader = new ContactLoader(context, contactUri,
- true /* postViewNotification */);
- loader.registerListener(0, new OnLoadCompleteListener<Contact>() {
- @Override
- public void onLoadComplete(
- Loader<Contact> loader, Contact contact) {
- try {
- loader.reset();
- } catch (RuntimeException e) {
- Log.e(TAG, "Error resetting loader", e);
- }
- }
- });
- loader.startLoading();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/CircularRevealFragment.java b/InCallUI/src/com/android/incallui/CircularRevealFragment.java
deleted file mode 100644
index 01bd253ec..000000000
--- a/InCallUI/src/com/android/incallui/CircularRevealFragment.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 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.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.graphics.Outline;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-import com.android.dialer.R;
-
-public class CircularRevealFragment extends Fragment {
- static final String TAG = "CircularRevealFragment";
-
- private Point mTouchPoint;
- private OnCircularRevealCompleteListener mListener;
- private boolean mAnimationStarted;
-
- interface OnCircularRevealCompleteListener {
- public void onCircularRevealComplete(FragmentManager fm);
- }
-
- public static void startCircularReveal(FragmentManager fm, Point touchPoint,
- OnCircularRevealCompleteListener listener) {
- if (fm.findFragmentByTag(TAG) == null) {
- fm.beginTransaction().add(R.id.main,
- new CircularRevealFragment(touchPoint, listener), TAG)
- .commitAllowingStateLoss();
- } else {
- Log.w(TAG, "An instance of CircularRevealFragment already exists");
- }
- }
-
- public static void endCircularReveal(FragmentManager fm) {
- final Fragment fragment = fm.findFragmentByTag(TAG);
- if (fragment != null) {
- fm.beginTransaction().remove(fragment).commitAllowingStateLoss();
- }
- }
-
- /**
- * Empty constructor used only by the {@link FragmentManager}.
- */
- public CircularRevealFragment() {}
-
- public CircularRevealFragment(Point touchPoint, OnCircularRevealCompleteListener listener) {
- mTouchPoint = touchPoint;
- mListener = listener;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (!mAnimationStarted) {
- // Only run the animation once for each instance of the fragment
- startOutgoingAnimation(InCallPresenter.getInstance().getThemeColors());
- }
- mAnimationStarted = true;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.outgoing_call_animation, container, false);
- }
-
- public void startOutgoingAnimation(MaterialPalette palette) {
- final Activity activity = getActivity();
- if (activity == null) {
- Log.w(this, "Asked to do outgoing call animation when not attached");
- return;
- }
-
- final View view = activity.getWindow().getDecorView();
-
- // The circle starts from an initial size of 0 so clip it such that it is invisible.
- // Otherwise the first frame is drawn with a fully opaque screen which causes jank. When
- // the animation later starts, this clip will be clobbered by the circular reveal clip.
- // See ViewAnimationUtils.createCircularReveal.
- view.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- // Using (0, 0, 0, 0) will not work since the outline will simply be treated as
- // an empty outline.
- outline.setOval(-1, -1, 0, 0);
- }
- });
- view.setClipToOutline(true);
-
- if (palette != null) {
- view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor(
- palette.mPrimaryColor);
- activity.getWindow().setStatusBarColor(palette.mSecondaryColor);
- }
-
- view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- final ViewTreeObserver vto = view.getViewTreeObserver();
- if (vto.isAlive()) {
- vto.removeOnPreDrawListener(this);
- }
- final Animator animator = getRevealAnimator(mTouchPoint);
- if (animator != null) {
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setClipToOutline(false);
- if (mListener != null) {
- mListener.onCircularRevealComplete(getFragmentManager());
- }
- }
- });
- animator.start();
- }
- return false;
- }
- });
- }
-
- private Animator getRevealAnimator(Point touchPoint) {
- final Activity activity = getActivity();
- if (activity == null) {
- return null;
- }
- final View view = activity.getWindow().getDecorView();
- final Display display = activity.getWindowManager().getDefaultDisplay();
- final Point size = new Point();
- display.getSize(size);
-
- int startX = size.x / 2;
- int startY = size.y / 2;
- if (touchPoint != null) {
- startX = touchPoint.x;
- startY = touchPoint.y;
- }
-
- final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view,
- startX, startY, 0, Math.max(size.x, size.y));
- valueAnimator.setDuration(getResources().getInteger(R.integer.reveal_animation_duration));
- return valueAnimator;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java b/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
deleted file mode 100644
index fe941c8c5..000000000
--- a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2013 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.ActionBar;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import com.android.contacts.common.ContactPhotoManager;
-import com.android.dialer.R;
-
-import java.util.List;
-
-/**
- * Fragment that allows the user to manage a conference call.
- */
-public class ConferenceManagerFragment
- extends BaseFragment<ConferenceManagerPresenter,
- ConferenceManagerPresenter.ConferenceManagerUi>
- implements ConferenceManagerPresenter.ConferenceManagerUi {
-
- private static final String KEY_IS_VISIBLE = "key_conference_is_visible";
-
- private ListView mConferenceParticipantList;
- private int mActionBarElevation;
- private ContactPhotoManager mContactPhotoManager;
- private LayoutInflater mInflater;
- private ConferenceParticipantListAdapter mConferenceParticipantListAdapter;
- private boolean mIsVisible;
- private boolean mIsRecreating;
-
- @Override
- public ConferenceManagerPresenter createPresenter() {
- return new ConferenceManagerPresenter();
- }
-
- @Override
- public ConferenceManagerPresenter.ConferenceManagerUi getUi() {
- return this;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mIsRecreating = true;
- mIsVisible = savedInstanceState.getBoolean(KEY_IS_VISIBLE);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View parent =
- inflater.inflate(R.layout.conference_manager_fragment, container, false);
-
- mConferenceParticipantList = (ListView) parent.findViewById(R.id.participantList);
- mContactPhotoManager =
- ContactPhotoManager.getInstance(getActivity().getApplicationContext());
- mActionBarElevation =
- (int) getResources().getDimension(R.dimen.incall_action_bar_elevation);
- mInflater = LayoutInflater.from(getActivity().getApplicationContext());
-
- return parent;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mIsRecreating) {
- onVisibilityChanged(mIsVisible);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(KEY_IS_VISIBLE, mIsVisible);
- super.onSaveInstanceState(outState);
- }
-
- public void onVisibilityChanged(boolean isVisible) {
- mIsVisible = isVisible;
- ActionBar actionBar = getActivity().getActionBar();
- if (isVisible) {
- actionBar.setTitle(R.string.manageConferenceLabel);
- actionBar.setElevation(mActionBarElevation);
- actionBar.setHideOffset(0);
- actionBar.show();
-
- final CallList calls = CallList.getInstance();
- getPresenter().init(getActivity(), calls);
- // Request focus on the list of participants for accessibility purposes. This ensures
- // that once the list of participants is shown, the first participant is announced.
- mConferenceParticipantList.requestFocus();
- } else {
- actionBar.setElevation(0);
- actionBar.setHideOffset(actionBar.getHeight());
- }
- }
-
- @Override
- public boolean isFragmentVisible() {
- return isVisible();
- }
-
- @Override
- public void update(Context context, List<Call> participants, boolean parentCanSeparate) {
- if (mConferenceParticipantListAdapter == null) {
- mConferenceParticipantListAdapter = new ConferenceParticipantListAdapter(
- mConferenceParticipantList, context, mInflater, mContactPhotoManager);
-
- mConferenceParticipantList.setAdapter(mConferenceParticipantListAdapter);
- }
- mConferenceParticipantListAdapter.updateParticipants(participants, parentCanSeparate);
- }
-
- @Override
- public void refreshCall(Call call) {
- mConferenceParticipantListAdapter.refreshCall(call);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
deleted file mode 100644
index 6fb6e5dda..000000000
--- a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-
-import com.android.incallui.InCallPresenter.InCallDetailsListener;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.InCallPresenter.InCallStateListener;
-import com.android.incallui.InCallPresenter.IncomingCallListener;
-
-import com.google.common.base.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Logic for call buttons.
- */
-public class ConferenceManagerPresenter
- extends Presenter<ConferenceManagerPresenter.ConferenceManagerUi>
- implements InCallStateListener, InCallDetailsListener, IncomingCallListener {
-
- private Context mContext;
-
- @Override
- public void onUiReady(ConferenceManagerUi ui) {
- super.onUiReady(ui);
-
- // register for call state changes last
- InCallPresenter.getInstance().addListener(this);
- InCallPresenter.getInstance().addIncomingCallListener(this);
- }
-
- @Override
- public void onUiUnready(ConferenceManagerUi ui) {
- super.onUiUnready(ui);
-
- InCallPresenter.getInstance().removeListener(this);
- InCallPresenter.getInstance().removeIncomingCallListener(this);
- }
-
- @Override
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
- if (getUi().isFragmentVisible()) {
- Log.v(this, "onStateChange" + newState);
- if (newState == InCallState.INCALL) {
- final Call call = callList.getActiveOrBackgroundCall();
- if (call != null && call.isConferenceCall()) {
- Log.v(this, "Number of existing calls is " +
- String.valueOf(call.getChildCallIds().size()));
- update(callList);
- } else {
- InCallPresenter.getInstance().showConferenceCallManager(false);
- }
- } else {
- InCallPresenter.getInstance().showConferenceCallManager(false);
- }
- }
- }
-
- @Override
- public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
- boolean canDisconnect = details.can(
- android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE);
- boolean canSeparate = details.can(
- android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE);
-
- if (call.can(android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE)
- != canDisconnect
- || call.can(android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE)
- != canSeparate) {
- getUi().refreshCall(call);
- }
-
- if (!details.can(
- android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)) {
- InCallPresenter.getInstance().showConferenceCallManager(false);
- }
- }
-
- @Override
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
- // When incoming call exists, set conference ui invisible.
- if (getUi().isFragmentVisible()) {
- Log.d(this, "onIncomingCall()... Conference ui is showing, hide it.");
- InCallPresenter.getInstance().showConferenceCallManager(false);
- }
- }
-
- public void init(Context context, CallList callList) {
- mContext = Preconditions.checkNotNull(context);
- mContext = context;
- update(callList);
- }
-
- /**
- * Updates the conference participant adapter.
- *
- * @param callList The callList.
- */
- private void update(CallList callList) {
- // callList is non null, but getActiveOrBackgroundCall() may return null
- final Call currentCall = callList.getActiveOrBackgroundCall();
- if (currentCall == null) {
- return;
- }
-
- ArrayList<Call> calls = new ArrayList<>(currentCall.getChildCallIds().size());
- for (String callerId : currentCall.getChildCallIds()) {
- calls.add(callList.getCallById(callerId));
- }
-
- Log.d(this, "Number of calls is " + String.valueOf(calls.size()));
-
- // Users can split out a call from the conference call if either the active call or the
- // holding call is empty. If both are filled, users can not split out another call.
- final boolean hasActiveCall = (callList.getActiveCall() != null);
- final boolean hasHoldingCall = (callList.getBackgroundCall() != null);
- boolean canSeparate = !(hasActiveCall && hasHoldingCall);
-
- getUi().update(mContext, calls, canSeparate);
- }
-
- public interface ConferenceManagerUi extends Ui {
- boolean isFragmentVisible();
- void update(Context context, List<Call> participants, boolean parentCanSeparate);
- void refreshCall(Call call);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
deleted file mode 100644
index d68ae1f6f..000000000
--- a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
+++ /dev/null
@@ -1,533 +0,0 @@
-/*
- * Copyright (C) 2014 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 com.google.common.base.MoreObjects;
-
-import android.content.Context;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.contacts.common.ContactPhotoManager;
-import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
-import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.dialer.R;
-import com.android.incallui.ContactInfoCache.ContactCacheEntry;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * Adapter for a ListView containing conference call participant information.
- */
-public class ConferenceParticipantListAdapter extends BaseAdapter {
-
- /**
- * Internal class which represents a participant. Includes a reference to the {@link Call} and
- * the corresponding {@link ContactCacheEntry} for the participant.
- */
- private class ParticipantInfo {
- private Call mCall;
- private ContactCacheEntry mContactCacheEntry;
- private boolean mCacheLookupComplete = false;
-
- public ParticipantInfo(Call call, ContactCacheEntry contactCacheEntry) {
- mCall = call;
- mContactCacheEntry = contactCacheEntry;
- }
-
- public Call getCall() {
- return mCall;
- }
-
- public void setCall(Call call) {
- mCall = call;
- }
-
- public ContactCacheEntry getContactCacheEntry() {
- return mContactCacheEntry;
- }
-
- public void setContactCacheEntry(ContactCacheEntry entry) {
- mContactCacheEntry = entry;
- }
-
- public boolean isCacheLookupComplete() {
- return mCacheLookupComplete;
- }
-
- public void setCacheLookupComplete(boolean cacheLookupComplete) {
- mCacheLookupComplete = cacheLookupComplete;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof ParticipantInfo) {
- ParticipantInfo p = (ParticipantInfo) o;
- return
- Objects.equals(p.getCall().getId(), mCall.getId());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return mCall.getId().hashCode();
- }
- }
-
- /**
- * Callback class used when making requests to the {@link ContactInfoCache} to resolve contact
- * info and contact photos for conference participants.
- */
- public static class ContactLookupCallback implements ContactInfoCache.ContactInfoCacheCallback {
- private final WeakReference<ConferenceParticipantListAdapter> mListAdapter;
-
- public ContactLookupCallback(ConferenceParticipantListAdapter listAdapter) {
- mListAdapter = new WeakReference<ConferenceParticipantListAdapter>(listAdapter);
- }
-
- /**
- * Called when contact info has been resolved.
- *
- * @param callId The call id.
- * @param entry The new contact information.
- */
- @Override
- public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
- update(callId, entry);
- }
-
- /**
- * Called when contact photo has been loaded into the cache.
- *
- * @param callId The call id.
- * @param entry The new contact information.
- */
- @Override
- public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
- update(callId, entry);
- }
-
- @Override
- public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) {}
-
- /**
- * Updates the contact information for a participant.
- *
- * @param callId The call id.
- * @param entry The new contact information.
- */
- private void update(String callId, ContactCacheEntry entry) {
- ConferenceParticipantListAdapter listAdapter = mListAdapter.get();
- if (listAdapter != null) {
- listAdapter.updateContactInfo(callId, entry);
- }
- }
- }
-
- /**
- * Listener used to handle tap of the "disconnect' button for a participant.
- */
- private View.OnClickListener mDisconnectListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- View parent = (View) v.getParent();
- String callId = (String) parent.getTag();
- TelecomAdapter.getInstance().disconnectCall(callId);
- }
- };
-
- /**
- * Listener used to handle tap of the "separate' button for a participant.
- */
- private View.OnClickListener mSeparateListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- View parent = (View) v.getParent();
- String callId = (String) parent.getTag();
- TelecomAdapter.getInstance().separateCall(callId);
- }
- };
-
- /**
- * The ListView containing the participant information.
- */
- private final ListView mListView;
-
- /**
- * The conference participants to show in the ListView.
- */
- private List<ParticipantInfo> mConferenceParticipants = new ArrayList<>();
-
- /**
- * Hashmap to make accessing participant info by call Id faster.
- */
- private final HashMap<String, ParticipantInfo> mParticipantsByCallId = new HashMap<>();
-
- /**
- * The context.
- */
- private final Context mContext;
-
- /**
- * ContactsPreferences used to lookup displayName preferences
- */
- @Nullable private final ContactsPreferences mContactsPreferences;
-
- /**
- * The layout inflater used to inflate new views.
- */
- private final LayoutInflater mLayoutInflater;
-
- /**
- * Contact photo manager to retrieve cached contact photo information.
- */
- private final ContactPhotoManager mContactPhotoManager;
-
- /**
- * {@code True} if the conference parent supports separating calls from the conference.
- */
- private boolean mParentCanSeparate;
-
- /**
- * Creates an instance of the ConferenceParticipantListAdapter.
- *
- * @param listView The listview.
- * @param context The context.
- * @param layoutInflater The layout inflater.
- * @param contactPhotoManager The contact photo manager, used to load contact photos.
- */
- public ConferenceParticipantListAdapter(ListView listView, Context context,
- LayoutInflater layoutInflater, ContactPhotoManager contactPhotoManager) {
-
- mListView = listView;
- mContext = context;
- mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
- mLayoutInflater = layoutInflater;
- mContactPhotoManager = contactPhotoManager;
- }
-
- /**
- * Updates the adapter with the new conference participant information provided.
- *
- * @param conferenceParticipants The list of conference participants.
- * @param parentCanSeparate {@code True} if the parent supports separating calls from the
- * conference.
- */
- public void updateParticipants(List<Call> conferenceParticipants, boolean parentCanSeparate) {
- if (mContactsPreferences != null) {
- mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
- mContactsPreferences.refreshValue(ContactsPreferences.SORT_ORDER_KEY);
- }
- mParentCanSeparate = parentCanSeparate;
- updateParticipantInfo(conferenceParticipants);
- }
-
- /**
- * Determines the number of participants in the conference.
- *
- * @return The number of participants.
- */
- @Override
- public int getCount() {
- return mConferenceParticipants.size();
- }
-
- /**
- * Retrieves an item from the list of participants.
- *
- * @param position Position of the item whose data we want within the adapter's
- * data set.
- * @return The {@link ParticipantInfo}.
- */
- @Override
- public Object getItem(int position) {
- return mConferenceParticipants.get(position);
- }
-
- /**
- * Retreives the adapter-specific item id for an item at a specified position.
- *
- * @param position The position of the item within the adapter's data set whose row id we want.
- * @return The item id.
- */
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- /**
- * Refreshes call information for the call passed in.
- *
- * @param call The new call information.
- */
- public void refreshCall(Call call) {
- String callId = call.getId();
-
- if (mParticipantsByCallId.containsKey(callId)) {
- ParticipantInfo participantInfo = mParticipantsByCallId.get(callId);
- participantInfo.setCall(call);
- refreshView(callId);
- }
- }
-
- /**
- * Attempts to refresh the view for the specified call ID. This ensures the contact info and
- * photo loaded from cache are updated.
- *
- * @param callId The call id.
- */
- private void refreshView(String callId) {
- int first = mListView.getFirstVisiblePosition();
- int last = mListView.getLastVisiblePosition();
-
- for (int position = 0; position <= last - first; position++) {
- View view = mListView.getChildAt(position);
- String rowCallId = (String) view.getTag();
- if (rowCallId.equals(callId)) {
- getView(position+first, view, mListView);
- break;
- }
- }
- }
-
- /**
- * Creates or populates an existing conference participant row.
- *
- * @param position The position of the item within the adapter's data set of the item whose view
- * we want.
- * @param convertView The old view to reuse, if possible.
- * @param parent The parent that this view will eventually be attached to
- * @return The populated view.
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // Make sure we have a valid convertView to start with
- final View result = convertView == null
- ? mLayoutInflater.inflate(R.layout.caller_in_conference, parent, false)
- : convertView;
-
- ParticipantInfo participantInfo = mConferenceParticipants.get(position);
- Call call = participantInfo.getCall();
- ContactCacheEntry contactCache = participantInfo.getContactCacheEntry();
-
- final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
-
- // If a cache lookup has not yet been performed to retrieve the contact information and
- // photo, do it now.
- if (!participantInfo.isCacheLookupComplete()) {
- cache.findInfo(participantInfo.getCall(),
- participantInfo.getCall().getState() == Call.State.INCOMING,
- new ContactLookupCallback(this));
- }
-
- boolean thisRowCanSeparate = mParentCanSeparate && call.getTelecomCall().getDetails().can(
- android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE);
- boolean thisRowCanDisconnect = call.getTelecomCall().getDetails().can(
- android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE);
-
- setCallerInfoForRow(result, contactCache.namePrimary,
- ContactDisplayUtils.getPreferredDisplayName(contactCache.namePrimary,
- contactCache.nameAlternative, mContactsPreferences),
- contactCache.number, contactCache.label,
- contactCache.lookupKey, contactCache.displayPhotoUri, thisRowCanSeparate,
- thisRowCanDisconnect);
-
- // Tag the row in the conference participant list with the call id to make it easier to
- // find calls when contact cache information is loaded.
- result.setTag(call.getId());
-
- return result;
- }
-
- /**
- * Replaces the contact info for a participant and triggers a refresh of the UI.
- *
- * @param callId The call id.
- * @param entry The new contact info.
- */
- /* package */ void updateContactInfo(String callId, ContactCacheEntry entry) {
- if (mParticipantsByCallId.containsKey(callId)) {
- ParticipantInfo participantInfo = mParticipantsByCallId.get(callId);
- participantInfo.setContactCacheEntry(entry);
- participantInfo.setCacheLookupComplete(true);
- refreshView(callId);
- }
- }
-
- /**
- * Sets the caller information for a row in the conference participant list.
- *
- * @param view The view to set the details on.
- * @param callerName The participant's name.
- * @param callerNumber The participant's phone number.
- * @param callerNumberType The participant's phone number typ.e
- * @param lookupKey The lookup key for the participant (for photo lookup).
- * @param photoUri The URI of the contact photo.
- * @param thisRowCanSeparate {@code True} if this participant can separate from the conference.
- * @param thisRowCanDisconnect {@code True} if this participant can be disconnected.
- */
- private final void setCallerInfoForRow(View view, String callerName, String preferredName,
- String callerNumber, String callerNumberType, String lookupKey, Uri photoUri,
- boolean thisRowCanSeparate, boolean thisRowCanDisconnect) {
-
- final ImageView photoView = (ImageView) view.findViewById(R.id.callerPhoto);
- final TextView nameTextView = (TextView) view.findViewById(R.id.conferenceCallerName);
- final TextView numberTextView = (TextView) view.findViewById(R.id.conferenceCallerNumber);
- final TextView numberTypeTextView = (TextView) view.findViewById(
- R.id.conferenceCallerNumberType);
- final View endButton = view.findViewById(R.id.conferenceCallerDisconnect);
- final View separateButton = view.findViewById(R.id.conferenceCallerSeparate);
-
- endButton.setVisibility(thisRowCanDisconnect ? View.VISIBLE : View.GONE);
- if (thisRowCanDisconnect) {
- endButton.setOnClickListener(mDisconnectListener);
- } else {
- endButton.setOnClickListener(null);
- }
-
- separateButton.setVisibility(thisRowCanSeparate ? View.VISIBLE : View.GONE);
- if (thisRowCanSeparate) {
- separateButton.setOnClickListener(mSeparateListener);
- } else {
- separateButton.setOnClickListener(null);
- }
-
- DefaultImageRequest imageRequest = (photoUri != null) ? null :
- new DefaultImageRequest(callerName, lookupKey, true /* isCircularPhoto */);
-
- mContactPhotoManager.loadDirectoryPhoto(photoView, photoUri, false, true, imageRequest);
-
- // set the caller name
- nameTextView.setText(preferredName);
-
- // set the caller number in subscript, or make the field disappear.
- if (TextUtils.isEmpty(callerNumber)) {
- numberTextView.setVisibility(View.GONE);
- numberTypeTextView.setVisibility(View.GONE);
- } else {
- numberTextView.setVisibility(View.VISIBLE);
- numberTextView.setText(PhoneNumberUtilsCompat.createTtsSpannable(
- BidiFormatter.getInstance().unicodeWrap(
- callerNumber, TextDirectionHeuristics.LTR)));
- numberTypeTextView.setVisibility(View.VISIBLE);
- numberTypeTextView.setText(callerNumberType);
- }
- }
-
- /**
- * Updates the participant info list which is bound to the ListView. Stores the call and
- * contact info for all entries. The list is sorted alphabetically by participant name.
- *
- * @param conferenceParticipants The calls which make up the conference participants.
- */
- private void updateParticipantInfo(List<Call> conferenceParticipants) {
- final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
- boolean newParticipantAdded = false;
- HashSet<String> newCallIds = new HashSet<>(conferenceParticipants.size());
-
- // Update or add conference participant info.
- for (Call call : conferenceParticipants) {
- String callId = call.getId();
- newCallIds.add(callId);
- ContactCacheEntry contactCache = cache.getInfo(callId);
- if (contactCache == null) {
- contactCache = ContactInfoCache.buildCacheEntryFromCall(mContext, call,
- call.getState() == Call.State.INCOMING);
- }
-
- if (mParticipantsByCallId.containsKey(callId)) {
- ParticipantInfo participantInfo = mParticipantsByCallId.get(callId);
- participantInfo.setCall(call);
- participantInfo.setContactCacheEntry(contactCache);
- } else {
- newParticipantAdded = true;
- ParticipantInfo participantInfo = new ParticipantInfo(call, contactCache);
- mConferenceParticipants.add(participantInfo);
- mParticipantsByCallId.put(call.getId(), participantInfo);
- }
- }
-
- // Remove any participants that no longer exist.
- Iterator<Map.Entry<String, ParticipantInfo>> it =
- mParticipantsByCallId.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<String, ParticipantInfo> entry = it.next();
- String existingCallId = entry.getKey();
- if (!newCallIds.contains(existingCallId)) {
- ParticipantInfo existingInfo = entry.getValue();
- mConferenceParticipants.remove(existingInfo);
- it.remove();
- }
- }
-
- if (newParticipantAdded) {
- // Sort the list of participants by contact name.
- sortParticipantList();
- }
- notifyDataSetChanged();
- }
-
- /**
- * Sorts the participant list by contact name.
- */
- private void sortParticipantList() {
- Collections.sort(mConferenceParticipants, new Comparator<ParticipantInfo>() {
- public int compare(ParticipantInfo p1, ParticipantInfo p2) {
- // Contact names might be null, so replace with empty string.
- ContactCacheEntry c1 = p1.getContactCacheEntry();
- String p1Name = MoreObjects.firstNonNull(
- ContactDisplayUtils.getPreferredSortName(
- c1.namePrimary,
- c1.nameAlternative,
- mContactsPreferences),
- "");
-
- ContactCacheEntry c2 = p2.getContactCacheEntry();
- String p2Name = MoreObjects.firstNonNull(
- ContactDisplayUtils.getPreferredSortName(
- c2.namePrimary,
- c2.nameAlternative,
- mContactsPreferences),
- "");
-
- return p1Name.compareToIgnoreCase(p2Name);
- }
- });
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java
deleted file mode 100644
index 9d6fc4627..000000000
--- a/InCallUI/src/com/android/incallui/ContactInfoCache.java
+++ /dev/null
@@ -1,699 +0,0 @@
-/*
- * Copyright (C) 2013 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 com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.location.Address;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.telecom.TelecomManager;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.util.PhoneNumberHelper;
-import com.android.dialer.R;
-import com.android.dialer.calllog.ContactInfo;
-import com.android.dialer.service.CachedNumberLookupService;
-import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
-import com.android.dialer.util.MoreStrings;
-import com.android.incallui.Call.LogState;
-import com.android.incallui.service.PhoneNumberService;
-import com.android.incalluibind.ObjectFactory;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Class responsible for querying Contact Information for Call objects. Can perform asynchronous
- * requests to the Contact Provider for information as well as respond synchronously for any data
- * that it currently has cached from previous queries. This class always gets called from the UI
- * thread so it does not need thread protection.
- */
-public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadCompleteListener {
-
- private static final String TAG = ContactInfoCache.class.getSimpleName();
- private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
-
- private final Context mContext;
- private final PhoneNumberService mPhoneNumberService;
- private final CachedNumberLookupService mCachedNumberLookupService;
- private final HashMap<String, ContactCacheEntry> mInfoMap = Maps.newHashMap();
- private final HashMap<String, Set<ContactInfoCacheCallback>> mCallBacks = Maps.newHashMap();
-
- private static ContactInfoCache sCache = null;
-
- private Drawable mDefaultContactPhotoDrawable;
- private Drawable mConferencePhotoDrawable;
- private ContactUtils mContactUtils;
-
- public static synchronized ContactInfoCache getInstance(Context mContext) {
- if (sCache == null) {
- sCache = new ContactInfoCache(mContext.getApplicationContext());
- }
- return sCache;
- }
-
- private ContactInfoCache(Context context) {
- mContext = context;
- mPhoneNumberService = ObjectFactory.newPhoneNumberService(context);
- mCachedNumberLookupService =
- com.android.dialerbind.ObjectFactory.newCachedNumberLookupService();
- mContactUtils = ObjectFactory.getContactUtilsInstance(context);
-
- }
-
- public ContactCacheEntry getInfo(String callId) {
- return mInfoMap.get(callId);
- }
-
- public static ContactCacheEntry buildCacheEntryFromCall(Context context, Call call,
- boolean isIncoming) {
- final ContactCacheEntry entry = new ContactCacheEntry();
-
- // TODO: get rid of caller info.
- final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, call);
- ContactInfoCache.populateCacheEntry(context, info, entry, call.getNumberPresentation(),
- isIncoming);
- return entry;
- }
-
- public void maybeInsertCnapInformationIntoCache(Context context, final Call call,
- final CallerInfo info) {
- if (mCachedNumberLookupService == null || TextUtils.isEmpty(info.cnapName)
- || mInfoMap.get(call.getId()) != null) {
- return;
- }
- final Context applicationContext = context.getApplicationContext();
- Log.i(TAG, "Found contact with CNAP name - inserting into cache");
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- ContactInfo contactInfo = new ContactInfo();
- CachedContactInfo cacheInfo = mCachedNumberLookupService.buildCachedContactInfo(
- contactInfo);
- cacheInfo.setSource(CachedContactInfo.SOURCE_TYPE_CNAP, "CNAP", 0);
- contactInfo.name = info.cnapName;
- contactInfo.number = call.getNumber();
- contactInfo.type = ContactsContract.CommonDataKinds.Phone.TYPE_MAIN;
- try {
- final JSONObject contactRows = new JSONObject().put(Phone.CONTENT_ITEM_TYPE,
- new JSONObject()
- .put(Phone.NUMBER, contactInfo.number)
- .put(Phone.TYPE, Phone.TYPE_MAIN));
- final String jsonString = new JSONObject()
- .put(Contacts.DISPLAY_NAME, contactInfo.name)
- .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME)
- .put(Contacts.CONTENT_ITEM_TYPE, contactRows).toString();
- cacheInfo.setLookupKey(jsonString);
- } catch (JSONException e) {
- Log.w(TAG, "Creation of lookup key failed when caching CNAP information");
- }
- mCachedNumberLookupService.addContact(applicationContext, cacheInfo);
- return null;
- }
- }.execute();
- }
-
- private class FindInfoCallback implements CallerInfoAsyncQuery.OnQueryCompleteListener {
- private final boolean mIsIncoming;
-
- public FindInfoCallback(boolean isIncoming) {
- mIsIncoming = isIncoming;
- }
-
- @Override
- public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
- findInfoQueryComplete((Call) cookie, callerInfo, mIsIncoming, true);
- }
- }
-
- /**
- * Requests contact data for the Call object passed in.
- * Returns the data through callback. If callback is null, no response is made, however the
- * query is still performed and cached.
- *
- * @param callback The function to call back when the call is found. Can be null.
- */
- public void findInfo(final Call call, final boolean isIncoming,
- ContactInfoCacheCallback callback) {
- Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread());
- Preconditions.checkNotNull(callback);
-
- final String callId = call.getId();
- final ContactCacheEntry cacheEntry = mInfoMap.get(callId);
- Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
-
- // If we have a previously obtained intermediate result return that now
- if (cacheEntry != null) {
- Log.d(TAG, "Contact lookup. In memory cache hit; lookup "
- + (callBacks == null ? "complete" : "still running"));
- callback.onContactInfoComplete(callId, cacheEntry);
- // If no other callbacks are in flight, we're done.
- if (callBacks == null) {
- return;
- }
- }
-
- // If the entry already exists, add callback
- if (callBacks != null) {
- callBacks.add(callback);
- return;
- }
- Log.d(TAG, "Contact lookup. In memory cache miss; searching provider.");
- // New lookup
- callBacks = Sets.newHashSet();
- callBacks.add(callback);
- mCallBacks.put(callId, callBacks);
-
- /**
- * Performs a query for caller information.
- * Save any immediate data we get from the query. An asynchronous query may also be made
- * for any data that we do not already have. Some queries, such as those for voicemail and
- * emergency call information, will not perform an additional asynchronous query.
- */
- final CallerInfo callerInfo = CallerInfoUtils.getCallerInfoForCall(
- mContext, call, new FindInfoCallback(isIncoming));
-
- findInfoQueryComplete(call, callerInfo, isIncoming, false);
- }
-
- private void findInfoQueryComplete(Call call, CallerInfo callerInfo, boolean isIncoming,
- boolean didLocalLookup) {
- final String callId = call.getId();
- int presentationMode = call.getNumberPresentation();
- if (callerInfo.contactExists || callerInfo.isEmergencyNumber() ||
- callerInfo.isVoiceMailNumber()) {
- presentationMode = TelecomManager.PRESENTATION_ALLOWED;
- }
-
- ContactCacheEntry cacheEntry = mInfoMap.get(callId);
- // Ensure we always have a cacheEntry. Replace the existing entry if
- // it has no name or if we found a local contact.
- if (cacheEntry == null || TextUtils.isEmpty(cacheEntry.namePrimary) ||
- callerInfo.contactExists) {
- cacheEntry = buildEntry(mContext, callId, callerInfo, presentationMode, isIncoming);
- mInfoMap.put(callId, cacheEntry);
- }
-
- sendInfoNotifications(callId, cacheEntry);
-
- if (didLocalLookup) {
- // Before issuing a request for more data from other services, we only check that the
- // contact wasn't found in the local DB. We don't check the if the cache entry already
- // has a name because we allow overriding cnap data with data from other services.
- if (!callerInfo.contactExists && mPhoneNumberService != null) {
- Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
- final PhoneNumberServiceListener listener = new PhoneNumberServiceListener(callId);
- mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener,
- isIncoming);
- } else if (cacheEntry.displayPhotoUri != null) {
- Log.d(TAG, "Contact lookup. Local contact found, starting image load");
- // Load the image with a callback to update the image state.
- // When the load is finished, onImageLoadComplete() will be called.
- cacheEntry.isLoadingPhoto = true;
- ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
- mContext, cacheEntry.displayPhotoUri, ContactInfoCache.this, callId);
- } else {
- if (callerInfo.contactExists) {
- Log.d(TAG, "Contact lookup done. Local contact found, no image.");
- } else {
- Log.d(TAG, "Contact lookup done. Local contact not found and"
- + " no remote lookup service available.");
- }
- clearCallbacks(callId);
- }
- }
- }
-
- class PhoneNumberServiceListener implements PhoneNumberService.NumberLookupListener,
- PhoneNumberService.ImageLookupListener, ContactUtils.Listener {
- private final String mCallId;
-
- PhoneNumberServiceListener(String callId) {
- mCallId = callId;
- }
-
- @Override
- public void onPhoneNumberInfoComplete(
- final PhoneNumberService.PhoneNumberInfo info) {
- // If we got a miss, this is the end of the lookup pipeline,
- // so clear the callbacks and return.
- if (info == null) {
- Log.d(TAG, "Contact lookup done. Remote contact not found.");
- clearCallbacks(mCallId);
- return;
- }
-
- ContactCacheEntry entry = new ContactCacheEntry();
- entry.namePrimary = info.getDisplayName();
- entry.number = info.getNumber();
- entry.contactLookupResult = info.getLookupSource();
- final int type = info.getPhoneType();
- final String label = info.getPhoneLabel();
- if (type == Phone.TYPE_CUSTOM) {
- entry.label = label;
- } else {
- final CharSequence typeStr = Phone.getTypeLabel(
- mContext.getResources(), type, label);
- entry.label = typeStr == null ? null : typeStr.toString();
- }
- final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
- if (oldEntry != null) {
- // Location is only obtained from local lookup so persist
- // the value for remote lookups. Once we have a name this
- // field is no longer used; it is persisted here in case
- // the UI is ever changed to use it.
- entry.location = oldEntry.location;
- // Contact specific ringtone is obtained from local lookup.
- entry.contactRingtoneUri = oldEntry.contactRingtoneUri;
- }
-
- // If no image and it's a business, switch to using the default business avatar.
- if (info.getImageUrl() == null && info.isBusiness()) {
- Log.d(TAG, "Business has no image. Using default.");
- entry.photo = mContext.getResources().getDrawable(R.drawable.img_business);
- }
-
- mInfoMap.put(mCallId, entry);
- sendInfoNotifications(mCallId, entry);
-
- if (mContactUtils != null) {
- // This method will callback "onContactInteractionsFound".
- entry.isLoadingContactInteractions =
- mContactUtils.retrieveContactInteractionsFromLookupKey(
- info.getLookupKey(), this);
- }
-
- entry.isLoadingPhoto = info.getImageUrl() != null;
-
- // If there is no image or contact interactions then we should not expect another
- // callback.
- if (!entry.isLoadingPhoto && !entry.isLoadingContactInteractions) {
- // We're done, so clear callbacks
- clearCallbacks(mCallId);
- }
- }
-
- @Override
- public void onImageFetchComplete(Bitmap bitmap) {
- onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, mCallId);
- }
-
- @Override
- public void onContactInteractionsFound(Address address,
- List<Pair<Calendar, Calendar>> openingHours) {
- final ContactCacheEntry entry = mInfoMap.get(mCallId);
- if (entry == null) {
- Log.e(this, "Contact context received for empty search entry.");
- clearCallbacks(mCallId);
- return;
- }
-
- entry.isLoadingContactInteractions = false;
-
- Log.v(ContactInfoCache.this, "Setting contact interactions for entry: ", entry);
-
- entry.locationAddress = address;
- entry.openingHours = openingHours;
- sendContactInteractionsNotifications(mCallId, entry);
-
- if (!entry.isLoadingPhoto) {
- clearCallbacks(mCallId);
- }
- }
- }
-
- /**
- * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface.
- * make sure that the call state is reflected after the image is loaded.
- */
- @Override
- public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
- Log.d(this, "Image load complete with context: ", mContext);
- // TODO: may be nice to update the image view again once the newer one
- // is available on contacts database.
-
- final String callId = (String) cookie;
- final ContactCacheEntry entry = mInfoMap.get(callId);
-
- if (entry == null) {
- Log.e(this, "Image Load received for empty search entry.");
- clearCallbacks(callId);
- return;
- }
-
- entry.isLoadingPhoto = false;
-
- Log.d(this, "setting photo for entry: ", entry);
-
- // Conference call icons are being handled in CallCardPresenter.
- if (photo != null) {
- Log.v(this, "direct drawable: ", photo);
- entry.photo = photo;
- } else if (photoIcon != null) {
- Log.v(this, "photo icon: ", photoIcon);
- entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
- } else {
- Log.v(this, "unknown photo");
- entry.photo = null;
- }
-
- sendImageNotifications(callId, entry);
-
- if (!entry.isLoadingContactInteractions) {
- clearCallbacks(callId);
- }
- }
-
- /**
- * Blows away the stored cache values.
- */
- public void clearCache() {
- mInfoMap.clear();
- mCallBacks.clear();
- }
-
- private ContactCacheEntry buildEntry(Context context, String callId,
- CallerInfo info, int presentation, boolean isIncoming) {
- // The actual strings we're going to display onscreen:
- Drawable photo = null;
-
- final ContactCacheEntry cce = new ContactCacheEntry();
- populateCacheEntry(context, info, cce, presentation, isIncoming);
-
- // This will only be true for emergency numbers
- if (info.photoResource != 0) {
- photo = context.getResources().getDrawable(info.photoResource);
- } else if (info.isCachedPhotoCurrent) {
- if (info.cachedPhoto != null) {
- photo = info.cachedPhoto;
- } else {
- photo = getDefaultContactPhotoDrawable();
- }
- } else if (info.contactDisplayPhotoUri == null) {
- photo = getDefaultContactPhotoDrawable();
- } else {
- cce.displayPhotoUri = info.contactDisplayPhotoUri;
- }
-
- // Support any contact id in N because QuickContacts in N starts supporting enterprise
- // contact id
- if (info.lookupKeyOrNull != null
- && (ContactsUtils.FLAG_N_FEATURE || info.contactIdOrZero != 0)) {
- cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull);
- } else {
- Log.v(TAG, "lookup key is null or contact ID is 0 on M. Don't create a lookup uri.");
- cce.lookupUri = null;
- }
-
- cce.photo = photo;
- cce.lookupKey = info.lookupKeyOrNull;
- cce.contactRingtoneUri = info.contactRingtoneUri;
- if (cce.contactRingtoneUri == null || cce.contactRingtoneUri == Uri.EMPTY) {
- cce.contactRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
- }
-
- return cce;
- }
-
- /**
- * Populate a cache entry from a call (which got converted into a caller info).
- */
- public static void populateCacheEntry(Context context, CallerInfo info, ContactCacheEntry cce,
- int presentation, boolean isIncoming) {
- Preconditions.checkNotNull(info);
- String displayName = null;
- String displayNumber = null;
- String displayLocation = null;
- String label = null;
- boolean isSipCall = false;
-
- // It appears that there is a small change in behaviour with the
- // PhoneUtils' startGetCallerInfo whereby if we query with an
- // empty number, we will get a valid CallerInfo object, but with
- // fields that are all null, and the isTemporary boolean input
- // parameter as true.
-
- // In the past, we would see a NULL callerinfo object, but this
- // ends up causing null pointer exceptions elsewhere down the
- // line in other cases, so we need to make this fix instead. It
- // appears that this was the ONLY call to PhoneUtils
- // .getCallerInfo() that relied on a NULL CallerInfo to indicate
- // an unknown contact.
-
- // Currently, infi.phoneNumber may actually be a SIP address, and
- // if so, it might sometimes include the "sip:" prefix. That
- // prefix isn't really useful to the user, though, so strip it off
- // if present. (For any other URI scheme, though, leave the
- // prefix alone.)
- // TODO: It would be cleaner for CallerInfo to explicitly support
- // SIP addresses instead of overloading the "phoneNumber" field.
- // Then we could remove this hack, and instead ask the CallerInfo
- // for a "user visible" form of the SIP address.
- String number = info.phoneNumber;
-
- if (!TextUtils.isEmpty(number)) {
- isSipCall = PhoneNumberHelper.isUriNumber(number);
- if (number.startsWith("sip:")) {
- number = number.substring(4);
- }
- }
-
- if (TextUtils.isEmpty(info.name)) {
- // No valid "name" in the CallerInfo, so fall back to
- // something else.
- // (Typically, we promote the phone number up to the "name" slot
- // onscreen, and possibly display a descriptive string in the
- // "number" slot.)
- if (TextUtils.isEmpty(number)) {
- // No name *or* number! Display a generic "unknown" string
- // (or potentially some other default based on the presentation.)
- displayName = getPresentationString(context, presentation, info.callSubject);
- Log.d(TAG, " ==> no name *or* number! displayName = " + displayName);
- } else if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
- // This case should never happen since the network should never send a phone #
- // AND a restricted presentation. However we leave it here in case of weird
- // network behavior
- displayName = getPresentationString(context, presentation, info.callSubject);
- Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName);
- } else if (!TextUtils.isEmpty(info.cnapName)) {
- // No name, but we do have a valid CNAP name, so use that.
- displayName = info.cnapName;
- info.name = info.cnapName;
- displayNumber = number;
- Log.d(TAG, " ==> cnapName available: displayName '" + displayName +
- "', displayNumber '" + displayNumber + "'");
- } else {
- // No name; all we have is a number. This is the typical
- // case when an incoming call doesn't match any contact,
- // or if you manually dial an outgoing number using the
- // dialpad.
- displayNumber = number;
-
- // Display a geographical description string if available
- // (but only for incoming calls.)
- if (isIncoming) {
- // TODO (CallerInfoAsyncQuery cleanup): Fix the CallerInfo
- // query to only do the geoDescription lookup in the first
- // place for incoming calls.
- displayLocation = info.geoDescription; // may be null
- Log.d(TAG, "Geodescrption: " + info.geoDescription);
- }
-
- Log.d(TAG, " ==> no name; falling back to number:"
- + " displayNumber '" + Log.pii(displayNumber)
- + "', displayLocation '" + displayLocation + "'");
- }
- } else {
- // We do have a valid "name" in the CallerInfo. Display that
- // in the "name" slot, and the phone number in the "number" slot.
- if (presentation != TelecomManager.PRESENTATION_ALLOWED) {
- // This case should never happen since the network should never send a name
- // AND a restricted presentation. However we leave it here in case of weird
- // network behavior
- displayName = getPresentationString(context, presentation, info.callSubject);
- Log.d(TAG, " ==> valid name, but presentation not allowed!" +
- " displayName = " + displayName);
- } else {
- // Causes cce.namePrimary to be set as info.name below. CallCardPresenter will
- // later determine whether to use the name or nameAlternative when presenting
- displayName = info.name;
- cce.nameAlternative = info.nameAlternative;
- displayNumber = number;
- label = info.phoneLabel;
- Log.d(TAG, " ==> name is present in CallerInfo: displayName '" + displayName
- + "', displayNumber '" + displayNumber + "'");
- }
- }
-
- cce.namePrimary = displayName;
- cce.number = displayNumber;
- cce.location = displayLocation;
- cce.label = label;
- cce.isSipCall = isSipCall;
- cce.userType = info.userType;
-
- if (info.contactExists) {
- cce.contactLookupResult = LogState.LOOKUP_LOCAL_CONTACT;
- }
- }
-
- /**
- * Sends the updated information to call the callbacks for the entry.
- */
- private void sendInfoNotifications(String callId, ContactCacheEntry entry) {
- final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
- if (callBacks != null) {
- for (ContactInfoCacheCallback callBack : callBacks) {
- callBack.onContactInfoComplete(callId, entry);
- }
- }
- }
-
- private void sendImageNotifications(String callId, ContactCacheEntry entry) {
- final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
- if (callBacks != null && entry.photo != null) {
- for (ContactInfoCacheCallback callBack : callBacks) {
- callBack.onImageLoadComplete(callId, entry);
- }
- }
- }
-
- private void sendContactInteractionsNotifications(String callId, ContactCacheEntry entry) {
- final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
- if (callBacks != null) {
- for (ContactInfoCacheCallback callBack : callBacks) {
- callBack.onContactInteractionsInfoComplete(callId, entry);
- }
- }
- }
-
- private void clearCallbacks(String callId) {
- mCallBacks.remove(callId);
- }
-
- /**
- * Gets name strings based on some special presentation modes and the associated custom label.
- */
- private static String getPresentationString(Context context, int presentation,
- String customLabel) {
- String name = context.getString(R.string.unknown);
- if (!TextUtils.isEmpty(customLabel) &&
- ((presentation == TelecomManager.PRESENTATION_UNKNOWN) ||
- (presentation == TelecomManager.PRESENTATION_RESTRICTED))) {
- name = customLabel;
- return name;
- } else {
- if (presentation == TelecomManager.PRESENTATION_RESTRICTED) {
- name = context.getString(R.string.private_num);
- } else if (presentation == TelecomManager.PRESENTATION_PAYPHONE) {
- name = context.getString(R.string.payphone);
- }
- }
- return name;
- }
-
- public Drawable getDefaultContactPhotoDrawable() {
- if (mDefaultContactPhotoDrawable == null) {
- mDefaultContactPhotoDrawable =
- mContext.getResources().getDrawable(R.drawable.img_no_image_automirrored);
- }
- return mDefaultContactPhotoDrawable;
- }
-
- public Drawable getConferenceDrawable() {
- if (mConferencePhotoDrawable == null) {
- mConferencePhotoDrawable =
- mContext.getResources().getDrawable(R.drawable.img_conference_automirrored);
- }
- return mConferencePhotoDrawable;
- }
-
- /**
- * Callback interface for the contact query.
- */
- public interface ContactInfoCacheCallback {
- public void onContactInfoComplete(String callId, ContactCacheEntry entry);
- public void onImageLoadComplete(String callId, ContactCacheEntry entry);
- public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry);
- }
-
- public static class ContactCacheEntry {
- public String namePrimary;
- public String nameAlternative;
- public String number;
- public String location;
- public String label;
- public Drawable photo;
- public boolean isSipCall;
- // Note in cache entry whether this is a pending async loading action to know whether to
- // wait for its callback or not.
- public boolean isLoadingPhoto;
- public boolean isLoadingContactInteractions;
- /** This will be used for the "view" notification. */
- public Uri contactUri;
- /** Either a display photo or a thumbnail URI. */
- public Uri displayPhotoUri;
- public Uri lookupUri; // Sent to NotificationMananger
- public String lookupKey;
- public Address locationAddress;
- public List<Pair<Calendar, Calendar>> openingHours;
- public int contactLookupResult = LogState.LOOKUP_NOT_FOUND;
- public long userType = ContactsUtils.USER_TYPE_CURRENT;
- public Uri contactRingtoneUri;
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("name", MoreStrings.toSafeString(namePrimary))
- .add("nameAlternative", MoreStrings.toSafeString(nameAlternative))
- .add("number", MoreStrings.toSafeString(number))
- .add("location", MoreStrings.toSafeString(location))
- .add("label", label)
- .add("photo", photo)
- .add("isSipCall", isSipCall)
- .add("contactUri", contactUri)
- .add("displayPhotoUri", displayPhotoUri)
- .add("locationAddress", locationAddress)
- .add("openingHours", openingHours)
- .add("contactLookupResult", contactLookupResult)
- .add("userType", userType)
- .add("contactRingtoneUri", contactRingtoneUri)
- .toString();
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ContactUtils.java b/InCallUI/src/com/android/incallui/ContactUtils.java
deleted file mode 100644
index 0750af731..000000000
--- a/InCallUI/src/com/android/incallui/ContactUtils.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2015 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.Context;
-import android.location.Address;
-import android.util.Pair;
-
-import com.android.incalluibind.ObjectFactory;
-
-import java.util.Calendar;
-import java.util.List;
-
-/**
- * Utility functions to help manipulate contact data.
- */
-public abstract class ContactUtils {
- protected Context mContext;
-
- public static ContactUtils getInstance(Context context) {
- return ObjectFactory.getContactUtilsInstance(context);
- }
-
- protected ContactUtils(Context context) {
- mContext = context;
- }
-
- public interface Listener {
- public void onContactInteractionsFound(Address address,
- List<Pair<Calendar, Calendar>> openingHours);
- }
-
- public abstract boolean retrieveContactInteractionsFromLookupKey(String lookupKey,
- Listener listener);
-}
diff --git a/InCallUI/src/com/android/incallui/ContactsAsyncHelper.java b/InCallUI/src/com/android/incallui/ContactsAsyncHelper.java
deleted file mode 100644
index d959fadd4..000000000
--- a/InCallUI/src/com/android/incallui/ContactsAsyncHelper.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2008 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.Notification;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.ContactsContract.Contacts;
-
-import com.android.dialer.R;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Helper class for loading contacts photo asynchronously.
- */
-public class ContactsAsyncHelper {
-
- /**
- * Interface for a WorkerHandler result return.
- */
- public interface OnImageLoadCompleteListener {
- /**
- * Called when the image load is complete.
- *
- * @param token Integer passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int,
- * Context, Uri, OnImageLoadCompleteListener, Object)}.
- * @param photo Drawable object obtained by the async load.
- * @param photoIcon Bitmap object obtained by the async load.
- * @param cookie Object passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int,
- * Context, Uri, OnImageLoadCompleteListener, Object)}. Can be null iff. the original
- * cookie is null.
- */
- public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon,
- Object cookie);
- }
-
- // constants
- private static final int EVENT_LOAD_IMAGE = 1;
-
- private final Handler mResultHandler = new Handler() {
- /** Called when loading is done. */
- @Override
- public void handleMessage(Message msg) {
- WorkerArgs args = (WorkerArgs) msg.obj;
- switch (msg.arg1) {
- case EVENT_LOAD_IMAGE:
- if (args.listener != null) {
- Log.d(this, "Notifying listener: " + args.listener.toString() +
- " image: " + args.displayPhotoUri + " completed");
- args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
- args.cookie);
- }
- break;
- default:
- }
- }
- };
-
- /** Handler run on a worker thread to load photo asynchronously. */
- private static Handler sThreadHandler;
-
- /** For forcing the system to call its constructor */
- @SuppressWarnings("unused")
- private static ContactsAsyncHelper sInstance;
-
- static {
- sInstance = new ContactsAsyncHelper();
- }
-
- private static final class WorkerArgs {
- public Context context;
- public Uri displayPhotoUri;
- public Drawable photo;
- public Bitmap photoIcon;
- public Object cookie;
- public OnImageLoadCompleteListener listener;
- }
-
- /**
- * Thread worker class that handles the task of opening the stream and loading
- * the images.
- */
- private class WorkerHandler extends Handler {
- public WorkerHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- WorkerArgs args = (WorkerArgs) msg.obj;
-
- switch (msg.arg1) {
- case EVENT_LOAD_IMAGE:
- InputStream inputStream = null;
- try {
- try {
- inputStream = args.context.getContentResolver()
- .openInputStream(args.displayPhotoUri);
- } catch (Exception e) {
- Log.e(this, "Error opening photo input stream", e);
- }
-
- if (inputStream != null) {
- args.photo = Drawable.createFromStream(inputStream,
- args.displayPhotoUri.toString());
-
- // This assumes Drawable coming from contact database is usually
- // BitmapDrawable and thus we can have (down)scaled version of it.
- args.photoIcon = getPhotoIconWhenAppropriate(args.context, args.photo);
-
- Log.d(ContactsAsyncHelper.this, "Loading image: " + msg.arg1 +
- " token: " + msg.what + " image URI: " + args.displayPhotoUri);
- } else {
- args.photo = null;
- args.photoIcon = null;
- Log.d(ContactsAsyncHelper.this, "Problem with image: " + msg.arg1 +
- " token: " + msg.what + " image URI: " + args.displayPhotoUri +
- ", using default image.");
- }
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(this, "Unable to close input stream.", e);
- }
- }
- }
- break;
- default:
- }
-
- // send the reply to the enclosing class.
- Message reply = ContactsAsyncHelper.this.mResultHandler.obtainMessage(msg.what);
- reply.arg1 = msg.arg1;
- reply.obj = msg.obj;
- reply.sendToTarget();
- }
-
- /**
- * Returns a Bitmap object suitable for {@link Notification}'s large icon. This might
- * return null when the given Drawable isn't BitmapDrawable, or if the system fails to
- * create a scaled Bitmap for the Drawable.
- */
- private Bitmap getPhotoIconWhenAppropriate(Context context, Drawable photo) {
- if (!(photo instanceof BitmapDrawable)) {
- return null;
- }
- int iconSize = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_icon_size);
- Bitmap orgBitmap = ((BitmapDrawable) photo).getBitmap();
- int orgWidth = orgBitmap.getWidth();
- int orgHeight = orgBitmap.getHeight();
- int longerEdge = orgWidth > orgHeight ? orgWidth : orgHeight;
- // We want downscaled one only when the original icon is too big.
- if (longerEdge > iconSize) {
- float ratio = ((float) longerEdge) / iconSize;
- int newWidth = (int) (orgWidth / ratio);
- int newHeight = (int) (orgHeight / ratio);
- // If the longer edge is much longer than the shorter edge, the latter may
- // become 0 which will cause a crash.
- if (newWidth <= 0 || newHeight <= 0) {
- Log.w(this, "Photo icon's width or height become 0.");
- return null;
- }
-
- // It is sure ratio >= 1.0f in any case and thus the newly created Bitmap
- // should be smaller than the original.
- return Bitmap.createScaledBitmap(orgBitmap, newWidth, newHeight, true);
- } else {
- return orgBitmap;
- }
- }
- }
-
- /**
- * Private constructor for static class
- */
- private ContactsAsyncHelper() {
- HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
- thread.start();
- sThreadHandler = new WorkerHandler(thread.getLooper());
- }
-
- /**
- * Starts an asynchronous image load. After finishing the load,
- * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)}
- * will be called.
- *
- * @param token Arbitrary integer which will be returned as the first argument of
- * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)}
- * @param context Context object used to do the time-consuming operation.
- * @param displayPhotoUri Uri to be used to fetch the photo
- * @param listener Callback object which will be used when the asynchronous load is done.
- * Can be null, which means only the asynchronous load is done while there's no way to
- * obtain the loaded photos.
- * @param cookie Arbitrary object the caller wants to remember, which will become the
- * fourth argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable,
- * Bitmap, Object)}. Can be null, at which the callback will also has null for the argument.
- */
- public static final void startObtainPhotoAsync(int token, Context context, Uri displayPhotoUri,
- OnImageLoadCompleteListener listener, Object cookie) {
- // in case the source caller info is null, the URI will be null as well.
- // just update using the placeholder image in this case.
- if (displayPhotoUri == null) {
- Log.wtf("startObjectPhotoAsync", "Uri is missing");
- return;
- }
-
- // Added additional Cookie field in the callee to handle arguments
- // sent to the callback function.
-
- // setup arguments
- WorkerArgs args = new WorkerArgs();
- args.cookie = cookie;
- args.context = context;
- args.displayPhotoUri = displayPhotoUri;
- args.listener = listener;
-
- // setup message arguments
- Message msg = sThreadHandler.obtainMessage(token);
- msg.arg1 = EVENT_LOAD_IMAGE;
- msg.obj = args;
-
- Log.d("startObjectPhotoAsync", "Begin loading image: " + args.displayPhotoUri +
- ", displaying default image for now.");
-
- // notify the thread to begin working
- sThreadHandler.sendMessage(msg);
- }
-
-
-}
diff --git a/InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java b/InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java
deleted file mode 100644
index a9cc93bda..000000000
--- a/InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.content.Context;
-import android.support.annotation.Nullable;
-import com.android.dialer.compat.UserManagerCompat;
-
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.testing.NeededForTesting;
-
-/**
- * Factory class for {@link ContactsPreferences}.
- */
-public class ContactsPreferencesFactory {
-
- private static boolean sUseTestInstance;
- private static ContactsPreferences sTestInstance;
-
- /**
- * Creates a new {@link ContactsPreferences} object if possible.
- *
- * @param context the context to use when creating the ContactsPreferences.
- * @return a new ContactsPreferences object or {@code null} if the user is locked.
- */
- @Nullable
- public static ContactsPreferences newContactsPreferences(Context context) {
- if (sUseTestInstance) {
- return sTestInstance;
- }
- if (UserManagerCompat.isUserUnlocked(context)) {
- return new ContactsPreferences(context);
- }
- return null;
- }
-
- /**
- * Sets the instance to be returned by all calls to {@link #newContactsPreferences(Context)}.
- *
- * @param testInstance the instance to return.
- */
- @NeededForTesting
- static void setTestInstance(ContactsPreferences testInstance) {
- sUseTestInstance = true;
- sTestInstance = testInstance;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/DialpadFragment.java b/InCallUI/src/com/android/incallui/DialpadFragment.java
deleted file mode 100644
index ad288bdc6..000000000
--- a/InCallUI/src/com/android/incallui/DialpadFragment.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.text.Editable;
-import android.text.method.DialerKeyListener;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
-import com.android.dialer.R;
-import com.android.phone.common.dialpad.DialpadKeyButton;
-import com.android.phone.common.dialpad.DialpadView;
-
-import java.util.HashMap;
-
-/**
- * Fragment for call control buttons
- */
-public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPresenter.DialpadUi>
- implements DialpadPresenter.DialpadUi, View.OnTouchListener, View.OnKeyListener,
- View.OnHoverListener, View.OnClickListener {
-
- private static final int ACCESSIBILITY_DTMF_STOP_DELAY_MILLIS = 50;
-
- private final int[] mButtonIds = new int[] {R.id.zero, R.id.one, R.id.two, R.id.three,
- R.id.four, R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star,
- R.id.pound};
-
- /**
- * LinearLayout with getter and setter methods for the translationY property using floats,
- * for animation purposes.
- */
- public static class DialpadSlidingLinearLayout extends LinearLayout {
-
- public DialpadSlidingLinearLayout(Context context) {
- super(context);
- }
-
- public DialpadSlidingLinearLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public float getYFraction() {
- final int height = getHeight();
- if (height == 0) return 0;
- return getTranslationY() / height;
- }
-
- public void setYFraction(float yFraction) {
- setTranslationY(yFraction * getHeight());
- }
- }
-
- private EditText mDtmfDialerField;
-
- /** Hash Map to map a view id to a character*/
- private static final HashMap<Integer, Character> mDisplayMap =
- new HashMap<Integer, Character>();
-
- private static final Handler sHandler = new Handler(Looper.getMainLooper());
-
-
- /** Set up the static maps*/
- static {
- // Map the buttons to the display characters
- mDisplayMap.put(R.id.one, '1');
- mDisplayMap.put(R.id.two, '2');
- mDisplayMap.put(R.id.three, '3');
- mDisplayMap.put(R.id.four, '4');
- mDisplayMap.put(R.id.five, '5');
- mDisplayMap.put(R.id.six, '6');
- mDisplayMap.put(R.id.seven, '7');
- mDisplayMap.put(R.id.eight, '8');
- mDisplayMap.put(R.id.nine, '9');
- mDisplayMap.put(R.id.zero, '0');
- mDisplayMap.put(R.id.pound, '#');
- mDisplayMap.put(R.id.star, '*');
- }
-
- // KeyListener used with the "dialpad digits" EditText widget.
- private DTMFKeyListener mDialerKeyListener;
-
- private DialpadView mDialpadView;
-
- private int mCurrentTextColor;
-
- /**
- * Our own key listener, specialized for dealing with DTMF codes.
- * 1. Ignore the backspace since it is irrelevant.
- * 2. Allow ONLY valid DTMF characters to generate a tone and be
- * sent as a DTMF code.
- * 3. All other remaining characters are handled by the superclass.
- *
- * This code is purely here to handle events from the hardware keyboard
- * while the DTMF dialpad is up.
- */
- private class DTMFKeyListener extends DialerKeyListener {
-
- private DTMFKeyListener() {
- super();
- }
-
- /**
- * Overriden to return correct DTMF-dialable characters.
- */
- @Override
- protected char[] getAcceptedChars(){
- return DTMF_CHARACTERS;
- }
-
- /** special key listener ignores backspace. */
- @Override
- public boolean backspace(View view, Editable content, int keyCode,
- KeyEvent event) {
- return false;
- }
-
- /**
- * Return true if the keyCode is an accepted modifier key for the
- * dialer (ALT or SHIFT).
- */
- private boolean isAcceptableModifierKey(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_ALT_LEFT:
- case KeyEvent.KEYCODE_ALT_RIGHT:
- case KeyEvent.KEYCODE_SHIFT_LEFT:
- case KeyEvent.KEYCODE_SHIFT_RIGHT:
- return true;
- default:
- return false;
- }
- }
-
- /**
- * Overriden so that with each valid button press, we start sending
- * a dtmf code and play a local dtmf tone.
- */
- @Override
- public boolean onKeyDown(View view, Editable content,
- int keyCode, KeyEvent event) {
- // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view);
-
- // find the character
- char c = (char) lookup(event, content);
-
- // if not a long press, and parent onKeyDown accepts the input
- if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
-
- boolean keyOK = ok(getAcceptedChars(), c);
-
- // if the character is a valid dtmf code, start playing the tone and send the
- // code.
- if (keyOK) {
- Log.d(this, "DTMFKeyListener reading '" + c + "' from input.");
- getPresenter().processDtmf(c);
- } else {
- Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input.");
- }
- return true;
- }
- return false;
- }
-
- /**
- * Overriden so that with each valid button up, we stop sending
- * a dtmf code and the dtmf tone.
- */
- @Override
- public boolean onKeyUp(View view, Editable content,
- int keyCode, KeyEvent event) {
- // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view);
-
- super.onKeyUp(view, content, keyCode, event);
-
- // find the character
- char c = (char) lookup(event, content);
-
- boolean keyOK = ok(getAcceptedChars(), c);
-
- if (keyOK) {
- Log.d(this, "Stopping the tone for '" + c + "'");
- getPresenter().stopDtmf();
- return true;
- }
-
- return false;
- }
-
- /**
- * Handle individual keydown events when we DO NOT have an Editable handy.
- */
- public boolean onKeyDown(KeyEvent event) {
- char c = lookup(event);
- Log.d(this, "DTMFKeyListener.onKeyDown: event '" + c + "'");
-
- // if not a long press, and parent onKeyDown accepts the input
- if (event.getRepeatCount() == 0 && c != 0) {
- // if the character is a valid dtmf code, start playing the tone and send the
- // code.
- if (ok(getAcceptedChars(), c)) {
- Log.d(this, "DTMFKeyListener reading '" + c + "' from input.");
- getPresenter().processDtmf(c);
- return true;
- } else {
- Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input.");
- }
- }
- return false;
- }
-
- /**
- * Handle individual keyup events.
- *
- * @param event is the event we are trying to stop. If this is null,
- * then we just force-stop the last tone without checking if the event
- * is an acceptable dialer event.
- */
- public boolean onKeyUp(KeyEvent event) {
- if (event == null) {
- //the below piece of code sends stopDTMF event unnecessarily even when a null event
- //is received, hence commenting it.
- /*if (DBG) log("Stopping the last played tone.");
- stopTone();*/
- return true;
- }
-
- char c = lookup(event);
- Log.d(this, "DTMFKeyListener.onKeyUp: event '" + c + "'");
-
- // TODO: stopTone does not take in character input, we may want to
- // consider checking for this ourselves.
- if (ok(getAcceptedChars(), c)) {
- Log.d(this, "Stopping the tone for '" + c + "'");
- getPresenter().stopDtmf();
- return true;
- }
-
- return false;
- }
-
- /**
- * Find the Dialer Key mapped to this event.
- *
- * @return The char value of the input event, otherwise
- * 0 if no matching character was found.
- */
- private char lookup(KeyEvent event) {
- // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
- int meta = event.getMetaState();
- int number = event.getNumber();
-
- if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
- int match = event.getMatch(getAcceptedChars(), meta);
- number = (match != 0) ? match : number;
- }
-
- return (char) number;
- }
-
- /**
- * Check to see if the keyEvent is dialable.
- */
- boolean isKeyEventAcceptable (KeyEvent event) {
- return (ok(getAcceptedChars(), lookup(event)));
- }
-
- /**
- * Overrides the characters used in {@link DialerKeyListener#CHARACTERS}
- * These are the valid dtmf characters.
- */
- public final char[] DTMF_CHARACTERS = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'
- };
- }
-
- @Override
- public void onClick(View v) {
- final AccessibilityManager accessibilityManager = (AccessibilityManager)
- v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
- // When accessibility is on, simulate press and release to preserve the
- // semantic meaning of performClick(). Required for Braille support.
- if (accessibilityManager.isEnabled()) {
- final int id = v.getId();
- // Checking the press state prevents double activation.
- if (!v.isPressed() && mDisplayMap.containsKey(id)) {
- getPresenter().processDtmf(mDisplayMap.get(id));
- sHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- getPresenter().stopDtmf();
- }
- }, ACCESSIBILITY_DTMF_STOP_DELAY_MILLIS);
- }
- }
- if (v.getId() == R.id.dialpad_back) {
- getActivity().onBackPressed();
- }
- }
-
- @Override
- public boolean onHover(View v, MotionEvent event) {
- // When touch exploration is turned on, lifting a finger while inside
- // the button's hover target bounds should perform a click action.
- final AccessibilityManager accessibilityManager = (AccessibilityManager)
- v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-
- if (accessibilityManager.isEnabled()
- && accessibilityManager.isTouchExplorationEnabled()) {
- final int left = v.getPaddingLeft();
- final int right = (v.getWidth() - v.getPaddingRight());
- final int top = v.getPaddingTop();
- final int bottom = (v.getHeight() - v.getPaddingBottom());
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- // Lift-to-type temporarily disables double-tap activation.
- v.setClickable(false);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if ((x > left) && (x < right) && (y > top) && (y < bottom)) {
- v.performClick();
- }
- v.setClickable(true);
- break;
- }
- }
-
- return false;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- Log.d(this, "onKey: keyCode " + keyCode + ", view " + v);
-
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
- int viewId = v.getId();
- if (mDisplayMap.containsKey(viewId)) {
- switch (event.getAction()) {
- case KeyEvent.ACTION_DOWN:
- if (event.getRepeatCount() == 0) {
- getPresenter().processDtmf(mDisplayMap.get(viewId));
- }
- break;
- case KeyEvent.ACTION_UP:
- getPresenter().stopDtmf();
- break;
- }
- // do not return true [handled] here, since we want the
- // press / click animation to be handled by the framework.
- }
- }
- return false;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- Log.d(this, "onTouch");
- int viewId = v.getId();
-
- // if the button is recognized
- if (mDisplayMap.containsKey(viewId)) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- // Append the character mapped to this button, to the display.
- // start the tone
- getPresenter().processDtmf(mDisplayMap.get(viewId));
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- // stop the tone on ANY other event, except for MOVE.
- getPresenter().stopDtmf();
- break;
- }
- // do not return true [handled] here, since we want the
- // press / click animation to be handled by the framework.
- }
- return false;
- }
-
- // TODO(klp) Adds hardware keyboard listener
-
- @Override
- public DialpadPresenter createPresenter() {
- return new DialpadPresenter();
- }
-
- @Override
- public DialpadPresenter.DialpadUi getUi() {
- return this;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View parent = inflater.inflate(
- R.layout.incall_dialpad_fragment, container, false);
- mDialpadView = (DialpadView) parent.findViewById(R.id.dialpad_view);
- mDialpadView.setCanDigitsBeEdited(false);
- mDialpadView.setBackgroundResource(R.color.incall_dialpad_background);
- mDtmfDialerField = (EditText) parent.findViewById(R.id.digits);
- if (mDtmfDialerField != null) {
- mDialerKeyListener = new DTMFKeyListener();
- mDtmfDialerField.setKeyListener(mDialerKeyListener);
- // remove the long-press context menus that support
- // the edit (copy / paste / select) functions.
- mDtmfDialerField.setLongClickable(false);
- mDtmfDialerField.setElegantTextHeight(false);
- configureKeypadListeners();
- }
- View backButton = mDialpadView.findViewById(R.id.dialpad_back);
- backButton.setVisibility(View.VISIBLE);
- backButton.setOnClickListener(this);
-
- return parent;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateColors();
- }
-
- public void updateColors() {
- int textColor = InCallPresenter.getInstance().getThemeColors().mPrimaryColor;
-
- if (mCurrentTextColor == textColor) {
- return;
- }
-
- DialpadKeyButton dialpadKey;
- for (int i = 0; i < mButtonIds.length; i++) {
- dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]);
- ((TextView) dialpadKey.findViewById(R.id.dialpad_key_number)).setTextColor(textColor);
- }
-
- mCurrentTextColor = textColor;
- }
-
- @Override
- public void onDestroyView() {
- mDialerKeyListener = null;
- super.onDestroyView();
- }
-
- /**
- * Getter for Dialpad text.
- *
- * @return String containing current Dialpad EditText text.
- */
- public String getDtmfText() {
- return mDtmfDialerField.getText().toString();
- }
-
- /**
- * Sets the Dialpad text field with some text.
- *
- * @param text Text to set Dialpad EditText to.
- */
- public void setDtmfText(String text) {
- mDtmfDialerField.setText(PhoneNumberUtilsCompat.createTtsSpannable(text));
- }
-
- @Override
- public void setVisible(boolean on) {
- if (on) {
- getView().setVisibility(View.VISIBLE);
- } else {
- getView().setVisibility(View.INVISIBLE);
- }
- }
-
- /**
- * Starts the slide up animation for the Dialpad keys when the Dialpad is revealed.
- */
- public void animateShowDialpad() {
- final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
- dialpadView.animateShow();
- }
-
- @Override
- public void appendDigitsToField(char digit) {
- if (mDtmfDialerField != null) {
- // TODO: maybe *don't* manually append this digit if
- // mDialpadDigits is focused and this key came from the HW
- // keyboard, since in that case the EditText field will
- // get the key event directly and automatically appends
- // whetever the user types.
- // (Or, a cleaner fix would be to just make mDialpadDigits
- // *not* handle HW key presses. That seems to be more
- // complicated than just setting focusable="false" on it,
- // though.)
- mDtmfDialerField.getText().append(digit);
- }
- }
-
- /**
- * Called externally (from InCallScreen) to play a DTMF Tone.
- */
- /* package */ boolean onDialerKeyDown(KeyEvent event) {
- Log.d(this, "Notifying dtmf key down.");
- if (mDialerKeyListener != null) {
- return mDialerKeyListener.onKeyDown(event);
- } else {
- return false;
- }
- }
-
- /**
- * Called externally (from InCallScreen) to cancel the last DTMF Tone played.
- */
- public boolean onDialerKeyUp(KeyEvent event) {
- Log.d(this, "Notifying dtmf key up.");
- if (mDialerKeyListener != null) {
- return mDialerKeyListener.onKeyUp(event);
- } else {
- return false;
- }
- }
-
- private void configureKeypadListeners() {
- DialpadKeyButton dialpadKey;
- for (int i = 0; i < mButtonIds.length; i++) {
- dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]);
- dialpadKey.setOnTouchListener(this);
- dialpadKey.setOnKeyListener(this);
- dialpadKey.setOnHoverListener(this);
- dialpadKey.setOnClickListener(this);
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/DialpadPresenter.java b/InCallUI/src/com/android/incallui/DialpadPresenter.java
deleted file mode 100644
index 5e24bedef..000000000
--- a/InCallUI/src/com/android/incallui/DialpadPresenter.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2013 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.telephony.PhoneNumberUtils;
-
-/**
- * Logic for call buttons.
- */
-public class DialpadPresenter extends Presenter<DialpadPresenter.DialpadUi>
- implements InCallPresenter.InCallStateListener {
-
- private Call mCall;
-
- @Override
- public void onUiReady(DialpadUi ui) {
- super.onUiReady(ui);
- InCallPresenter.getInstance().addListener(this);
- mCall = CallList.getInstance().getOutgoingOrActive();
- }
-
- @Override
- public void onUiUnready(DialpadUi ui) {
- super.onUiUnready(ui);
- InCallPresenter.getInstance().removeListener(this);
- }
-
- @Override
- public void onStateChange(InCallPresenter.InCallState oldState,
- InCallPresenter.InCallState newState, CallList callList) {
- mCall = callList.getOutgoingOrActive();
- Log.d(this, "DialpadPresenter mCall = " + mCall);
- }
-
- /**
- * Processes the specified digit as a DTMF key, by playing the
- * appropriate DTMF tone, and appending the digit to the EditText
- * field that displays the DTMF digits sent so far.
- *
- */
- public final void processDtmf(char c) {
- Log.d(this, "Processing dtmf key " + c);
- // if it is a valid key, then update the display and send the dtmf tone.
- if (PhoneNumberUtils.is12Key(c) && mCall != null) {
- Log.d(this, "updating display and sending dtmf tone for '" + c + "'");
-
- // Append this key to the "digits" widget.
- getUi().appendDigitsToField(c);
- // Plays the tone through Telecom.
- TelecomAdapter.getInstance().playDtmfTone(mCall.getId(), c);
- } else {
- Log.d(this, "ignoring dtmf request for '" + c + "'");
- }
- }
-
- /**
- * Stops the local tone based on the phone type.
- */
- public void stopDtmf() {
- if (mCall != null) {
- Log.d(this, "stopping remote tone");
- TelecomAdapter.getInstance().stopDtmfTone(mCall.getId());
- }
- }
-
- public interface DialpadUi extends Ui {
- void setVisible(boolean on);
- void appendDigitsToField(char digit);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/DistanceHelper.java b/InCallUI/src/com/android/incallui/DistanceHelper.java
deleted file mode 100644
index a4db5fed3..000000000
--- a/InCallUI/src/com/android/incallui/DistanceHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 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.location.Address;
-
-/**
- * Superclass for a helper class to get the current location and distance to other locations.
- */
-public abstract class DistanceHelper {
- public static final float DISTANCE_NOT_FOUND = -1;
- public static final float MILES_PER_METER = (float) 0.000621371192;
- public static final float KILOMETERS_PER_METER = (float) 0.001;
-
- public interface Listener {
- public void onLocationReady();
- }
-
- public void cleanUp() {}
-
- public float calculateDistance(Address address) {
- return DISTANCE_NOT_FOUND;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ExternalCallList.java b/InCallUI/src/com/android/incallui/ExternalCallList.java
deleted file mode 100644
index 06e0bb975..000000000
--- a/InCallUI/src/com/android/incallui/ExternalCallList.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.android.incallui;
-
-import com.google.common.base.Preconditions;
-
-import com.android.contacts.common.compat.CallSdkCompat;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.telecom.Call;
-import android.util.ArraySet;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Tracks the external calls known to the InCall UI.
- *
- * External calls are those with {@link android.telecom.Call.Details#PROPERTY_IS_EXTERNAL_CALL}.
- */
-public class ExternalCallList {
-
- public interface ExternalCallListener {
- void onExternalCallAdded(Call call);
- void onExternalCallRemoved(Call call);
- void onExternalCallUpdated(Call call);
- }
-
- /**
- * Handles {@link android.telecom.Call.Callback} callbacks.
- */
- private final Call.Callback mTelecomCallCallback = new Call.Callback() {
- @Override
- public void onDetailsChanged(Call call, Call.Details details) {
- notifyExternalCallUpdated(call);
- }
- };
-
- private final Set<Call> mExternalCalls = new ArraySet<>();
- private final Set<ExternalCallListener> mExternalCallListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<ExternalCallListener, Boolean>(8, 0.9f, 1));
-
- /**
- * Begins tracking an external call and notifies listeners of the new call.
- */
- public void onCallAdded(Call telecomCall) {
- Preconditions.checkArgument(telecomCall.getDetails()
- .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL));
- mExternalCalls.add(telecomCall);
- telecomCall.registerCallback(mTelecomCallCallback, new Handler(Looper.getMainLooper()));
- notifyExternalCallAdded(telecomCall);
- }
-
- /**
- * Stops tracking an external call and notifies listeners of the removal of the call.
- */
- public void onCallRemoved(Call telecomCall) {
- Preconditions.checkArgument(mExternalCalls.contains(telecomCall));
- mExternalCalls.remove(telecomCall);
- telecomCall.unregisterCallback(mTelecomCallCallback);
- notifyExternalCallRemoved(telecomCall);
- }
-
- /**
- * Adds a new listener to external call events.
- */
- public void addExternalCallListener(ExternalCallListener listener) {
- mExternalCallListeners.add(Preconditions.checkNotNull(listener));
- }
-
- /**
- * Removes a listener to external call events.
- */
- public void removeExternalCallListener(ExternalCallListener listener) {
- Preconditions.checkArgument(mExternalCallListeners.contains(listener));
- mExternalCallListeners.remove(Preconditions.checkNotNull(listener));
- }
-
- /**
- * Notifies listeners of the addition of a new external call.
- */
- private void notifyExternalCallAdded(Call call) {
- for (ExternalCallListener listener : mExternalCallListeners) {
- listener.onExternalCallAdded(call);
- }
- }
-
- /**
- * Notifies listeners of the removal of an external call.
- */
- private void notifyExternalCallRemoved(Call call) {
- for (ExternalCallListener listener : mExternalCallListeners) {
- listener.onExternalCallRemoved(call);
- }
- }
-
- /**
- * Notifies listeners of changes to an external call.
- */
- private void notifyExternalCallUpdated(Call call) {
- for (ExternalCallListener listener : mExternalCallListeners) {
- listener.onExternalCallUpdated(call);
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ExternalCallNotifier.java b/InCallUI/src/com/android/incallui/ExternalCallNotifier.java
deleted file mode 100644
index 639a46da0..000000000
--- a/InCallUI/src/com/android/incallui/ExternalCallNotifier.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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 com.google.common.base.Preconditions;
-
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.compat.CallSdkCompat;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.util.BitmapUtil;
-import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.dialer.R;
-import com.android.incallui.util.TelecomCallUtil;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.telecom.Call;
-import android.telecom.PhoneAccount;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-
-import java.util.Map;
-
-/**
- * Handles the display of notifications for "external calls".
- *
- * External calls are a representation of a call which is in progress on the user's other device
- * (e.g. another phone, or a watch).
- */
-public class ExternalCallNotifier implements ExternalCallList.ExternalCallListener {
-
- /**
- * Tag used with the notification manager to uniquely identify external call notifications.
- */
- private static final String NOTIFICATION_TAG = "EXTERNAL_CALL";
-
- /**
- * Represents a call and associated cached notification data.
- */
- private static class NotificationInfo {
- private final Call mCall;
- private final int mNotificationId;
- @Nullable private String mContentTitle;
- @Nullable private Bitmap mLargeIcon;
- @Nullable private String mPersonReference;
-
- public NotificationInfo(Call call, int notificationId) {
- Preconditions.checkNotNull(call);
- mCall = call;
- mNotificationId = notificationId;
- }
-
- public Call getCall() {
- return mCall;
- }
-
- public int getNotificationId() {
- return mNotificationId;
- }
-
- public @Nullable String getContentTitle() {
- return mContentTitle;
- }
-
- public @Nullable Bitmap getLargeIcon() {
- return mLargeIcon;
- }
-
- public @Nullable String getPersonReference() {
- return mPersonReference;
- }
-
- public void setContentTitle(@Nullable String contentTitle) {
- mContentTitle = contentTitle;
- }
-
- public void setLargeIcon(@Nullable Bitmap largeIcon) {
- mLargeIcon = largeIcon;
- }
-
- public void setPersonReference(@Nullable String personReference) {
- mPersonReference = personReference;
- }
- }
-
- private final Context mContext;
- private final ContactInfoCache mContactInfoCache;
- private Map<Call, NotificationInfo> mNotifications = new ArrayMap<>();
- private int mNextUniqueNotificationId;
- private ContactsPreferences mContactsPreferences;
-
- /**
- * Initializes a new instance of the external call notifier.
- */
- public ExternalCallNotifier(Context context, ContactInfoCache contactInfoCache) {
- mContext = Preconditions.checkNotNull(context);
- mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
- mContactInfoCache = Preconditions.checkNotNull(contactInfoCache);
- }
-
- /**
- * Handles the addition of a new external call by showing a new notification.
- * Triggered by {@link CallList#onCallAdded(android.telecom.Call)}.
- */
- @Override
- public void onExternalCallAdded(android.telecom.Call call) {
- Log.i(this, "onExternalCallAdded " + call);
- Preconditions.checkArgument(!mNotifications.containsKey(call));
- NotificationInfo info = new NotificationInfo(call, mNextUniqueNotificationId++);
- mNotifications.put(call, info);
-
- showNotifcation(info);
- }
-
- /**
- * Handles the removal of an external call by hiding its associated notification.
- * Triggered by {@link CallList#onCallRemoved(android.telecom.Call)}.
- */
- @Override
- public void onExternalCallRemoved(android.telecom.Call call) {
- Log.i(this, "onExternalCallRemoved " + call);
-
- dismissNotification(call);
- }
-
- /**
- * Handles updates to an external call.
- */
- @Override
- public void onExternalCallUpdated(Call call) {
- Preconditions.checkArgument(mNotifications.containsKey(call));
- postNotification(mNotifications.get(call));
- }
-
- /**
- * Initiates a call pull given a notification ID.
- *
- * @param notificationId The notification ID associated with the external call which is to be
- * pulled.
- */
- public void pullExternalCall(int notificationId) {
- for (NotificationInfo info : mNotifications.values()) {
- if (info.getNotificationId() == notificationId) {
- CallSdkCompat.pullExternalCall(info.getCall());
- return;
- }
- }
- }
-
- /**
- * Shows a notification for a new external call. Performs a contact cache lookup to find any
- * associated photo and information for the call.
- */
- private void showNotifcation(final NotificationInfo info) {
- // We make a call to the contact info cache to query for supplemental data to what the
- // call provides. This includes the contact name and photo.
- // This callback will always get called immediately and synchronously with whatever data
- // it has available, and may make a subsequent call later (same thread) if it had to
- // call into the contacts provider for more data.
- com.android.incallui.Call incallCall = new com.android.incallui.Call(info.getCall(),
- new LatencyReport(), false /* registerCallback */);
-
- mContactInfoCache.findInfo(incallCall, false /* isIncoming */,
- new ContactInfoCache.ContactInfoCacheCallback() {
- @Override
- public void onContactInfoComplete(String callId,
- ContactInfoCache.ContactCacheEntry entry) {
-
- // Ensure notification still exists as the external call could have been
- // removed during async contact info lookup.
- if (mNotifications.containsKey(info.getCall())) {
- saveContactInfo(info, entry);
- }
- }
-
- @Override
- public void onImageLoadComplete(String callId,
- ContactInfoCache.ContactCacheEntry entry) {
-
- // Ensure notification still exists as the external call could have been
- // removed during async contact info lookup.
- if (mNotifications.containsKey(info.getCall())) {
- savePhoto(info, entry);
- }
- }
-
- @Override
- public void onContactInteractionsInfoComplete(String callId,
- ContactInfoCache.ContactCacheEntry entry) {
- }
- });
- }
-
- /**
- * Dismisses a notification for an external call.
- */
- private void dismissNotification(Call call) {
- Preconditions.checkArgument(mNotifications.containsKey(call));
-
- NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(NOTIFICATION_TAG, mNotifications.get(call).getNotificationId());
-
- mNotifications.remove(call);
- }
-
- /**
- * Attempts to build a large icon to use for the notification based on the contact info and
- * post the updated notification to the notification manager.
- */
- private void savePhoto(NotificationInfo info, ContactInfoCache.ContactCacheEntry entry) {
- Bitmap largeIcon = getLargeIconToDisplay(mContext, entry, info.getCall());
- if (largeIcon != null) {
- largeIcon = getRoundedIcon(mContext, largeIcon);
- }
- info.setLargeIcon(largeIcon);
- postNotification(info);
- }
-
- /**
- * Builds and stores the contact information the notification will display and posts the updated
- * notification to the notification manager.
- */
- private void saveContactInfo(NotificationInfo info, ContactInfoCache.ContactCacheEntry entry) {
- info.setContentTitle(getContentTitle(mContext, mContactsPreferences,
- entry, info.getCall()));
- info.setPersonReference(getPersonReference(entry, info.getCall()));
- postNotification(info);
- }
-
- /**
- * Rebuild an existing or show a new notification given {@link NotificationInfo}.
- */
- private void postNotification(NotificationInfo info) {
- Log.i(this, "postNotification : " + info.getContentTitle());
- Notification.Builder builder = new Notification.Builder(mContext);
- // Set notification as ongoing since calls are long-running versus a point-in-time notice.
- builder.setOngoing(true);
- // Make the notification prioritized over the other normal notifications.
- builder.setPriority(Notification.PRIORITY_HIGH);
- // Set the content ("Ongoing call on another device")
- builder.setContentText(mContext.getString(R.string.notification_external_call));
- builder.setSmallIcon(R.drawable.ic_call_white_24dp);
- builder.setContentTitle(info.getContentTitle());
- builder.setLargeIcon(info.getLargeIcon());
- builder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color));
- builder.addPerson(info.getPersonReference());
-
- // Where the external call supports being transferred to the local device, add an action
- // to the notification to initiate the call pull process.
- if ((info.getCall().getDetails().getCallCapabilities()
- & CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL)
- == CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL) {
-
- Intent intent = new Intent(
- NotificationBroadcastReceiver.ACTION_PULL_EXTERNAL_CALL, null, mContext,
- NotificationBroadcastReceiver.class);
- intent.putExtra(NotificationBroadcastReceiver.EXTRA_NOTIFICATION_ID,
- info.getNotificationId());
-
- builder.addAction(new Notification.Action.Builder(R.drawable.ic_call_white_24dp,
- mContext.getText(R.string.notification_transfer_call),
- PendingIntent.getBroadcast(mContext, 0, intent, 0)).build());
- }
-
- /**
- * This builder is used for the notification shown when the device is locked and the user
- * has set their notification settings to 'hide sensitive content'
- * {@see Notification.Builder#setPublicVersion}.
- */
- Notification.Builder publicBuilder = new Notification.Builder(mContext);
- publicBuilder.setSmallIcon(R.drawable.ic_call_white_24dp);
- publicBuilder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color));
-
- builder.setPublicVersion(publicBuilder.build());
- Notification notification = builder.build();
-
- NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(NOTIFICATION_TAG, info.getNotificationId(), notification);
- }
-
- /**
- * Finds a large icon to display in a notification for a call. For conference calls, a
- * conference call icon is used, otherwise if contact info is specified, the user's contact
- * photo or avatar is used.
- *
- * @param context The context.
- * @param contactInfo The contact cache info.
- * @param call The call.
- * @return The large icon to use for the notification.
- */
- private @Nullable Bitmap getLargeIconToDisplay(Context context,
- ContactInfoCache.ContactCacheEntry contactInfo, android.telecom.Call call) {
-
- Bitmap largeIcon = null;
- if (call.getDetails().hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE) &&
- !call.getDetails()
- .hasProperty(android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE)) {
-
- largeIcon = BitmapFactory.decodeResource(context.getResources(),
- R.drawable.img_conference);
- }
- if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
- largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
- }
- return largeIcon;
- }
-
- /**
- * Given a bitmap, returns a rounded version of the icon suitable for display in a notification.
- *
- * @param context The context.
- * @param bitmap The bitmap to round.
- * @return The rounded bitmap.
- */
- private @Nullable Bitmap getRoundedIcon(Context context, @Nullable Bitmap bitmap) {
- if (bitmap == null) {
- return null;
- }
- final int height = (int) context.getResources().getDimension(
- android.R.dimen.notification_large_icon_height);
- final int width = (int) context.getResources().getDimension(
- android.R.dimen.notification_large_icon_width);
- return BitmapUtil.getRoundedBitmap(bitmap, width, height);
- }
-
- /**
- * Builds a notification content title for a call. If the call is a conference call, it is
- * identified as such. Otherwise an attempt is made to show an associated contact name or
- * phone number.
- *
- * @param context The context.
- * @param contactsPreferences Contacts preferences, used to determine the preferred formatting
- * for contact names.
- * @param contactInfo The contact info which was looked up in the contact cache.
- * @param call The call to generate a title for.
- * @return The content title.
- */
- private @Nullable String getContentTitle(Context context,
- @Nullable ContactsPreferences contactsPreferences,
- ContactInfoCache.ContactCacheEntry contactInfo, android.telecom.Call call) {
-
- if (call.getDetails().hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE) &&
- !call.getDetails()
- .hasProperty(android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE)) {
-
- return context.getResources().getString(R.string.card_title_conf_call);
- }
-
- String preferredName = ContactDisplayUtils.getPreferredDisplayName(contactInfo.namePrimary,
- contactInfo.nameAlternative, contactsPreferences);
- if (TextUtils.isEmpty(preferredName)) {
- return TextUtils.isEmpty(contactInfo.number) ? null : BidiFormatter.getInstance()
- .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
- }
- return preferredName;
- }
-
- /**
- * Gets a "person reference" for a notification, used by the system to determine whether the
- * notification should be allowed past notification interruption filters.
- *
- * @param contactInfo The contact info from cache.
- * @param call The call.
- * @return the person reference.
- */
- private String getPersonReference(ContactInfoCache.ContactCacheEntry contactInfo,
- Call call) {
-
- String number = TelecomCallUtil.getNumber(call);
- // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
- // So, do not pass {@link Contacts#CONTENT_LOOKUP_URI} to NotificationManager to avoid
- // NotificationManager using it.
- if (contactInfo.lookupUri != null && contactInfo.userType != ContactsUtils.USER_TYPE_WORK) {
- return contactInfo.lookupUri.toString();
- } else if (!TextUtils.isEmpty(number)) {
- return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null).toString();
- }
- return "";
- }
-}
diff --git a/InCallUI/src/com/android/incallui/FragmentDisplayManager.java b/InCallUI/src/com/android/incallui/FragmentDisplayManager.java
deleted file mode 100644
index 045d999a0..000000000
--- a/InCallUI/src/com/android/incallui/FragmentDisplayManager.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2015 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.Fragment;
-
-interface FragmentDisplayManager {
- public void onFragmentAttached(Fragment fragment);
-}
diff --git a/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java b/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java
deleted file mode 100644
index 62a8e7829..000000000
--- a/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2013 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.os.Bundle;
-import android.telecom.VideoProfile;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.dialer.R;
-
-public class GlowPadAnswerFragment extends AnswerFragment {
-
- private GlowPadWrapper mGlowpad;
-
- public GlowPadAnswerFragment() {
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mGlowpad = (GlowPadWrapper) inflater.inflate(R.layout.answer_fragment,
- container, false);
-
- Log.d(this, "Creating view for answer fragment ", this);
- Log.d(this, "Created from activity", getActivity());
- mGlowpad.setAnswerFragment(this);
-
- return mGlowpad;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mGlowpad.requestFocus();
- }
-
- @Override
- public void onDestroyView() {
- Log.d(this, "onDestroyView");
- if (mGlowpad != null) {
- mGlowpad.stopPing();
- mGlowpad = null;
- }
- super.onDestroyView();
- }
-
- @Override
- public void onShowAnswerUi(boolean shown) {
- Log.d(this, "Show answer UI: " + shown);
- if (shown) {
- mGlowpad.startPing();
- } else {
- mGlowpad.stopPing();
- }
- }
-
- /**
- * Sets targets on the glowpad according to target set identified by the parameter.
- *
- * @param targetSet Integer identifying the set of targets to use.
- */
- public void showTargets(int targetSet) {
- showTargets(targetSet, VideoProfile.STATE_BIDIRECTIONAL);
- }
-
- /**
- * Sets targets on the glowpad according to target set identified by the parameter.
- *
- * @param targetSet Integer identifying the set of targets to use.
- */
- @Override
- public void showTargets(int targetSet, int videoState) {
- final int targetResourceId;
- final int targetDescriptionsResourceId;
- final int directionDescriptionsResourceId;
- final int handleDrawableResourceId;
- mGlowpad.setVideoState(videoState);
-
- switch (targetSet) {
- case TARGET_SET_FOR_AUDIO_WITH_SMS:
- targetResourceId = R.array.incoming_call_widget_audio_with_sms_targets;
- targetDescriptionsResourceId =
- R.array.incoming_call_widget_audio_with_sms_target_descriptions;
- directionDescriptionsResourceId =
- R.array.incoming_call_widget_audio_with_sms_direction_descriptions;
- handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
- break;
- case TARGET_SET_FOR_VIDEO_WITHOUT_SMS:
- targetResourceId = R.array.incoming_call_widget_video_without_sms_targets;
- targetDescriptionsResourceId =
- R.array.incoming_call_widget_video_without_sms_target_descriptions;
- directionDescriptionsResourceId =
- R.array.incoming_call_widget_video_without_sms_direction_descriptions;
- handleDrawableResourceId = R.drawable.ic_incall_video_handle;
- break;
- case TARGET_SET_FOR_VIDEO_WITH_SMS:
- targetResourceId = R.array.incoming_call_widget_video_with_sms_targets;
- targetDescriptionsResourceId =
- R.array.incoming_call_widget_video_with_sms_target_descriptions;
- directionDescriptionsResourceId =
- R.array.incoming_call_widget_video_with_sms_direction_descriptions;
- handleDrawableResourceId = R.drawable.ic_incall_video_handle;
- break;
- case TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST:
- targetResourceId =
- R.array.incoming_call_widget_video_request_targets;
- targetDescriptionsResourceId =
- R.array.incoming_call_widget_video_request_target_descriptions;
- directionDescriptionsResourceId = R.array
- .incoming_call_widget_video_request_target_direction_descriptions;
- handleDrawableResourceId = R.drawable.ic_incall_video_handle;
- break;
- case TARGET_SET_FOR_AUDIO_WITHOUT_SMS:
- default:
- targetResourceId = R.array.incoming_call_widget_audio_without_sms_targets;
- targetDescriptionsResourceId =
- R.array.incoming_call_widget_audio_without_sms_target_descriptions;
- directionDescriptionsResourceId =
- R.array.incoming_call_widget_audio_without_sms_direction_descriptions;
- handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
- break;
- }
-
- if (targetResourceId != mGlowpad.getTargetResourceId()) {
- mGlowpad.setTargetResources(targetResourceId);
- mGlowpad.setTargetDescriptionsResourceId(targetDescriptionsResourceId);
- mGlowpad.setDirectionDescriptionsResourceId(directionDescriptionsResourceId);
- mGlowpad.setHandleDrawable(handleDrawableResourceId);
- mGlowpad.reset(false);
- }
- }
-
- @Override
- protected void onMessageDialogCancel() {
- if (mGlowpad != null) {
- mGlowpad.startPing();
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
deleted file mode 100644
index 342f6b4fd..000000000
--- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.telecom.VideoProfile;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.dialer.R;
-import com.android.incallui.widget.multiwaveview.GlowPadView;
-
-/**
- *
- */
-public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTriggerListener {
-
- // Parameters for the GlowPadView "ping" animation; see triggerPing().
- private static final int PING_MESSAGE_WHAT = 101;
- private static final boolean ENABLE_PING_AUTO_REPEAT = true;
- private static final long PING_REPEAT_DELAY_MS = 1200;
-
- private final Handler mPingHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case PING_MESSAGE_WHAT:
- triggerPing();
- break;
- }
- }
- };
-
- private AnswerFragment mAnswerFragment;
- private boolean mPingEnabled = true;
- private boolean mTargetTriggered = false;
- private int mVideoState = VideoProfile.STATE_BIDIRECTIONAL;
-
- public GlowPadWrapper(Context context) {
- super(context);
- Log.d(this, "class created " + this + " ");
- }
-
- public GlowPadWrapper(Context context, AttributeSet attrs) {
- super(context, attrs);
- Log.d(this, "class created " + this);
- }
-
- @Override
- protected void onFinishInflate() {
- Log.d(this, "onFinishInflate()");
- super.onFinishInflate();
- setOnTriggerListener(this);
- }
-
- public void startPing() {
- Log.d(this, "startPing");
- mPingEnabled = true;
- triggerPing();
- }
-
- public void stopPing() {
- Log.d(this, "stopPing");
- mPingEnabled = false;
- mPingHandler.removeMessages(PING_MESSAGE_WHAT);
- }
-
- private void triggerPing() {
- Log.d(this, "triggerPing(): " + mPingEnabled + " " + this);
- if (mPingEnabled && !mPingHandler.hasMessages(PING_MESSAGE_WHAT)) {
- ping();
-
- if (ENABLE_PING_AUTO_REPEAT) {
- mPingHandler.sendEmptyMessageDelayed(PING_MESSAGE_WHAT, PING_REPEAT_DELAY_MS);
- }
- }
- }
-
- @Override
- public void onGrabbed(View v, int handle) {
- Log.d(this, "onGrabbed()");
- stopPing();
- }
-
- @Override
- public void onReleased(View v, int handle) {
- Log.d(this, "onReleased()");
- if (mTargetTriggered) {
- mTargetTriggered = false;
- } else {
- startPing();
- }
- }
-
- @Override
- public void onTrigger(View v, int target) {
- Log.d(this, "onTrigger() view=" + v + " target=" + target);
- final int resId = getResourceIdForTarget(target);
- if (resId == R.drawable.ic_lockscreen_answer) {
- mAnswerFragment.onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
- mTargetTriggered = true;
- } else if (resId == R.drawable.ic_lockscreen_decline) {
- mAnswerFragment.onDecline(getContext());
- mTargetTriggered = true;
- } else if (resId == R.drawable.ic_lockscreen_text) {
- mAnswerFragment.onText();
- mTargetTriggered = true;
- } else if (resId == R.drawable.ic_videocam || resId == R.drawable.ic_lockscreen_answer_video) {
- mAnswerFragment.onAnswer(mVideoState, getContext());
- mTargetTriggered = true;
- } else if (resId == R.drawable.ic_lockscreen_decline_video) {
- mAnswerFragment.onDeclineUpgradeRequest(getContext());
- mTargetTriggered = true;
- } else {
- // Code should never reach here.
- Log.e(this, "Trigger detected on unhandled resource. Skipping.");
- }
- }
-
- @Override
- public void onGrabbedStateChange(View v, int handle) {
-
- }
-
- @Override
- public void onFinishFinalAnimation() {
-
- }
-
- public void setAnswerFragment(AnswerFragment fragment) {
- mAnswerFragment = fragment;
- }
-
- /**
- * Sets the video state represented by the "video" icon on the glow pad.
- *
- * @param videoState The new video state.
- */
- public void setVideoState(int videoState) {
- mVideoState = videoState;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
deleted file mode 100644
index eaaedff2c..000000000
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ /dev/null
@@ -1,980 +0,0 @@
-/*
- * Copyright (C) 2006 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.ActionBar;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.hardware.SensorManager;
-import android.os.Bundle;
-import android.os.Trace;
-import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccountHandle;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-
-import com.android.contacts.common.activity.TransactionSafeActivity;
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.contacts.common.interactions.TouchPointManager;
-import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
-import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
-import com.android.dialer.R;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.logging.ScreenEvent;
-import com.android.incallui.Call.State;
-import com.android.incallui.util.AccessibilityUtil;
-import com.android.phone.common.animation.AnimUtils;
-import com.android.phone.common.animation.AnimationListenerAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Main activity that the user interacts with while in a live call.
- */
-public class InCallActivity extends TransactionSafeActivity implements FragmentDisplayManager {
-
- public static final String TAG = InCallActivity.class.getSimpleName();
-
- public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
- public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
- public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
- public static final String FOR_FULL_SCREEN_INTENT = "InCallActivity.for_full_screen_intent";
-
- private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
- private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment";
- private static final String TAG_CALLCARD_FRAGMENT = "tag_callcard_fragment";
- private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment";
- private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
-
- private static final int DIALPAD_REQUEST_NONE = 1;
- private static final int DIALPAD_REQUEST_SHOW = 2;
- private static final int DIALPAD_REQUEST_HIDE = 3;
-
- /**
- * This is used to relaunch the activity if resizing beyond which it needs to load different
- * layout file.
- */
- private static final int SCREEN_HEIGHT_RESIZE_THRESHOLD = 500;
-
- private CallButtonFragment mCallButtonFragment;
- private CallCardFragment mCallCardFragment;
- private AnswerFragment mAnswerFragment;
- private DialpadFragment mDialpadFragment;
- private ConferenceManagerFragment mConferenceManagerFragment;
- private FragmentManager mChildFragmentManager;
-
- private AlertDialog mDialog;
- private InCallOrientationEventListener mInCallOrientationEventListener;
-
- /**
- * Used to indicate whether the dialpad should be hidden or shown {@link #onResume}.
- * {@code #DIALPAD_REQUEST_SHOW} indicates that the dialpad should be shown.
- * {@code #DIALPAD_REQUEST_HIDE} indicates that the dialpad should be hidden.
- * {@code #DIALPAD_REQUEST_NONE} indicates no change should be made to dialpad visibility.
- */
- private int mShowDialpadRequest = DIALPAD_REQUEST_NONE;
-
- /**
- * Use to determine if the dialpad should be animated on show.
- */
- private boolean mAnimateDialpadOnShow;
-
- /**
- * Use to determine the DTMF Text which should be pre-populated in the dialpad.
- */
- private String mDtmfText;
-
- /**
- * Use to pass parameters for showing the PostCharDialog to {@link #onResume}
- */
- private boolean mShowPostCharWaitDialogOnResume;
- private String mShowPostCharWaitDialogCallId;
- private String mShowPostCharWaitDialogChars;
-
- private boolean mIsLandscape;
- private Animation mSlideIn;
- private Animation mSlideOut;
- private boolean mDismissKeyguard = false;
-
- AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
- @Override
- public void onAnimationEnd(Animation animation) {
- showFragment(TAG_DIALPAD_FRAGMENT, false, true);
- }
- };
-
- private OnTouchListener mDispatchTouchEventListener;
-
- private SelectPhoneAccountListener mSelectAcctListener = new SelectPhoneAccountListener() {
- @Override
- public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
- boolean setDefault) {
- InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle,
- setDefault);
- }
-
- @Override
- public void onDialogDismissed() {
- InCallPresenter.getInstance().cancelAccountSelection();
- }
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- Log.d(this, "onCreate()... this = " + this);
-
- super.onCreate(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;
-
- getWindow().addFlags(flags);
-
- // Setup action bar for the conference call manager.
- requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
- ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.hide();
- }
-
- // TODO(klp): Do we need to add this back when prox sensor is not available?
- // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
-
- setContentView(R.layout.incall_screen);
-
- internalResolveIntent(getIntent());
-
- mIsLandscape = getResources().getConfiguration().orientation ==
- Configuration.ORIENTATION_LANDSCAPE;
-
- final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
- View.LAYOUT_DIRECTION_RTL;
-
- if (mIsLandscape) {
- mSlideIn = AnimationUtils.loadAnimation(this,
- isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
- mSlideOut = AnimationUtils.loadAnimation(this,
- isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
- } else {
- mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
- mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
- }
-
- mSlideIn.setInterpolator(AnimUtils.EASE_IN);
- mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
-
- mSlideOut.setAnimationListener(mSlideOutListener);
-
- // If the dialpad fragment already exists, retrieve it. This is important when rotating as
- // we will not be able to hide or show the dialpad after the rotation otherwise.
- Fragment existingFragment =
- getFragmentManager().findFragmentByTag(DialpadFragment.class.getName());
- if (existingFragment != null) {
- mDialpadFragment = (DialpadFragment) existingFragment;
- }
-
- 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 CallCardFragment has been inflated and is ready
- // to receive it.
- if (icicle.containsKey(SHOW_DIALPAD_EXTRA)) {
- boolean showDialpad = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
- mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
- mAnimateDialpadOnShow = false;
- }
- mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
-
- SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
- getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
- if (dialogFragment != null) {
- dialogFragment.setListener(mSelectAcctListener);
- }
- }
- mInCallOrientationEventListener = new InCallOrientationEventListener(this);
-
- Log.d(this, "onCreate(): exit");
- }
-
- @Override
- protected void onSaveInstanceState(Bundle out) {
- // TODO: The dialpad fragment should handle this as part of its own state
- out.putBoolean(SHOW_DIALPAD_EXTRA,
- mCallButtonFragment != null && mCallButtonFragment.isDialpadVisible());
- if (mDialpadFragment != null) {
- out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText());
- }
- super.onSaveInstanceState(out);
- }
-
- @Override
- protected void onStart() {
- Log.d(this, "onStart()...");
- super.onStart();
-
- // setting activity should be last thing in setup process
- InCallPresenter.getInstance().setActivity(this);
- enableInCallOrientationEventListener(getRequestedOrientation() ==
- InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
-
- InCallPresenter.getInstance().onActivityStarted();
- }
-
- @Override
- protected void onResume() {
- Log.i(this, "onResume()...");
- super.onResume();
-
- InCallPresenter.getInstance().setThemeColors();
- InCallPresenter.getInstance().onUiShowing(true);
-
- // Clear fullscreen state onResume; the stored value may not match reality.
- InCallPresenter.getInstance().clearFullscreen();
-
- // If there is a pending request to show or hide the dialpad, handle that now.
- if (mShowDialpadRequest != DIALPAD_REQUEST_NONE) {
- if (mShowDialpadRequest == 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 */);
-
- mCallButtonFragment.displayDialpad(true /* show */,
- mAnimateDialpadOnShow /* animate */);
- mAnimateDialpadOnShow = false;
-
- if (mDialpadFragment != null) {
- mDialpadFragment.setDtmfText(mDtmfText);
- mDtmfText = null;
- }
- } else {
- Log.v(this, "onResume : force hide dialpad");
- if (mDialpadFragment != null) {
- mCallButtonFragment.displayDialpad(false /* show */, false /* animate */);
- }
- }
- mShowDialpadRequest = DIALPAD_REQUEST_NONE;
- }
-
- if (mShowPostCharWaitDialogOnResume) {
- showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
- }
-
- CallList.getInstance().onInCallUiShown(
- getIntent().getBooleanExtra(FOR_FULL_SCREEN_INTENT, false));
- }
-
- // onPause is guaranteed to be called when the InCallActivity goes
- // in the background.
- @Override
- protected void onPause() {
- Log.d(this, "onPause()...");
- if (mDialpadFragment != null) {
- mDialpadFragment.onDialerKeyUp(null);
- }
-
- InCallPresenter.getInstance().onUiShowing(false);
- if (isFinishing()) {
- InCallPresenter.getInstance().unsetActivity(this);
- }
- super.onPause();
- }
-
- @Override
- protected void onStop() {
- Log.d(this, "onStop()...");
- enableInCallOrientationEventListener(false);
- InCallPresenter.getInstance().updateIsChangingConfigurations();
- InCallPresenter.getInstance().onActivityStopped();
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- Log.d(this, "onDestroy()... this = " + this);
- InCallPresenter.getInstance().unsetActivity(this);
- InCallPresenter.getInstance().updateIsChangingConfigurations();
- super.onDestroy();
- }
-
- /**
- * When fragments have a parent fragment, onAttachFragment is not called on the parent
- * activity. To fix this, register our own callback instead that is always called for
- * all fragments.
- *
- * @see {@link BaseFragment#onAttach(Activity)}
- */
- @Override
- public void onFragmentAttached(Fragment fragment) {
- if (fragment instanceof DialpadFragment) {
- mDialpadFragment = (DialpadFragment) fragment;
- } else if (fragment instanceof AnswerFragment) {
- mAnswerFragment = (AnswerFragment) fragment;
- } else if (fragment instanceof CallCardFragment) {
- mCallCardFragment = (CallCardFragment) fragment;
- mChildFragmentManager = mCallCardFragment.getChildFragmentManager();
- } else if (fragment instanceof ConferenceManagerFragment) {
- mConferenceManagerFragment = (ConferenceManagerFragment) fragment;
- } else if (fragment instanceof CallButtonFragment) {
- mCallButtonFragment = (CallButtonFragment) fragment;
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- Configuration oldConfig = getResources().getConfiguration();
- Log.v(this, String.format(
- "incallui config changed, screen size: w%ddp x h%ddp old:w%ddp x h%ddp",
- newConfig.screenWidthDp, newConfig.screenHeightDp,
- oldConfig.screenWidthDp, oldConfig.screenHeightDp));
- // Recreate this activity if height is changing beyond the threshold to load different
- // layout file.
- if (oldConfig.screenHeightDp < SCREEN_HEIGHT_RESIZE_THRESHOLD &&
- newConfig.screenHeightDp > SCREEN_HEIGHT_RESIZE_THRESHOLD ||
- oldConfig.screenHeightDp > SCREEN_HEIGHT_RESIZE_THRESHOLD &&
- newConfig.screenHeightDp < SCREEN_HEIGHT_RESIZE_THRESHOLD) {
- Log.i(this, String.format(
- "Recreate activity due to resize beyond threshold: %d dp",
- SCREEN_HEIGHT_RESIZE_THRESHOLD));
- recreate();
- }
- }
-
- /**
- * Returns true when the Activity is currently visible.
- */
- /* package */ boolean isVisible() {
- return isSafeToCommitTransactions();
- }
-
- private boolean hasPendingDialogs() {
- return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs());
- }
-
- @Override
- public void finish() {
- Log.i(this, "finish(). Dialog showing: " + (mDialog != null));
-
- // skip finish if we are still showing a dialog.
- if (!hasPendingDialogs()) {
- super.finish();
- }
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- Log.d(this, "onNewIntent: intent = " + intent);
-
- // 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!)
- 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);
- }
-
- @Override
- public void onBackPressed() {
- Log.i(this, "onBackPressed");
-
- // BACK is also used to exit out of any "special modes" of the
- // in-call UI:
- if (!isVisible()) {
- return;
- }
-
- if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible())
- && (mCallCardFragment == null || !mCallCardFragment.isVisible())) {
- return;
- }
-
- if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
- mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
- return;
- } else if (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible()) {
- showConferenceFragment(false);
- return;
- }
-
- // Always disable the Back key while an incoming call is ringing
- final Call call = CallList.getInstance().getIncomingCall();
- if (call != null) {
- Log.i(this, "Consume Back press for an incoming call");
- return;
- }
-
- // Nothing special to do. Fall back to the default behavior.
- super.onBackPressed();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- final int itemId = item.getItemId();
- if (itemId == android.R.id.home) {
- onBackPressed();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- // push input to the dialer.
- if (mDialpadFragment != null && (mDialpadFragment.isVisible()) &&
- (mDialpadFragment.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 super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (mDispatchTouchEventListener != null) {
- boolean handled = mDispatchTouchEventListener.onTouch(null, ev);
- if (handled) {
- return true;
- }
- }
- return super.dispatchTouchEvent(ev);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL:
- boolean handled = InCallPresenter.getInstance().handleCallKey();
- if (!handled) {
- Log.w(this, "InCallActivity 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:
- // toggle mute
- TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute());
- return true;
-
- // Various testing/debugging features, enabled ONLY when VERBOSE == true.
- case KeyEvent.KEYCODE_SLASH:
- if (Log.VERBOSE) {
- Log.v(this, "----------- InCallActivity View dump --------------");
- // Dump starting from the top-level view of the entire activity:
- Window w = this.getWindow();
- View decorView = w.getDecorView();
- Log.d(this, "View dump:" + decorView);
- return true;
- }
- break;
- case KeyEvent.KEYCODE_EQUALS:
- // TODO: Dump phone state?
- break;
- }
-
- if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
- Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + 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.
- if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
- return mDialpadFragment.onDialerKeyDown(event);
- }
-
- return false;
- }
-
- public CallButtonFragment getCallButtonFragment() {
- return mCallButtonFragment;
- }
-
- public CallCardFragment getCallCardFragment() {
- return mCallCardFragment;
- }
-
- public AnswerFragment getAnswerFragment() {
- return mAnswerFragment;
- }
-
- private void internalResolveIntent(Intent intent) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_MAIN)) {
- // This action is the normal way to bring up the in-call UI.
- //
- // But we do check here for one extra that can come along with the
- // ACTION_MAIN intent:
-
- if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
- // 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.
-
- final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
- Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
-
- relaunchedFromDialer(showDialpad);
- }
-
- boolean newOutgoingCall = false;
- if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) {
- intent.removeExtra(NEW_OUTGOING_CALL_EXTRA);
- Call call = CallList.getInstance().getOutgoingCall();
- if (call == null) {
- call = CallList.getInstance().getPendingOutgoingCall();
- }
-
- Bundle extras = null;
- if (call != null) {
- extras = call.getTelecomCall().getDetails().getIntentExtras();
- }
- if (extras == null) {
- // Initialize the extras bundle to avoid NPE
- extras = new Bundle();
- }
-
- Point touchPoint = null;
- if (TouchPointManager.getInstance().hasValidPoint()) {
- // Use the most immediate touch point in the InCallUi if available
- touchPoint = TouchPointManager.getInstance().getPoint();
- } else {
- // Otherwise retrieve the touch point from the call intent
- if (call != null) {
- touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT);
- }
- }
-
- // Start animation for new outgoing call
- CircularRevealFragment.startCircularReveal(getFragmentManager(), touchPoint,
- InCallPresenter.getInstance());
-
- // 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(call)) {
- TelecomAdapter.getInstance().disconnectCall(call.getId());
- }
-
- dismissKeyguard(true);
- newOutgoingCall = true;
- }
-
- Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall();
- if (pendingAccountSelectionCall != null) {
- showCallCardFragment(false);
- Bundle extras =
- pendingAccountSelectionCall.getTelecomCall().getDetails().getIntentExtras();
-
- final 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,
- mSelectAcctListener);
- dialogFragment.show(getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT);
- } else if (!newOutgoingCall) {
- showCallCardFragment(true);
- }
- return;
- }
- }
-
- /**
- * 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) {
- mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
- mAnimateDialpadOnShow = true;
-
- if (mShowDialpadRequest == 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.
- final Call call = CallList.getInstance().getActiveOrBackgroundCall();
- if (call != null && call.getState() == State.ONHOLD) {
- TelecomAdapter.getInstance().unholdCall(call.getId());
- }
- }
- }
-
- public void dismissKeyguard(boolean dismiss) {
- if (mDismissKeyguard == dismiss) {
- return;
- }
- mDismissKeyguard = dismiss;
- if (dismiss) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- } else {
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- }
- }
-
- private void showFragment(String tag, boolean show, boolean executeImmediately) {
- Trace.beginSection("showFragment - " + tag);
- final FragmentManager fm = getFragmentManagerForTag(tag);
-
- if (fm == null) {
- Log.w(TAG, "Fragment manager is null for : " + tag);
- return;
- }
-
- Fragment fragment = fm.findFragmentByTag(tag);
- if (!show && fragment == null) {
- // Nothing to show, so bail early.
- return;
- }
-
- final FragmentTransaction transaction = fm.beginTransaction();
- if (show) {
- if (fragment == null) {
- fragment = createNewFragmentForTag(tag);
- transaction.add(getContainerIdForFragment(tag), fragment, tag);
- } else {
- transaction.show(fragment);
- }
- Logger.logScreenView(getScreenTypeForTag(tag), this);
- } else {
- transaction.hide(fragment);
- }
-
- transaction.commitAllowingStateLoss();
- if (executeImmediately) {
- fm.executePendingTransactions();
- }
- Trace.endSection();
- }
-
- private Fragment createNewFragmentForTag(String tag) {
- if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
- mDialpadFragment = new DialpadFragment();
- return mDialpadFragment;
- } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
- if (AccessibilityUtil.isTalkBackEnabled(this)) {
- mAnswerFragment = new AccessibleAnswerFragment();
- } else {
- mAnswerFragment = new GlowPadAnswerFragment();
- }
- return mAnswerFragment;
- } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
- mConferenceManagerFragment = new ConferenceManagerFragment();
- return mConferenceManagerFragment;
- } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
- mCallCardFragment = new CallCardFragment();
- return mCallCardFragment;
- }
- throw new IllegalStateException("Unexpected fragment: " + tag);
- }
-
- private FragmentManager getFragmentManagerForTag(String tag) {
- if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
- return mChildFragmentManager;
- } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
- return mChildFragmentManager;
- } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
- return getFragmentManager();
- } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
- return getFragmentManager();
- }
- throw new IllegalStateException("Unexpected fragment: " + tag);
- }
-
- private int getScreenTypeForTag(String tag) {
- switch (tag) {
- case TAG_DIALPAD_FRAGMENT:
- return ScreenEvent.INCALL_DIALPAD;
- case TAG_CALLCARD_FRAGMENT:
- return ScreenEvent.INCALL;
- case TAG_CONFERENCE_FRAGMENT:
- return ScreenEvent.CONFERENCE_MANAGEMENT;
- case TAG_ANSWER_FRAGMENT:
- return ScreenEvent.INCOMING_CALL;
- default:
- return ScreenEvent.UNKNOWN;
- }
- }
-
- private int getContainerIdForFragment(String tag) {
- if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
- return R.id.answer_and_dialpad_container;
- } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
- return R.id.answer_and_dialpad_container;
- } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
- return R.id.main;
- } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
- return R.id.main;
- }
- throw new IllegalStateException("Unexpected fragment: " + tag);
- }
-
- /**
- * @return {@code true} while the visibility of the dialpad has actually changed.
- */
- 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.
- if ((show && isDialpadVisible()) || (!show && !isDialpadVisible())) {
- 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) {
- showFragment(TAG_DIALPAD_FRAGMENT, show, true);
- } else {
- if (show) {
- showFragment(TAG_DIALPAD_FRAGMENT, true, true);
- mDialpadFragment.animateShowDialpad();
- }
- mDialpadFragment.getView().startAnimation(show ? mSlideIn : mSlideOut);
- }
- // Note: onDialpadVisibilityChange is called here to ensure that the dialpad FAB
- // repositions itself.
- mCallCardFragment.onDialpadVisibilityChange(show);
-
- final ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
- if (sensor != null) {
- sensor.onDialpadVisible(show);
- }
- return true;
- }
-
- public boolean isDialpadVisible() {
- return mDialpadFragment != null && mDialpadFragment.isVisible();
- }
-
- public void showCallCardFragment(boolean show) {
- showFragment(TAG_CALLCARD_FRAGMENT, show, true);
- }
-
- /**
- * Hides or shows the conference manager fragment.
- *
- * @param show {@code true} if the conference manager should be shown, {@code false} if it
- * should be hidden.
- */
- public void showConferenceFragment(boolean show) {
- showFragment(TAG_CONFERENCE_FRAGMENT, show, true);
- mConferenceManagerFragment.onVisibilityChanged(show);
-
- // Need to hide the call card fragment to ensure that accessibility service does not try to
- // give focus to the call card when the conference manager is visible.
- mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE);
- }
-
- public void showAnswerFragment(boolean show) {
- // CallCardFragment is the parent fragment of AnswerFragment.
- // Must create the CallCardFragment first before creating
- // AnswerFragment if CallCardFragment is null.
- if (show && getCallCardFragment() == null) {
- showCallCardFragment(true);
- }
- showFragment(TAG_ANSWER_FRAGMENT, show, true);
- }
-
- public void showPostCharWaitDialog(String callId, String chars) {
- if (isVisible()) {
- final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
- fragment.show(getFragmentManager(), "postCharWait");
-
- mShowPostCharWaitDialogOnResume = false;
- mShowPostCharWaitDialogCallId = null;
- mShowPostCharWaitDialogChars = null;
- } else {
- mShowPostCharWaitDialogOnResume = true;
- mShowPostCharWaitDialogCallId = callId;
- mShowPostCharWaitDialogChars = chars;
- }
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (mCallCardFragment != null) {
- mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
- }
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
- Log.d(this, "maybeShowErrorDialogOnDisconnect");
-
- if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription())
- && (disconnectCause.getCode() == DisconnectCause.ERROR ||
- disconnectCause.getCode() == DisconnectCause.RESTRICTED)) {
- showErrorDialog(disconnectCause.getDescription());
- }
- }
-
- public void dismissPendingDialogs() {
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- if (mAnswerFragment != null) {
- mAnswerFragment.dismissPendingDialogs();
- }
-
- SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
- getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
- if (dialogFragment != null) {
- dialogFragment.dismiss();
- }
- }
-
- /**
- * Utility function to bring up a generic "error" dialog.
- */
- private void showErrorDialog(CharSequence msg) {
- Log.i(this, "Show Dialog: " + msg);
-
- dismissPendingDialogs();
-
- mDialog = new AlertDialog.Builder(this)
- .setMessage(msg)
- .setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onDialogDismissed();
- }
- })
- .setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- onDialogDismissed();
- }
- })
- .create();
-
- mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- mDialog.show();
- }
-
- private void onDialogDismissed() {
- mDialog = null;
- CallList.getInstance().onErrorDialogDismissed();
- InCallPresenter.getInstance().onDismissDialog();
- }
-
- public void setExcludeFromRecents(boolean exclude) {
- ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
- List<ActivityManager.AppTask> tasks = am.getAppTasks();
- int taskId = getTaskId();
- for (int i = 0; i < tasks.size(); i++) {
- ActivityManager.AppTask task = tasks.get(i);
- if (task.getTaskInfo().id == taskId) {
- try {
- task.setExcludeFromRecents(exclude);
- } catch (RuntimeException e) {
- Log.e(TAG, "RuntimeException when excluding task from recents.", e);
- }
- }
- }
- }
-
-
- public OnTouchListener getDispatchTouchEventListener() {
- return mDispatchTouchEventListener;
- }
-
- public void setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener) {
- this.mDispatchTouchEventListener = mDispatchTouchEventListener;
- }
-
- /**
- * Enables the OrientationEventListener if enable flag is true. Disables it if enable is
- * false
- * @param enable true or false.
- */
- public void enableInCallOrientationEventListener(boolean enable) {
- if (enable) {
- mInCallOrientationEventListener.enable(enable);
- } else {
- mInCallOrientationEventListener.disable();
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallAnimationUtils.java b/InCallUI/src/com/android/incallui/InCallAnimationUtils.java
deleted file mode 100644
index 44bb369e6..000000000
--- a/InCallUI/src/com/android/incallui/InCallAnimationUtils.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2012 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.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.view.ViewPropertyAnimator;
-import android.widget.ImageView;
-
-/**
- * Utilities for Animation.
- */
-public class InCallAnimationUtils {
- private static final String LOG_TAG = InCallAnimationUtils.class.getSimpleName();
- /**
- * Turn on when you're interested in fading animation. Intentionally untied from other debug
- * settings.
- */
- private static final boolean FADE_DBG = false;
-
- /**
- * Duration for animations in msec, which can be used with
- * {@link ViewPropertyAnimator#setDuration(long)} for example.
- */
- public static final int ANIMATION_DURATION = 250;
-
- private InCallAnimationUtils() {
- }
-
- /**
- * Drawable achieving cross-fade, just like TransitionDrawable. We can have
- * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}).
- */
- private static class CrossFadeDrawable extends LayerDrawable {
- private final ObjectAnimator mAnimator;
-
- public CrossFadeDrawable(Drawable[] layers) {
- super(layers);
- mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0);
- }
-
- private int mCrossFadeAlpha;
-
- /**
- * This will be used from ObjectAnimator.
- * Note: this method is protected by proguard.flags so that it won't be removed
- * automatically.
- */
- @SuppressWarnings("unused")
- public void setCrossFadeAlpha(int alpha) {
- mCrossFadeAlpha = alpha;
- invalidateSelf();
- }
-
- public ObjectAnimator getAnimator() {
- return mAnimator;
- }
-
- @Override
- public void draw(Canvas canvas) {
- Drawable first = getDrawable(0);
- Drawable second = getDrawable(1);
-
- if (mCrossFadeAlpha > 0) {
- first.setAlpha(mCrossFadeAlpha);
- first.draw(canvas);
- first.setAlpha(255);
- }
-
- if (mCrossFadeAlpha < 0xff) {
- second.setAlpha(0xff - mCrossFadeAlpha);
- second.draw(canvas);
- second.setAlpha(0xff);
- }
- }
- }
-
- private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) {
- Drawable[] layers = new Drawable[2];
- layers[0] = first;
- layers[1] = second;
- return new CrossFadeDrawable(layers);
- }
-
- /**
- * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to"
- * are the same.
- */
- public static void startCrossFade(
- final ImageView imageView, final Drawable from, final Drawable to) {
- // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables
- // pointing to the same Bitmap.
- final boolean drawableIsEqual = (from != null && to != null && from.equals(to));
- final boolean hasFromImage = ((from instanceof BitmapDrawable) &&
- ((BitmapDrawable) from).getBitmap() != null);
- final boolean hasToImage = ((to instanceof BitmapDrawable) &&
- ((BitmapDrawable) to).getBitmap() != null);
- final boolean areSameImage = drawableIsEqual || (hasFromImage && hasToImage &&
- ((BitmapDrawable) from).getBitmap().equals(((BitmapDrawable) to).getBitmap()));
-
- if (!areSameImage) {
- if (FADE_DBG) {
- log("Start cross-fade animation for " + imageView
- + "(" + Integer.toHexString(from.hashCode()) + " -> "
- + Integer.toHexString(to.hashCode()) + ")");
- }
-
- CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to);
- ObjectAnimator animator = crossFadeDrawable.getAnimator();
- imageView.setImageDrawable(crossFadeDrawable);
- animator.setDuration(ANIMATION_DURATION);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- if (FADE_DBG) {
- log("cross-fade animation start ("
- + Integer.toHexString(from.hashCode()) + " -> "
- + Integer.toHexString(to.hashCode()) + ")");
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (FADE_DBG) {
- log("cross-fade animation ended ("
- + Integer.toHexString(from.hashCode()) + " -> "
- + Integer.toHexString(to.hashCode()) + ")");
- }
- animation.removeAllListeners();
- // Workaround for issue 6300562; this will force the drawable to the
- // resultant one regardless of animation glitch.
- imageView.setImageDrawable(to);
- }
- });
- animator.start();
-
- /* We could use TransitionDrawable here, but it may cause some weird animation in
- * some corner cases. See issue 6300562
- * TODO: decide which to be used in the long run. TransitionDrawable is old but system
- * one. Ours uses new animation framework and thus have callback (great for testing),
- * while no framework support for the exact class.
-
- Drawable[] layers = new Drawable[2];
- layers[0] = from;
- layers[1] = to;
- TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
- imageView.setImageDrawable(transitionDrawable);
- transitionDrawable.startTransition(ANIMATION_DURATION); */
- imageView.setTag(to);
- } else if (!hasFromImage && hasToImage) {
- imageView.setImageDrawable(to);
- imageView.setTag(to);
- } else {
- if (FADE_DBG) {
- log("*Not* start cross-fade. " + imageView);
- }
- }
- }
-
- // Debugging / testing code
-
- private static void log(String msg) {
- Log.d(LOG_TAG, msg);
- }
-} \ No newline at end of file
diff --git a/InCallUI/src/com/android/incallui/InCallCameraManager.java b/InCallUI/src/com/android/incallui/InCallCameraManager.java
deleted file mode 100644
index 53000f1dd..000000000
--- a/InCallUI/src/com/android/incallui/InCallCameraManager.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2014 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.Context;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.util.Size;
-
-import java.lang.String;
-import java.util.Collections;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.Set;
-
-/**
- * Used to track which camera is used for outgoing video.
- */
-public class InCallCameraManager {
-
- public interface Listener {
- void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera);
- }
-
- private final Set<Listener> mCameraSelectionListeners = Collections.
- newSetFromMap(new ConcurrentHashMap<Listener, Boolean>(8,0.9f,1));
-
- /**
- * The camera ID for the front facing camera.
- */
- private String mFrontFacingCameraId;
-
- /**
- * The camera ID for the rear facing camera.
- */
- private String mRearFacingCameraId;
-
- /**
- * The currently active camera.
- */
- private boolean mUseFrontFacingCamera;
-
- /**
- * Indicates whether the list of cameras has been initialized yet. Initialization is delayed
- * until a video call is present.
- */
- private boolean mIsInitialized = false;
-
- /**
- * The context.
- */
- private Context mContext;
-
- /**
- * Initializes the InCall CameraManager.
- *
- * @param context The current context.
- */
- public InCallCameraManager(Context context) {
- mUseFrontFacingCamera = true;
- mContext = context;
- }
-
- /**
- * Sets whether the front facing camera should be used or not.
- *
- * @param useFrontFacingCamera {@code True} if the front facing camera is to be used.
- */
- public void setUseFrontFacingCamera(boolean useFrontFacingCamera) {
- mUseFrontFacingCamera = useFrontFacingCamera;
- for (Listener listener : mCameraSelectionListeners) {
- listener.onActiveCameraSelectionChanged(mUseFrontFacingCamera);
- }
- }
-
- /**
- * Determines whether the front facing camera is currently in use.
- *
- * @return {@code True} if the front facing camera is in use.
- */
- public boolean isUsingFrontFacingCamera() {
- return mUseFrontFacingCamera;
- }
-
- /**
- * Determines the active camera ID.
- *
- * @return The active camera ID.
- */
- public String getActiveCameraId() {
- maybeInitializeCameraList(mContext);
-
- if (mUseFrontFacingCamera) {
- return mFrontFacingCameraId;
- } else {
- return mRearFacingCameraId;
- }
- }
-
- /**
- * Get the list of cameras available for use.
- *
- * @param context The context.
- */
- private void maybeInitializeCameraList(Context context) {
- if (mIsInitialized || context == null) {
- return;
- }
-
- Log.v(this, "initializeCameraList");
-
- CameraManager cameraManager = null;
- try {
- cameraManager = (CameraManager) context.getSystemService(
- Context.CAMERA_SERVICE);
- } catch (Exception e) {
- Log.e(this, "Could not get camera service.");
- return;
- }
-
- if (cameraManager == null) {
- return;
- }
-
- String[] cameraIds = {};
- try {
- cameraIds = cameraManager.getCameraIdList();
- } catch (CameraAccessException e) {
- Log.d(this, "Could not access camera: "+e);
- // Camera disabled by device policy.
- return;
- }
-
- for (int i = 0; i < cameraIds.length; i++) {
- CameraCharacteristics c = null;
- try {
- c = cameraManager.getCameraCharacteristics(cameraIds[i]);
- } catch (IllegalArgumentException e) {
- // Device Id is unknown.
- } catch (CameraAccessException e) {
- // Camera disabled by device policy.
- }
- if (c != null) {
- int facingCharacteristic = c.get(CameraCharacteristics.LENS_FACING);
- if (facingCharacteristic == CameraCharacteristics.LENS_FACING_FRONT) {
- mFrontFacingCameraId = cameraIds[i];
- } else if (facingCharacteristic == CameraCharacteristics.LENS_FACING_BACK) {
- mRearFacingCameraId = cameraIds[i];
- }
- }
- }
-
- mIsInitialized = true;
- Log.v(this, "initializeCameraList : done");
- }
-
- public void addCameraSelectionListener(Listener listener) {
- if (listener != null) {
- mCameraSelectionListeners.add(listener);
- }
- }
-
- public void removeCameraSelectionListener(Listener listener) {
- if (listener != null) {
- mCameraSelectionListeners.remove(listener);
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallContactInteractions.java b/InCallUI/src/com/android/incallui/InCallContactInteractions.java
deleted file mode 100644
index 88070fe37..000000000
--- a/InCallUI/src/com/android/incallui/InCallContactInteractions.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (C) 2015 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 com.google.common.annotations.VisibleForTesting;
-
-import android.content.Context;
-import android.location.Address;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
-import android.widget.TextView;
-
-import com.android.dialer.R;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Wrapper class for objects that are used in generating the context about the contact in the InCall
- * screen.
- *
- * This handles generating the appropriate resource for the ListAdapter based on whether the contact
- * is a business contact or not and logic for the manipulation of data for the call context.
- */
-public class InCallContactInteractions {
- private static final String TAG = InCallContactInteractions.class.getSimpleName();
-
- private Context mContext;
- private InCallContactInteractionsListAdapter mListAdapter;
- private boolean mIsBusiness;
- private View mBusinessHeaderView;
- private LayoutInflater mInflater;
-
- public InCallContactInteractions(Context context, boolean isBusiness) {
- mContext = context;
- mInflater = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- switchContactType(isBusiness);
- }
-
- public InCallContactInteractionsListAdapter getListAdapter() {
- return mListAdapter;
- }
-
- /**
- * Switches the "isBusiness" value, if applicable. Recreates the list adapter with the resource
- * corresponding to the new isBusiness value if the "isBusiness" value is switched.
- *
- * @param isBusiness Whether or not the contact is a business.
- *
- * @return {@code true} if a new list adapter was created, {@code} otherwise.
- */
- public boolean switchContactType(boolean isBusiness) {
- if (mIsBusiness != isBusiness || mListAdapter == null) {
- mIsBusiness = isBusiness;
- mListAdapter = new InCallContactInteractionsListAdapter(mContext,
- mIsBusiness ? R.layout.business_context_info_list_item
- : R.layout.person_context_info_list_item);
- return true;
- }
- return false;
- }
-
- public View getBusinessListHeaderView() {
- if (mBusinessHeaderView == null) {
- mBusinessHeaderView = mInflater.inflate(
- R.layout.business_contact_context_list_header, null);
- }
- return mBusinessHeaderView;
- }
-
- public void setBusinessInfo(Address address, float distance,
- List<Pair<Calendar, Calendar>> openingHours) {
- mListAdapter.clear();
- List<ContactContextInfo> info = new ArrayList<ContactContextInfo>();
-
- // Hours of operation
- if (openingHours != null) {
- BusinessContextInfo hoursInfo = constructHoursInfo(openingHours);
- if (hoursInfo != null) {
- info.add(hoursInfo);
- }
- }
-
- // Location information
- if (address != null) {
- BusinessContextInfo locationInfo = constructLocationInfo(address, distance);
- info.add(locationInfo);
- }
-
- mListAdapter.addAll(info);
- }
-
- /**
- * Construct a BusinessContextInfo object containing hours of operation information.
- * The format is:
- * [Open now/Closed now]
- * [Hours]
- *
- * @param openingHours
- * @return BusinessContextInfo object with the schedule icon, the heading set to whether the
- * business is open or not and the details set to the hours of operation.
- */
- private BusinessContextInfo constructHoursInfo(List<Pair<Calendar, Calendar>> openingHours) {
- try {
- return constructHoursInfo(Calendar.getInstance(), openingHours);
- } catch (Exception e) {
- // Catch all exceptions here because we don't want any crashes if something goes wrong.
- Log.e(TAG, "Error constructing hours info: ", e);
- }
- return null;
- }
-
- /**
- * Pass in arbitrary current calendar time.
- */
- @VisibleForTesting
- BusinessContextInfo constructHoursInfo(Calendar currentTime,
- List<Pair<Calendar, Calendar>> openingHours) {
- if (currentTime == null || openingHours == null || openingHours.size() == 0) {
- return null;
- }
-
- BusinessContextInfo hoursInfo = new BusinessContextInfo();
- hoursInfo.iconId = R.drawable.ic_schedule_white_24dp;
-
- boolean isOpenNow = false;
- // This variable records which interval the current time is after. 0 denotes none of the
- // intervals, 1 after the first interval, etc. It is also the index of the interval the
- // current time is in (if open) or the next interval (if closed).
- int afterInterval = 0;
- // This variable counts the number of time intervals in today's opening hours.
- int todaysIntervalCount = 0;
-
- for (Pair<Calendar, Calendar> hours : openingHours) {
- if (hours.first.compareTo(currentTime) <= 0
- && currentTime.compareTo(hours.second) < 0) {
- // If the current time is on or after the opening time and strictly before the
- // closing time, then this business is open.
- isOpenNow = true;
- }
-
- if (currentTime.get(Calendar.DAY_OF_YEAR) == hours.first.get(Calendar.DAY_OF_YEAR)) {
- todaysIntervalCount += 1;
- }
-
- if (currentTime.compareTo(hours.second) > 0) {
- // This assumes that the list of intervals is sorted by time.
- afterInterval += 1;
- }
- }
-
- hoursInfo.heading = isOpenNow ? mContext.getString(R.string.open_now)
- : mContext.getString(R.string.closed_now);
-
- /*
- * The following logic determines what to display in various cases for hours of operation.
- *
- * - Display all intervals if open now and number of intervals is <=2.
- * - Display next closing time if open now and number of intervals is >2.
- * - Display next opening time if currently closed but opens later today.
- * - Display last time it closed today if closed now and tomorrow's hours are unknown.
- * - Display tomorrow's first open time if closed today and tomorrow's hours are known.
- *
- * NOTE: The logic below assumes that the intervals are sorted by ascending time. Possible
- * TODO to modify the logic above and ensure this is true.
- */
- if (isOpenNow) {
- if (todaysIntervalCount == 1) {
- hoursInfo.detail = getTimeSpanStringForHours(openingHours.get(0));
- } else if (todaysIntervalCount == 2) {
- hoursInfo.detail = mContext.getString(
- R.string.opening_hours,
- getTimeSpanStringForHours(openingHours.get(0)),
- getTimeSpanStringForHours(openingHours.get(1)));
- } else if (afterInterval < openingHours.size()) {
- // This check should not be necessary since if it is currently open, we should not
- // be after the last interval, but just in case, we don't want to crash.
- hoursInfo.detail = mContext.getString(
- R.string.closes_today_at,
- getFormattedTimeForCalendar(openingHours.get(afterInterval).second));
- }
- } else { // Currently closed
- final int lastIntervalToday = todaysIntervalCount - 1;
- if (todaysIntervalCount == 0) { // closed today
- hoursInfo.detail = mContext.getString(
- R.string.opens_tomorrow_at,
- getFormattedTimeForCalendar(openingHours.get(0).first));
- } else if (currentTime.after(openingHours.get(lastIntervalToday).second)) {
- // Passed hours for today
- if (todaysIntervalCount < openingHours.size()) {
- // If all of today's intervals are exhausted, assume the next are tomorrow's.
- hoursInfo.detail = mContext.getString(
- R.string.opens_tomorrow_at,
- getFormattedTimeForCalendar(
- openingHours.get(todaysIntervalCount).first));
- } else {
- // Grab the last time it was open today.
- hoursInfo.detail = mContext.getString(
- R.string.closed_today_at,
- getFormattedTimeForCalendar(
- openingHours.get(lastIntervalToday).second));
- }
- } else if (afterInterval < openingHours.size()) {
- // This check should not be necessary since if it is currently before the last
- // interval, afterInterval should be less than the count of intervals, but just in
- // case, we don't want to crash.
- hoursInfo.detail = mContext.getString(
- R.string.opens_today_at,
- getFormattedTimeForCalendar(openingHours.get(afterInterval).first));
- }
- }
-
- return hoursInfo;
- }
-
- String getFormattedTimeForCalendar(Calendar calendar) {
- return DateFormat.getTimeFormat(mContext).format(calendar.getTime());
- }
-
- String getTimeSpanStringForHours(Pair<Calendar, Calendar> hours) {
- return mContext.getString(R.string.open_time_span,
- getFormattedTimeForCalendar(hours.first),
- getFormattedTimeForCalendar(hours.second));
- }
-
- /**
- * Construct a BusinessContextInfo object with the location information of the business.
- * The format is:
- * [Straight line distance in miles or kilometers]
- * [Address without state/country/etc.]
- *
- * @param address An Address object containing address details of the business
- * @param distance The distance to the location in meters
- * @return A BusinessContextInfo object with the location icon, the heading as the distance to
- * the business and the details containing the address.
- */
- private BusinessContextInfo constructLocationInfo(Address address, float distance) {
- return constructLocationInfo(Locale.getDefault(), address, distance);
- }
-
- @VisibleForTesting
- BusinessContextInfo constructLocationInfo(Locale locale, Address address,
- float distance) {
- if (address == null) {
- return null;
- }
-
- BusinessContextInfo locationInfo = new BusinessContextInfo();
- locationInfo.iconId = R.drawable.ic_location_on_white_24dp;
- if (distance != DistanceHelper.DISTANCE_NOT_FOUND) {
- //TODO: add a setting to allow the user to select "KM" or "MI" as their distance units.
- if (Locale.US.equals(locale)) {
- locationInfo.heading = mContext.getString(R.string.distance_imperial_away,
- distance * DistanceHelper.MILES_PER_METER);
- } else {
- locationInfo.heading = mContext.getString(R.string.distance_metric_away,
- distance * DistanceHelper.KILOMETERS_PER_METER);
- }
- }
- if (address.getLocality() != null) {
- locationInfo.detail = mContext.getString(
- R.string.display_address,
- address.getAddressLine(0),
- address.getLocality());
- } else {
- locationInfo.detail = address.getAddressLine(0);
- }
- return locationInfo;
- }
-
- /**
- * Get the appropriate title for the context.
- * @return The "Business info" title for a business contact and the "Recent messages" title for
- * personal contacts.
- */
- public String getContactContextTitle() {
- return mIsBusiness
- ? mContext.getResources().getString(R.string.business_contact_context_title)
- : mContext.getResources().getString(R.string.person_contact_context_title);
- }
-
- public static abstract class ContactContextInfo {
- public abstract void bindView(View listItem);
- }
-
- public static class BusinessContextInfo extends ContactContextInfo {
- int iconId;
- String heading;
- String detail;
-
- @Override
- public void bindView(View listItem) {
- ImageView imageView = (ImageView) listItem.findViewById(R.id.icon);
- TextView headingTextView = (TextView) listItem.findViewById(R.id.heading);
- TextView detailTextView = (TextView) listItem.findViewById(R.id.detail);
-
- if (this.iconId == 0 || (this.heading == null && this.detail == null)) {
- return;
- }
-
- imageView.setImageDrawable(listItem.getContext().getDrawable(this.iconId));
-
- headingTextView.setText(this.heading);
- headingTextView.setVisibility(TextUtils.isEmpty(this.heading)
- ? View.GONE : View.VISIBLE);
-
- detailTextView.setText(this.detail);
- detailTextView.setVisibility(TextUtils.isEmpty(this.detail)
- ? View.GONE : View.VISIBLE);
-
- }
- }
-
- public static class PersonContextInfo extends ContactContextInfo {
- boolean isIncoming;
- String message;
- String detail;
-
- @Override
- public void bindView(View listItem) {
- TextView messageTextView = (TextView) listItem.findViewById(R.id.message);
- TextView detailTextView = (TextView) listItem.findViewById(R.id.detail);
-
- if (this.message == null || this.detail == null) {
- return;
- }
-
- messageTextView.setBackgroundResource(this.isIncoming ?
- R.drawable.incoming_sms_background : R.drawable.outgoing_sms_background);
- messageTextView.setText(this.message);
- LayoutParams messageLayoutParams = (LayoutParams) messageTextView.getLayoutParams();
- messageLayoutParams.addRule(this.isIncoming?
- RelativeLayout.ALIGN_PARENT_START : RelativeLayout.ALIGN_PARENT_END);
- messageTextView.setLayoutParams(messageLayoutParams);
-
- LayoutParams detailLayoutParams = (LayoutParams) detailTextView.getLayoutParams();
- detailLayoutParams.addRule(this.isIncoming ?
- RelativeLayout.ALIGN_PARENT_START : RelativeLayout.ALIGN_PARENT_END);
- detailTextView.setLayoutParams(detailLayoutParams);
- detailTextView.setText(this.detail);
- }
- }
-
- /**
- * A list adapter for call context information. We use the same adapter for both business and
- * contact context.
- */
- private class InCallContactInteractionsListAdapter extends ArrayAdapter<ContactContextInfo> {
- // The resource id of the list item layout.
- int mResId;
-
- public InCallContactInteractionsListAdapter(Context context, int resource) {
- super(context, resource);
- mResId = resource;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View listItem = mInflater.inflate(mResId, null);
- ContactContextInfo item = getItem(position);
-
- if (item == null) {
- return listItem;
- }
-
- item.bindView(listItem);
- return listItem;
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallDateUtils.java b/InCallUI/src/com/android/incallui/InCallDateUtils.java
deleted file mode 100644
index 3401692ea..000000000
--- a/InCallUI/src/com/android/incallui/InCallDateUtils.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.android.incallui;
-
-import android.icu.text.MeasureFormat;
-import android.icu.text.MeasureFormat.FormatWidth;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-/**
- * Methods to parse time and date information in the InCallUi
- */
-public class InCallDateUtils {
-
- /**
- * Return given duration in a human-friendly format. For example, "4 minutes 3 seconds" or
- * "3 hours 1 second". Returns the hours, minutes and seconds in that order if they exist.
- */
- public static String formatDuration(long millis) {
- int hours = 0;
- int minutes = 0;
- int seconds = 0;
- int elapsedSeconds = (int) (millis / 1000);
- if (elapsedSeconds >= 3600) {
- hours = elapsedSeconds / 3600;
- elapsedSeconds -= hours * 3600;
- }
- if (elapsedSeconds >= 60) {
- minutes = elapsedSeconds / 60;
- elapsedSeconds -= minutes * 60;
- }
- seconds = elapsedSeconds;
-
- final ArrayList<Measure> measures = new ArrayList<Measure>();
- if (hours > 0) {
- measures.add(new Measure(hours, MeasureUnit.HOUR));
- }
- if (minutes > 0) {
- measures.add(new Measure(minutes, MeasureUnit.MINUTE));
- }
- if (seconds > 0) {
- measures.add(new Measure(seconds, MeasureUnit.SECOND));
- }
-
- if (measures.isEmpty()) {
- return "";
- } else {
- return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE)
- .formatMeasures(measures.toArray(new Measure[measures.size()]));
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java b/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
deleted file mode 100644
index 3cab6dc3b..000000000
--- a/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2015 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.Context;
-import android.content.res.Configuration;
-import android.view.OrientationEventListener;
-import android.hardware.SensorManager;
-import android.view.Surface;
-import android.content.pm.ActivityInfo;
-
-/**
- * This class listens to Orientation events and overrides onOrientationChanged which gets
- * invoked when an orientation change occurs. When that happens, we notify InCallUI registrants
- * of the change.
- */
-public class InCallOrientationEventListener extends OrientationEventListener {
-
- /**
- * Screen orientation angles one of 0, 90, 180, 270, 360 in degrees.
- */
- public static int SCREEN_ORIENTATION_0 = 0;
- public static int SCREEN_ORIENTATION_90 = 90;
- public static int SCREEN_ORIENTATION_180 = 180;
- public static int SCREEN_ORIENTATION_270 = 270;
- public static int SCREEN_ORIENTATION_360 = 360;
-
- public static int FULL_SENSOR_SCREEN_ORIENTATION =
- ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
-
- public static int NO_SENSOR_SCREEN_ORIENTATION =
- ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-
- /**
- * This is to identify dead zones where we won't notify others of orientation changed.
- * Say for e.g our threshold is x degrees. We will only notify UI when our current rotation is
- * within x degrees right or left of the screen orientation angles. If it's not within those
- * ranges, we return SCREEN_ORIENTATION_UNKNOWN and ignore it.
- */
- private static int SCREEN_ORIENTATION_UNKNOWN = -1;
-
- // Rotation threshold is 10 degrees. So if the rotation angle is within 10 degrees of any of
- // the above angles, we will notify orientation changed.
- private static int ROTATION_THRESHOLD = 10;
-
-
- /**
- * Cache the current rotation of the device.
- */
- private static int sCurrentOrientation = SCREEN_ORIENTATION_0;
- private boolean mEnabled = false;
-
- public InCallOrientationEventListener(Context context) {
- super(context);
- }
-
- /**
- * Handles changes in device orientation. Notifies InCallPresenter of orientation changes.
- *
- * Note that this API receives sensor rotation in degrees as a param and we convert that to
- * one of our screen orientation constants - (one of: {@link SCREEN_ORIENTATION_0},
- * {@link SCREEN_ORIENTATION_90}, {@link SCREEN_ORIENTATION_180},
- * {@link SCREEN_ORIENTATION_270}).
- *
- * @param rotation The new device sensor rotation in degrees
- */
- @Override
- public void onOrientationChanged(int rotation) {
- if (rotation == OrientationEventListener.ORIENTATION_UNKNOWN) {
- return;
- }
-
- final int orientation = toScreenOrientation(rotation);
-
- if (orientation != SCREEN_ORIENTATION_UNKNOWN && sCurrentOrientation != orientation) {
- sCurrentOrientation = orientation;
- InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
- }
- }
-
- /**
- * Enables the OrientationEventListener and notifies listeners of current orientation if
- * notify flag is true
- * @param notify true or false. Notify device orientation changed if true.
- */
- public void enable(boolean notify) {
- if (mEnabled) {
- Log.v(this, "enable: Orientation listener is already enabled. Ignoring...");
- return;
- }
-
- super.enable();
- mEnabled = true;
- if (notify) {
- InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
- }
- }
-
- /**
- * Enables the OrientationEventListener with notify flag defaulting to false.
- */
- public void enable() {
- enable(false);
- }
-
- /**
- * Disables the OrientationEventListener.
- */
- public void disable() {
- if (!mEnabled) {
- Log.v(this, "enable: Orientation listener is already disabled. Ignoring...");
- return;
- }
-
- mEnabled = false;
- super.disable();
- }
-
- /**
- * Returns true the OrientationEventListener is enabled, false otherwise.
- */
- public boolean isEnabled() {
- return mEnabled;
- }
-
- /**
- * Converts sensor rotation in degrees to screen orientation constants.
- * @param rotation sensor rotation angle in degrees
- * @return Screen orientation angle in degrees (0, 90, 180, 270). Returns -1 for degrees not
- * within threshold to identify zones where orientation change should not be trigerred.
- */
- private int toScreenOrientation(int rotation) {
- // Sensor orientation 90 is equivalent to screen orientation 270 and vice versa. This
- // function returns the screen orientation. So we convert sensor rotation 90 to 270 and
- // vice versa here.
- if (isInLeftRange(rotation, SCREEN_ORIENTATION_360, ROTATION_THRESHOLD) ||
- isInRightRange(rotation, SCREEN_ORIENTATION_0, ROTATION_THRESHOLD)) {
- return SCREEN_ORIENTATION_0;
- } else if (isWithinThreshold(rotation, SCREEN_ORIENTATION_90, ROTATION_THRESHOLD)) {
- return SCREEN_ORIENTATION_270;
- } else if (isWithinThreshold(rotation, SCREEN_ORIENTATION_180, ROTATION_THRESHOLD)) {
- return SCREEN_ORIENTATION_180;
- } else if (isWithinThreshold(rotation, SCREEN_ORIENTATION_270, ROTATION_THRESHOLD)) {
- return SCREEN_ORIENTATION_90;
- }
- return SCREEN_ORIENTATION_UNKNOWN;
- }
-
- private static boolean isWithinRange(int value, int begin, int end) {
- return value >= begin && value < end;
- }
-
- private static boolean isWithinThreshold(int value, int center, int threshold) {
- return isWithinRange(value, center - threshold, center + threshold);
- }
-
- private static boolean isInLeftRange(int value, int center, int threshold) {
- return isWithinRange(value, center - threshold, center);
- }
-
- private static boolean isInRightRange(int value, int center, int threshold) {
- return isWithinRange(value, center, center + threshold);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
deleted file mode 100644
index 0103f61ed..000000000
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ /dev/null
@@ -1,1938 +0,0 @@
-/*
- * Copyright (C) 2013 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 com.google.common.base.Preconditions;
-
-import android.app.ActivityManager.TaskDescription;
-import android.app.FragmentManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.CallLog;
-import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telecom.VideoProfile;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-
-import com.android.contacts.common.GeoUtil;
-import com.android.contacts.common.compat.CallSdkCompat;
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
-import com.android.contacts.common.interactions.TouchPointManager;
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-import com.android.dialer.R;
-import com.android.dialer.calllog.CallLogAsyncTaskUtil;
-import com.android.dialer.calllog.CallLogAsyncTaskUtil.OnCallLogQueryFinishedListener;
-import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
-import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
-import com.android.dialer.filterednumber.FilteredNumbersUtil;
-import com.android.dialer.logging.InteractionEvent;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.util.TelecomUtil;
-import com.android.incallui.spam.SpamCallListListener;
-import com.android.incallui.util.TelecomCallUtil;
-import com.android.incalluibind.ObjectFactory;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Takes updates from the CallList and notifies the InCallActivity (UI)
- * of the changes.
- * Responsible for starting the activity for a new call and finishing the activity when all calls
- * are disconnected.
- * Creates and manages the in-call state and provides a listener pattern for the presenters
- * that want to listen in on the in-call state changes.
- * TODO: This class has become more of a state machine at this point. Consider renaming.
- */
-public class InCallPresenter implements CallList.Listener,
- CircularRevealFragment.OnCircularRevealCompleteListener,
- InCallVideoCallCallbackNotifier.SessionModificationListener {
-
- private static final String EXTRA_FIRST_TIME_SHOWN =
- "com.android.incallui.intent.extra.FIRST_TIME_SHOWN";
-
- private static final long BLOCK_QUERY_TIMEOUT_MS = 1000;
-
- private static final Bundle EMPTY_EXTRAS = new Bundle();
-
- private static InCallPresenter sInCallPresenter;
-
- /**
- * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
- * load factor before resizing, 1 means we only expect a single thread to
- * access the map so make only a single shard
- */
- private final Set<InCallStateListener> mListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallStateListener, Boolean>(8, 0.9f, 1));
- private final List<IncomingCallListener> mIncomingCallListeners = new CopyOnWriteArrayList<>();
- private final Set<InCallDetailsListener> mDetailsListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallDetailsListener, Boolean>(8, 0.9f, 1));
- private final Set<CanAddCallListener> mCanAddCallListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<CanAddCallListener, Boolean>(8, 0.9f, 1));
- private final Set<InCallUiListener> mInCallUiListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallUiListener, Boolean>(8, 0.9f, 1));
- private final Set<InCallOrientationListener> mOrientationListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallOrientationListener, Boolean>(8, 0.9f, 1));
- private final Set<InCallEventListener> mInCallEventListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallEventListener, Boolean>(8, 0.9f, 1));
-
- private AudioModeProvider mAudioModeProvider;
- private StatusBarNotifier mStatusBarNotifier;
- private ExternalCallNotifier mExternalCallNotifier;
- private ContactInfoCache mContactInfoCache;
- private Context mContext;
- private CallList mCallList;
- private ExternalCallList mExternalCallList;
- private InCallActivity mInCallActivity;
- private InCallState mInCallState = InCallState.NO_CALLS;
- private ProximitySensor mProximitySensor;
- private boolean mServiceConnected = false;
- private boolean mAccountSelectionCancelled = false;
- private InCallCameraManager mInCallCameraManager = null;
- private AnswerPresenter mAnswerPresenter = new AnswerPresenter();
- private FilteredNumberAsyncQueryHandler mFilteredQueryHandler;
- private CallList.Listener mSpamCallListListener;
-
- /**
- * Whether or not we are currently bound and waiting for Telecom to send us a new call.
- */
- private boolean mBoundAndWaitingForOutgoingCall;
-
- /**
- * If there is no actual call currently in the call list, this will be used as a fallback
- * to determine the theme color for InCallUI.
- */
- private PhoneAccountHandle mPendingPhoneAccountHandle;
-
- /**
- * Determines if the InCall UI is in fullscreen mode or not.
- */
- private boolean mIsFullScreen = false;
-
- private final android.telecom.Call.Callback mCallCallback = new android.telecom.Call.Callback() {
- @Override
- public void onPostDialWait(android.telecom.Call telecomCall,
- String remainingPostDialSequence) {
- final Call call = mCallList.getCallByTelecomCall(telecomCall);
- if (call == null) {
- Log.w(this, "Call not found in call list: " + telecomCall);
- return;
- }
- onPostDialCharWait(call.getId(), remainingPostDialSequence);
- }
-
- @Override
- public void onDetailsChanged(android.telecom.Call telecomCall,
- android.telecom.Call.Details details) {
- final Call call = mCallList.getCallByTelecomCall(telecomCall);
- if (call == null) {
- Log.w(this, "Call not found in call list: " + telecomCall);
- return;
- }
- for (InCallDetailsListener listener : mDetailsListeners) {
- listener.onDetailsChanged(call, details);
- }
- }
-
- @Override
- public void onConferenceableCallsChanged(android.telecom.Call telecomCall,
- List<android.telecom.Call> conferenceableCalls) {
- Log.i(this, "onConferenceableCallsChanged: " + telecomCall);
- onDetailsChanged(telecomCall, telecomCall.getDetails());
- }
- };
-
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- public void onCallStateChanged(int state, String incomingNumber) {
- if (state == TelephonyManager.CALL_STATE_RINGING) {
- if (FilteredNumbersUtil.hasRecentEmergencyCall(mContext)) {
- return;
- }
- // Check if the number is blocked, to silence the ringer.
- String countryIso = GeoUtil.getCurrentCountryIso(mContext);
- mFilteredQueryHandler.isBlockedNumber(
- mOnCheckBlockedListener, incomingNumber, countryIso);
- }
- }
- };
-
- private final OnCheckBlockedListener mOnCheckBlockedListener = new OnCheckBlockedListener() {
- @Override
- public void onCheckComplete(final Integer id) {
- if (id != null) {
- // Silence the ringer now to prevent ringing and vibration before the call is
- // terminated when Telecom attempts to add it.
- TelecomUtil.silenceRinger(mContext);
- }
- }
- };
-
- /**
- * Observes the CallLog to delete the call log entry for the blocked call after it is added.
- * Times out if too much time has passed.
- */
- private class BlockedNumberContentObserver extends ContentObserver {
- private static final int TIMEOUT_MS = 5000;
-
- private Handler mHandler;
- private String mNumber;
- private long mTimeAddedMs;
-
- private Runnable mTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- unregister();
- }
- };
-
- public BlockedNumberContentObserver(Handler handler, String number, long timeAddedMs) {
- super(handler);
-
- mHandler = handler;
- mNumber = number;
- mTimeAddedMs = timeAddedMs;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- CallLogAsyncTaskUtil.deleteBlockedCall(mContext, mNumber, mTimeAddedMs,
- new OnCallLogQueryFinishedListener() {
- @Override
- public void onQueryFinished(boolean hasEntry) {
- if (mContext != null && hasEntry) {
- unregister();
- }
- }
- });
- }
-
- public void register() {
- if (mContext != null) {
- mContext.getContentResolver().registerContentObserver(
- CallLog.CONTENT_URI, true, this);
- mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
- }
- }
-
- private void unregister() {
- if (mContext != null) {
- mHandler.removeCallbacks(mTimeoutRunnable);
- mContext.getContentResolver().unregisterContentObserver(this);
- }
- }
- };
-
- /**
- * Is true when the activity has been previously started. Some code needs to know not just if
- * the activity is currently up, but if it had been previously shown in foreground for this
- * in-call session (e.g., StatusBarNotifier). This gets reset when the session ends in the
- * tear-down method.
- */
- private boolean mIsActivityPreviouslyStarted = false;
-
- /**
- * Whether or not InCallService is bound to Telecom.
- */
- private boolean mServiceBound = false;
-
- /**
- * When configuration changes Android kills the current activity and starts a new one.
- * The flag is used to check if full clean up is necessary (activity is stopped and new
- * activity won't be started), or if a new activity will be started right after the current one
- * is destroyed, and therefore no need in release all resources.
- */
- private boolean mIsChangingConfigurations = false;
-
- /** Display colors for the UI. Consists of a primary color and secondary (darker) color */
- private MaterialPalette mThemeColors;
-
- private TelecomManager mTelecomManager;
- private TelephonyManager mTelephonyManager;
-
- public static synchronized InCallPresenter getInstance() {
- if (sInCallPresenter == null) {
- sInCallPresenter = new InCallPresenter();
- }
- return sInCallPresenter;
- }
-
- @NeededForTesting
- static synchronized void setInstance(InCallPresenter inCallPresenter) {
- sInCallPresenter = inCallPresenter;
- }
-
- public InCallState getInCallState() {
- return mInCallState;
- }
-
- public CallList getCallList() {
- return mCallList;
- }
-
- public void setUp(Context context,
- CallList callList,
- ExternalCallList externalCallList,
- AudioModeProvider audioModeProvider,
- StatusBarNotifier statusBarNotifier,
- ExternalCallNotifier externalCallNotifier,
- ContactInfoCache contactInfoCache,
- ProximitySensor proximitySensor) {
- if (mServiceConnected) {
- Log.i(this, "New service connection replacing existing one.");
- // retain the current resources, no need to create new ones.
- Preconditions.checkState(context == mContext);
- Preconditions.checkState(callList == mCallList);
- Preconditions.checkState(audioModeProvider == mAudioModeProvider);
- return;
- }
-
- Preconditions.checkNotNull(context);
- mContext = context;
-
- mContactInfoCache = contactInfoCache;
-
- mStatusBarNotifier = statusBarNotifier;
- mExternalCallNotifier = externalCallNotifier;
- addListener(mStatusBarNotifier);
-
- mAudioModeProvider = audioModeProvider;
-
- mProximitySensor = proximitySensor;
- addListener(mProximitySensor);
-
- addIncomingCallListener(mAnswerPresenter);
- addInCallUiListener(mAnswerPresenter);
-
- mCallList = callList;
- mExternalCallList = externalCallList;
- externalCallList.addExternalCallListener(mExternalCallNotifier);
-
- // This only gets called by the service so this is okay.
- mServiceConnected = true;
-
- // The final thing we do in this set up is add ourselves as a listener to CallList. This
- // will kick off an update and the whole process can start.
- mCallList.addListener(this);
-
- // Create spam call list listener and add it to the list of listeners
- mSpamCallListListener = new SpamCallListListener(context);
- mCallList.addListener(mSpamCallListListener);
-
- VideoPauseController.getInstance().setUp(this);
- InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
-
- mFilteredQueryHandler = new FilteredNumberAsyncQueryHandler(context.getContentResolver());
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- mCallList.setExtendedCallInfoService(
- com.android.dialerbind.ObjectFactory.newExtendedCallInfoService(context));
-
- Log.d(this, "Finished InCallPresenter.setUp");
- }
-
- /**
- * Called when the telephony service has disconnected from us. This will happen when there are
- * no more active calls. However, we may still want to continue showing the UI for
- * certain cases like showing "Call Ended".
- * What we really want is to wait for the activity and the service to both disconnect before we
- * tear things down. This method sets a serviceConnected boolean and calls a secondary method
- * that performs the aforementioned logic.
- */
- public void tearDown() {
- Log.d(this, "tearDown");
- mCallList.clearOnDisconnect();
-
- mServiceConnected = false;
- attemptCleanup();
-
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- VideoPauseController.getInstance().tearDown();
- InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
- }
-
- private void attemptFinishActivity() {
- final boolean doFinish = (mInCallActivity != null && isActivityStarted());
- Log.i(this, "Hide in call UI: " + doFinish);
- if (doFinish) {
- mInCallActivity.setExcludeFromRecents(true);
- mInCallActivity.finish();
-
- if (mAccountSelectionCancelled) {
- // This finish is a result of account selection cancellation
- // do not include activity ending transition
- mInCallActivity.overridePendingTransition(0, 0);
- }
- }
- }
-
- /**
- * Called when the UI begins, and starts the callstate callbacks if necessary.
- */
- public void setActivity(InCallActivity inCallActivity) {
- if (inCallActivity == null) {
- throw new IllegalArgumentException("registerActivity cannot be called with null");
- }
- if (mInCallActivity != null && mInCallActivity != inCallActivity) {
- Log.w(this, "Setting a second activity before destroying the first.");
- }
- updateActivity(inCallActivity);
- }
-
- /**
- * Called when the UI ends. Attempts to tear down everything if necessary. See
- * {@link #tearDown()} for more insight on the tear-down process.
- */
- public void unsetActivity(InCallActivity inCallActivity) {
- if (inCallActivity == null) {
- throw new IllegalArgumentException("unregisterActivity cannot be called with null");
- }
- if (mInCallActivity == null) {
- Log.i(this, "No InCallActivity currently set, no need to unset.");
- return;
- }
- if (mInCallActivity != inCallActivity) {
- Log.w(this, "Second instance of InCallActivity is trying to unregister when another"
- + " instance is active. Ignoring.");
- return;
- }
- updateActivity(null);
- }
-
- /**
- * Updates the current instance of {@link InCallActivity} with the provided one. If a
- * {@code null} activity is provided, it means that the activity was finished and we should
- * attempt to cleanup.
- */
- private void updateActivity(InCallActivity inCallActivity) {
- boolean updateListeners = false;
- boolean doAttemptCleanup = false;
-
- if (inCallActivity != null) {
- if (mInCallActivity == null) {
- updateListeners = true;
- Log.i(this, "UI Initialized");
- } else {
- // since setActivity is called onStart(), it can be called multiple times.
- // This is fine and ignorable, but we do not want to update the world every time
- // this happens (like going to/from background) so we do not set updateListeners.
- }
-
- mInCallActivity = inCallActivity;
- mInCallActivity.setExcludeFromRecents(false);
-
- // By the time the UI finally comes up, the call may already be disconnected.
- // If that's the case, we may need to show an error dialog.
- if (mCallList != null && mCallList.getDisconnectedCall() != null) {
- maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall());
- }
-
- // When the UI comes up, we need to first check the in-call state.
- // If we are showing NO_CALLS, that means that a call probably connected and
- // then immediately disconnected before the UI was able to come up.
- // If we dont have any calls, start tearing down the UI instead.
- // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after
- // it has been set.
- if (mInCallState == InCallState.NO_CALLS) {
- Log.i(this, "UI Initialized, but no calls left. shut down.");
- attemptFinishActivity();
- return;
- }
- } else {
- Log.i(this, "UI Destroyed");
- updateListeners = true;
- mInCallActivity = null;
-
- // We attempt cleanup for the destroy case but only after we recalculate the state
- // to see if we need to come back up or stay shut down. This is why we do the
- // cleanup after the call to onCallListChange() instead of directly here.
- doAttemptCleanup = true;
- }
-
- // Messages can come from the telephony layer while the activity is coming up
- // and while the activity is going down. So in both cases we need to recalculate what
- // state we should be in after they complete.
- // Examples: (1) A new incoming call could come in and then get disconnected before
- // the activity is created.
- // (2) All calls could disconnect and then get a new incoming call before the
- // activity is destroyed.
- //
- // b/1122139 - We previously had a check for mServiceConnected here as well, but there are
- // cases where we need to recalculate the current state even if the service in not
- // connected. In particular the case where startOrFinish() is called while the app is
- // already finish()ing. In that case, we skip updating the state with the knowledge that
- // we will check again once the activity has finished. That means we have to recalculate the
- // state here even if the service is disconnected since we may not have finished a state
- // transition while finish()ing.
- if (updateListeners) {
- onCallListChange(mCallList);
- }
-
- if (doAttemptCleanup) {
- attemptCleanup();
- }
- }
-
- private boolean mAwaitingCallListUpdate = false;
-
- public void onBringToForeground(boolean showDialpad) {
- Log.i(this, "Bringing UI to foreground.");
- bringToForeground(showDialpad);
- }
-
- public void onCallAdded(final android.telecom.Call call) {
- LatencyReport latencyReport = new LatencyReport(call);
- if (shouldAttemptBlocking(call)) {
- maybeBlockCall(call, latencyReport);
- } else {
- latencyReport.onCallBlockingDone();
- if (call.getDetails()
- .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
- mExternalCallList.onCallAdded(call);
- } else {
- mCallList.onCallAdded(call, latencyReport);
- }
- }
-
- // Since a call has been added we are no longer waiting for Telecom to send us a call.
- setBoundAndWaitingForOutgoingCall(false, null);
- call.registerCallback(mCallCallback);
- }
-
- private boolean shouldAttemptBlocking(android.telecom.Call call) {
- if (call.getState() != android.telecom.Call.STATE_RINGING) {
- return false;
- }
- if (TelecomCallUtil.isEmergencyCall(call)) {
- Log.i(this, "Not attempting to block incoming emergency call");
- return false;
- }
- if (FilteredNumbersUtil.hasRecentEmergencyCall(mContext)) {
- Log.i(this, "Not attempting to block incoming call due to recent emergency call");
- return false;
- }
- if (call.getDetails().hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Checks whether a call should be blocked, and blocks it if so. Otherwise, it adds the call
- * to the CallList so it can proceed as normal. There is a timeout, so if the function for
- * checking whether a function is blocked does not return in a reasonable time, we proceed
- * with adding the call anyways.
- */
- private void maybeBlockCall(final android.telecom.Call call,
- final LatencyReport latencyReport) {
- final String countryIso = GeoUtil.getCurrentCountryIso(mContext);
- final String number = TelecomCallUtil.getNumber(call);
- final long timeAdded = System.currentTimeMillis();
-
- // Though AtomicBoolean's can be scary, don't fear, as in this case it is only used on the
- // main UI thread. It is needed so we can change its value within different scopes, since
- // that cannot be done with a final boolean.
- final AtomicBoolean hasTimedOut = new AtomicBoolean(false);
-
- final Handler handler = new Handler();
-
- // Proceed if the query is slow; the call may still be blocked after the query returns.
- final Runnable runnable = new Runnable() {
- public void run() {
- hasTimedOut.set(true);
- latencyReport.onCallBlockingDone();
- mCallList.onCallAdded(call, latencyReport);
- }
- };
- handler.postDelayed(runnable, BLOCK_QUERY_TIMEOUT_MS);
-
- OnCheckBlockedListener onCheckBlockedListener = new OnCheckBlockedListener() {
- @Override
- public void onCheckComplete(final Integer id) {
- if (!hasTimedOut.get()) {
- handler.removeCallbacks(runnable);
- }
- if (id == null) {
- if (!hasTimedOut.get()) {
- latencyReport.onCallBlockingDone();
- mCallList.onCallAdded(call, latencyReport);
- }
- } else {
- Log.i(this, "Rejecting incoming call from blocked number");
- call.reject(false, null);
- Logger.logInteraction(InteractionEvent.CALL_BLOCKED);
-
- mFilteredQueryHandler.incrementFilteredCount(id);
-
- // Register observer to update the call log.
- // BlockedNumberContentObserver will unregister after successful log or timeout.
- BlockedNumberContentObserver contentObserver =
- new BlockedNumberContentObserver(new Handler(), number, timeAdded);
- contentObserver.register();
- }
- }
- };
-
- final boolean success = mFilteredQueryHandler.isBlockedNumber(
- onCheckBlockedListener, number, countryIso);
- if (!success) {
- Log.d(this, "checkForBlockedCall: invalid number, skipping block checking");
- if (!hasTimedOut.get()) {
- handler.removeCallbacks(runnable);
-
- latencyReport.onCallBlockingDone();
- mCallList.onCallAdded(call, latencyReport);
- }
- }
- }
-
- public void onCallRemoved(android.telecom.Call call) {
- if (call.getDetails()
- .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
- mExternalCallList.onCallRemoved(call);
- } else {
- mCallList.onCallRemoved(call);
- call.unregisterCallback(mCallCallback);
- }
- }
-
- public void onCanAddCallChanged(boolean canAddCall) {
- for (CanAddCallListener listener : mCanAddCallListeners) {
- listener.onCanAddCallChanged(canAddCall);
- }
- }
-
- /**
- * Called when there is a change to the call list.
- * Sets the In-Call state for the entire in-call app based on the information it gets from
- * CallList. Dispatches the in-call state to all listeners. Can trigger the creation or
- * destruction of the UI based on the states that is calculates.
- */
- @Override
- public void onCallListChange(CallList callList) {
- if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null &&
- mInCallActivity.getCallCardFragment().isAnimating()) {
- mAwaitingCallListUpdate = true;
- return;
- }
- if (callList == null) {
- return;
- }
-
- mAwaitingCallListUpdate = false;
-
- InCallState newState = getPotentialStateFromCallList(callList);
- InCallState oldState = mInCallState;
- Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState);
- newState = startOrFinishUi(newState);
- Log.d(this, "onCallListChange newState changed to " + newState);
-
- // Set the new state before announcing it to the world
- Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
- mInCallState = newState;
-
- // notify listeners of new state
- for (InCallStateListener listener : mListeners) {
- Log.d(this, "Notify " + listener + " of state " + mInCallState.toString());
- listener.onStateChange(oldState, mInCallState, callList);
- }
-
- if (isActivityStarted()) {
- final boolean hasCall = callList.getActiveOrBackgroundCall() != null ||
- callList.getOutgoingCall() != null;
- mInCallActivity.dismissKeyguard(hasCall);
- }
- }
-
- /**
- * Called when there is a new incoming call.
- *
- * @param call
- */
- @Override
- public void onIncomingCall(Call call) {
- InCallState newState = startOrFinishUi(InCallState.INCOMING);
- InCallState oldState = mInCallState;
-
- Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
- mInCallState = newState;
-
- for (IncomingCallListener listener : mIncomingCallListeners) {
- listener.onIncomingCall(oldState, mInCallState, call);
- }
- }
-
- @Override
- public void onUpgradeToVideo(Call call) {
- //NO-OP
- }
- /**
- * Called when a call becomes disconnected. Called everytime an existing call
- * changes from being connected (incoming/outgoing/active) to disconnected.
- */
- @Override
- public void onDisconnect(Call call) {
- maybeShowErrorDialogOnDisconnect(call);
-
- // We need to do the run the same code as onCallListChange.
- onCallListChange(mCallList);
-
- if (isActivityStarted()) {
- mInCallActivity.dismissKeyguard(false);
- }
-
- if (call.isEmergencyCall()) {
- FilteredNumbersUtil.recordLastEmergencyCallTime(mContext);
- }
- }
-
- @Override
- public void onUpgradeToVideoRequest(Call call, int videoState) {
- Log.d(this, "onUpgradeToVideoRequest call = " + call + " video state = " + videoState);
-
- if (call == null) {
- return;
- }
-
- call.setRequestedVideoState(videoState);
- }
-
- /**
- * Given the call list, return the state in which the in-call screen should be.
- */
- public InCallState getPotentialStateFromCallList(CallList callList) {
-
- InCallState newState = InCallState.NO_CALLS;
-
- if (callList == null) {
- return newState;
- }
- if (callList.getIncomingCall() != null) {
- newState = InCallState.INCOMING;
- } else if (callList.getWaitingForAccountCall() != null) {
- newState = InCallState.WAITING_FOR_ACCOUNT;
- } else if (callList.getPendingOutgoingCall() != null) {
- newState = InCallState.PENDING_OUTGOING;
- } else if (callList.getOutgoingCall() != null) {
- newState = InCallState.OUTGOING;
- } else if (callList.getActiveCall() != null ||
- callList.getBackgroundCall() != null ||
- callList.getDisconnectedCall() != null ||
- callList.getDisconnectingCall() != null) {
- newState = InCallState.INCALL;
- }
-
- if (newState == InCallState.NO_CALLS) {
- if (mBoundAndWaitingForOutgoingCall) {
- return InCallState.OUTGOING;
- }
- }
-
- return newState;
- }
-
- public boolean isBoundAndWaitingForOutgoingCall() {
- return mBoundAndWaitingForOutgoingCall;
- }
-
- public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) {
- // NOTE: It is possible for there to be a race and have handle become null before
- // the circular reveal starts. This should not cause any problems because CallCardFragment
- // should fallback to the actual call in the CallList at that point in time to determine
- // the theme color.
- Log.i(this, "setBoundAndWaitingForOutgoingCall: " + isBound);
- mBoundAndWaitingForOutgoingCall = isBound;
- mPendingPhoneAccountHandle = handle;
- if (isBound && mInCallState == InCallState.NO_CALLS) {
- mInCallState = InCallState.OUTGOING;
- }
- }
-
- @Override
- public void onCircularRevealComplete(FragmentManager fm) {
- if (mInCallActivity != null) {
- mInCallActivity.showCallCardFragment(true);
- mInCallActivity.getCallCardFragment().animateForNewOutgoingCall();
- CircularRevealFragment.endCircularReveal(mInCallActivity.getFragmentManager());
- }
- }
-
- public void onShrinkAnimationComplete() {
- if (mAwaitingCallListUpdate) {
- onCallListChange(mCallList);
- }
- }
-
- public void addIncomingCallListener(IncomingCallListener listener) {
- Preconditions.checkNotNull(listener);
- mIncomingCallListeners.add(listener);
- }
-
- public void removeIncomingCallListener(IncomingCallListener listener) {
- if (listener != null) {
- mIncomingCallListeners.remove(listener);
- }
- }
-
- public void addListener(InCallStateListener listener) {
- Preconditions.checkNotNull(listener);
- mListeners.add(listener);
- }
-
- public void removeListener(InCallStateListener listener) {
- if (listener != null) {
- mListeners.remove(listener);
- }
- }
-
- public void addDetailsListener(InCallDetailsListener listener) {
- Preconditions.checkNotNull(listener);
- mDetailsListeners.add(listener);
- }
-
- public void removeDetailsListener(InCallDetailsListener listener) {
- if (listener != null) {
- mDetailsListeners.remove(listener);
- }
- }
-
- public void addCanAddCallListener(CanAddCallListener listener) {
- Preconditions.checkNotNull(listener);
- mCanAddCallListeners.add(listener);
- }
-
- public void removeCanAddCallListener(CanAddCallListener listener) {
- if (listener != null) {
- mCanAddCallListeners.remove(listener);
- }
- }
-
- public void addOrientationListener(InCallOrientationListener listener) {
- Preconditions.checkNotNull(listener);
- mOrientationListeners.add(listener);
- }
-
- public void removeOrientationListener(InCallOrientationListener listener) {
- if (listener != null) {
- mOrientationListeners.remove(listener);
- }
- }
-
- public void addInCallEventListener(InCallEventListener listener) {
- Preconditions.checkNotNull(listener);
- mInCallEventListeners.add(listener);
- }
-
- public void removeInCallEventListener(InCallEventListener listener) {
- if (listener != null) {
- mInCallEventListeners.remove(listener);
- }
- }
-
- public ProximitySensor getProximitySensor() {
- return mProximitySensor;
- }
-
- public void handleAccountSelection(PhoneAccountHandle accountHandle, boolean setDefault) {
- if (mCallList != null) {
- Call call = mCallList.getWaitingForAccountCall();
- if (call != null) {
- String callId = call.getId();
- TelecomAdapter.getInstance().phoneAccountSelected(callId, accountHandle, setDefault);
- }
- }
- }
-
- public void cancelAccountSelection() {
- mAccountSelectionCancelled = true;
- if (mCallList != null) {
- Call call = mCallList.getWaitingForAccountCall();
- if (call != null) {
- String callId = call.getId();
- TelecomAdapter.getInstance().disconnectCall(callId);
- }
- }
- }
-
- /**
- * Hangs up any active or outgoing calls.
- */
- public void hangUpOngoingCall(Context context) {
- // By the time we receive this intent, we could be shut down and call list
- // could be null. Bail in those cases.
- if (mCallList == null) {
- if (mStatusBarNotifier == null) {
- // The In Call UI has crashed but the notification still stayed up. We should not
- // come to this stage.
- StatusBarNotifier.clearAllCallNotifications(context);
- }
- return;
- }
-
- Call call = mCallList.getOutgoingCall();
- if (call == null) {
- call = mCallList.getActiveOrBackgroundCall();
- }
-
- if (call != null) {
- TelecomAdapter.getInstance().disconnectCall(call.getId());
- call.setState(Call.State.DISCONNECTING);
- mCallList.onUpdate(call);
- }
- }
-
- /**
- * Answers any incoming call.
- */
- public void answerIncomingCall(Context context, int videoState) {
- // By the time we receive this intent, we could be shut down and call list
- // could be null. Bail in those cases.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- return;
- }
-
- Call call = mCallList.getIncomingCall();
- if (call != null) {
- TelecomAdapter.getInstance().answerCall(call.getId(), videoState);
- showInCall(false, false/* newOutgoingCall */);
- }
- }
-
- /**
- * Declines any incoming call.
- */
- public void declineIncomingCall(Context context) {
- // By the time we receive this intent, we could be shut down and call list
- // could be null. Bail in those cases.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- return;
- }
-
- Call call = mCallList.getIncomingCall();
- if (call != null) {
- TelecomAdapter.getInstance().rejectCall(call.getId(), false, null);
- }
- }
-
- public void acceptUpgradeRequest(int videoState, Context context) {
- Log.d(this, " acceptUpgradeRequest videoState " + videoState);
- // Bail if we have been shut down and the call list is null.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- Log.e(this, " acceptUpgradeRequest mCallList is empty so returning");
- return;
- }
-
- Call call = mCallList.getVideoUpgradeRequestCall();
- if (call != null) {
- VideoProfile videoProfile = new VideoProfile(videoState);
- call.getVideoCall().sendSessionModifyResponse(videoProfile);
- call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
- }
-
- public void declineUpgradeRequest(Context context) {
- Log.d(this, " declineUpgradeRequest");
- // Bail if we have been shut down and the call list is null.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- Log.e(this, " declineUpgradeRequest mCallList is empty so returning");
- return;
- }
-
- Call call = mCallList.getVideoUpgradeRequestCall();
- if (call != null) {
- VideoProfile videoProfile =
- new VideoProfile(call.getVideoState());
- call.getVideoCall().sendSessionModifyResponse(videoProfile);
- call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
- }
-
- /*package*/
- void declineUpgradeRequest() {
- // Pass mContext if InCallActivity is destroyed.
- // Ex: When user pressed back key while in active call and
- // then modify request is received followed by MT call.
- declineUpgradeRequest(mInCallActivity != null ? mInCallActivity : mContext);
- }
-
- /**
- * Returns true if the incall app is the foreground application.
- */
- public boolean isShowingInCallUi() {
- return (isActivityStarted() && mInCallActivity.isVisible());
- }
-
- /**
- * Returns true if the activity has been created and is running.
- * Returns true as long as activity is not destroyed or finishing. This ensures that we return
- * true even if the activity is paused (not in foreground).
- */
- public boolean isActivityStarted() {
- return (mInCallActivity != null &&
- !mInCallActivity.isDestroyed() &&
- !mInCallActivity.isFinishing());
- }
-
- public boolean isActivityPreviouslyStarted() {
- return mIsActivityPreviouslyStarted;
- }
-
- /**
- * Determines if the In-Call app is currently changing configuration.
- *
- * @return {@code true} if the In-Call app is changing configuration.
- */
- public boolean isChangingConfigurations() {
- return mIsChangingConfigurations;
- }
-
- /**
- * Tracks whether the In-Call app is currently in the process of changing configuration (i.e.
- * screen orientation).
- */
- /*package*/
- void updateIsChangingConfigurations() {
- mIsChangingConfigurations = false;
- if (mInCallActivity != null) {
- mIsChangingConfigurations = mInCallActivity.isChangingConfigurations();
- }
- Log.v(this, "updateIsChangingConfigurations = " + mIsChangingConfigurations);
- }
-
-
- /**
- * Called when the activity goes in/out of the foreground.
- */
- public void onUiShowing(boolean showing) {
- // We need to update the notification bar when we leave the UI because that
- // could trigger it to show again.
- if (mStatusBarNotifier != null) {
- mStatusBarNotifier.updateNotification(mInCallState, mCallList);
- }
-
- if (mProximitySensor != null) {
- mProximitySensor.onInCallShowing(showing);
- }
-
- Intent broadcastIntent = ObjectFactory.getUiReadyBroadcastIntent(mContext);
- if (broadcastIntent != null) {
- broadcastIntent.putExtra(EXTRA_FIRST_TIME_SHOWN, !mIsActivityPreviouslyStarted);
-
- if (showing) {
- Log.d(this, "Sending sticky broadcast: ", broadcastIntent);
- mContext.sendStickyBroadcast(broadcastIntent);
- } else {
- Log.d(this, "Removing sticky broadcast: ", broadcastIntent);
- mContext.removeStickyBroadcast(broadcastIntent);
- }
- }
-
- if (showing) {
- mIsActivityPreviouslyStarted = true;
- } else {
- updateIsChangingConfigurations();
- }
-
- for (InCallUiListener listener : mInCallUiListeners) {
- listener.onUiShowing(showing);
- }
- }
-
- public void addInCallUiListener(InCallUiListener listener) {
- mInCallUiListeners.add(listener);
- }
-
- public boolean removeInCallUiListener(InCallUiListener listener) {
- return mInCallUiListeners.remove(listener);
- }
-
- /*package*/
- void onActivityStarted() {
- Log.d(this, "onActivityStarted");
- notifyVideoPauseController(true);
- }
-
- /*package*/
- void onActivityStopped() {
- Log.d(this, "onActivityStopped");
- notifyVideoPauseController(false);
- }
-
- private void notifyVideoPauseController(boolean showing) {
- Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" +
- mIsChangingConfigurations);
- if (!mIsChangingConfigurations) {
- VideoPauseController.getInstance().onUiShowing(showing);
- }
- }
-
- /**
- * Brings the app into the foreground if possible.
- */
- public void bringToForeground(boolean showDialpad) {
- // Before we bring the incall UI to the foreground, we check to see if:
- // 1. It is not currently in the foreground
- // 2. We are in a state where we want to show the incall ui (i.e. there are calls to
- // be displayed)
- // If the activity hadn't actually been started previously, yet there are still calls
- // present (e.g. a call was accepted by a bluetooth or wired headset), we want to
- // bring it up the UI regardless.
- if (!isShowingInCallUi() && mInCallState != InCallState.NO_CALLS) {
- showInCall(showDialpad, false /* newOutgoingCall */);
- }
- }
-
- public void onPostDialCharWait(String callId, String chars) {
- if (isActivityStarted()) {
- mInCallActivity.showPostCharWaitDialog(callId, chars);
- }
- }
-
- /**
- * Handles the green CALL key while in-call.
- * @return true if we consumed the event.
- */
- public boolean handleCallKey() {
- Log.v(this, "handleCallKey");
-
- // The green CALL button means either "Answer", "Unhold", or
- // "Swap calls", or can be a no-op, depending on the current state
- // of the Phone.
-
- /**
- * INCOMING CALL
- */
- final CallList calls = mCallList;
- final Call incomingCall = calls.getIncomingCall();
- Log.v(this, "incomingCall: " + incomingCall);
-
- // (1) Attempt to answer a call
- if (incomingCall != null) {
- TelecomAdapter.getInstance().answerCall(
- incomingCall.getId(), VideoProfile.STATE_AUDIO_ONLY);
- return true;
- }
-
- /**
- * STATE_ACTIVE CALL
- */
- final Call activeCall = calls.getActiveCall();
- if (activeCall != null) {
- // TODO: This logic is repeated from CallButtonPresenter.java. We should
- // consolidate this logic.
- final boolean canMerge = activeCall.can(
- android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
- final boolean canSwap = activeCall.can(
- android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
-
- Log.v(this, "activeCall: " + activeCall + ", canMerge: " + canMerge +
- ", canSwap: " + canSwap);
-
- // (2) Attempt actions on conference calls
- if (canMerge) {
- TelecomAdapter.getInstance().merge(activeCall.getId());
- return true;
- } else if (canSwap) {
- TelecomAdapter.getInstance().swap(activeCall.getId());
- return true;
- }
- }
-
- /**
- * BACKGROUND CALL
- */
- final Call heldCall = calls.getBackgroundCall();
- if (heldCall != null) {
- // We have a hold call so presumeable it will always support HOLD...but
- // there is no harm in double checking.
- final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
-
- Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold);
-
- // (4) unhold call
- if (heldCall.getState() == Call.State.ONHOLD && canHold) {
- TelecomAdapter.getInstance().unholdCall(heldCall.getId());
- return true;
- }
- }
-
- // Always consume hard keys
- return true;
- }
-
- /**
- * A dialog could have prevented in-call screen from being previously finished.
- * This function checks to see if there should be any UI left and if not attempts
- * to tear down the UI.
- */
- public void onDismissDialog() {
- Log.i(this, "Dialog dismissed");
- if (mInCallState == InCallState.NO_CALLS) {
- attemptFinishActivity();
- attemptCleanup();
- }
- }
-
- /**
- * Toggles whether the application is in fullscreen mode or not.
- *
- * @return {@code true} if in-call is now in fullscreen mode.
- */
- public boolean toggleFullscreenMode() {
- boolean isFullScreen = !mIsFullScreen;
- Log.v(this, "toggleFullscreenMode = " + isFullScreen);
- setFullScreen(isFullScreen);
- return mIsFullScreen;
- }
-
- /**
- * Clears the previous fullscreen state.
- */
- public void clearFullscreen() {
- mIsFullScreen = false;
- }
-
- /**
- * Changes the fullscreen mode of the in-call UI.
- *
- * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
- * otherwise.
- */
- public void setFullScreen(boolean isFullScreen) {
- setFullScreen(isFullScreen, false /* force */);
- }
-
- /**
- * Changes the fullscreen mode of the in-call UI.
- *
- * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
- * otherwise.
- * @param force {@code true} if fullscreen mode should be set regardless of its current state.
- */
- public void setFullScreen(boolean isFullScreen, boolean force) {
- Log.v(this, "setFullScreen = " + isFullScreen);
-
- // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
- if (isDialpadVisible()) {
- isFullScreen = false;
- Log.v(this, "setFullScreen overridden as dialpad is shown = " + isFullScreen);
- }
-
- if (mIsFullScreen == isFullScreen && !force) {
- Log.v(this, "setFullScreen ignored as already in that state.");
- return;
- }
- mIsFullScreen = isFullScreen;
- notifyFullscreenModeChange(mIsFullScreen);
- }
-
- /**
- * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false}
- * otherwise.
- */
- public boolean isFullscreen() {
- return mIsFullScreen;
- }
-
-
- /**
- * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status.
- *
- * @param isFullscreenMode {@code True} if entering full screen mode.
- */
- public void notifyFullscreenModeChange(boolean isFullscreenMode) {
- for (InCallEventListener listener : mInCallEventListeners) {
- listener.onFullscreenModeChanged(isFullscreenMode);
- }
- }
-
- /**
- * Called by the {@link CallCardPresenter} to inform of a change in visibility of the secondary
- * caller info bar.
- *
- * @param isVisible {@code true} if the secondary caller info is visible, {@code false}
- * otherwise.
- * @param height the height of the secondary caller info bar.
- */
- public void notifySecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
- for (InCallEventListener listener : mInCallEventListeners) {
- listener.onSecondaryCallerInfoVisibilityChanged(isVisible, height);
- }
- }
-
-
- /**
- * For some disconnected causes, we show a dialog. This calls into the activity to show
- * the dialog if appropriate for the call.
- */
- private void maybeShowErrorDialogOnDisconnect(Call call) {
- // For newly disconnected calls, we may want to show a dialog on specific error conditions
- if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) {
- if (call.getAccountHandle() == null && !call.isConferenceCall()) {
- setDisconnectCauseForMissingAccounts(call);
- }
- mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause());
- }
- }
-
- /**
- * When the state of in-call changes, this is the first method to get called. It determines if
- * the UI needs to be started or finished depending on the new state and does it.
- */
- private InCallState startOrFinishUi(InCallState newState) {
- Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState);
-
- // TODO: Consider a proper state machine implementation
-
- // If the state isn't changing we have already done any starting/stopping of activities in
- // a previous pass...so lets cut out early
- if (newState == mInCallState) {
- return newState;
- }
-
- // A new Incoming call means that the user needs to be notified of the the call (since
- // it wasn't them who initiated it). We do this through full screen notifications and
- // happens indirectly through {@link StatusBarNotifier}.
- //
- // The process for incoming calls is as follows:
- //
- // 1) CallList - Announces existence of new INCOMING call
- // 2) InCallPresenter - Gets announcement and calculates that the new InCallState
- // - should be set to INCOMING.
- // 3) InCallPresenter - This method is called to see if we need to start or finish
- // the app given the new state.
- // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls
- // StatusBarNotifier explicitly to issue a FullScreen Notification
- // that will either start the InCallActivity or show the user a
- // top-level notification dialog if the user is in an immersive app.
- // That notification can also start the InCallActivity.
- // 5) InCallActivity - Main activity starts up and at the end of its onCreate will
- // call InCallPresenter::setActivity() to let the presenter
- // know that start-up is complete.
- //
- // [ AND NOW YOU'RE IN THE CALL. voila! ]
- //
- // Our app is started using a fullScreen notification. We need to do this whenever
- // we get an incoming call. Depending on the current context of the device, either a
- // incoming call HUN or the actual InCallActivity will be shown.
- final boolean startIncomingCallSequence = (InCallState.INCOMING == newState);
-
- // A dialog to show on top of the InCallUI to select a PhoneAccount
- final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
-
- // A new outgoing call indicates that the user just now dialed a number and when that
- // happens we need to display the screen immediately or show an account picker dialog if
- // no default is set. However, if the main InCallUI is already visible, we do not want to
- // re-initiate the start-up animation, so we do not need to do anything here.
- //
- // It is also possible to go into an intermediate state where the call has been initiated
- // but Telecom has not yet returned with the details of the call (handle, gateway, etc.).
- // This pending outgoing state can also launch the call screen.
- //
- // This is different from the incoming call sequence because we do not need to shock the
- // user with a top-level notification. Just show the call UI normally.
- final boolean mainUiNotVisible = !isShowingInCallUi() || !getCallCardFragmentVisible();
- boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible;
-
- // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the
- // outgoing call process, so the UI should be brought up to show an error dialog.
- showCallUi |= (InCallState.PENDING_OUTGOING == mInCallState
- && InCallState.INCALL == newState && !isShowingInCallUi());
-
- // Another exception - InCallActivity is in charge of disconnecting a call with no
- // valid accounts set. Bring the UI up if this is true for the current pending outgoing
- // call so that:
- // 1) The call can be disconnected correctly
- // 2) The UI comes up and correctly displays the error dialog.
- // TODO: Remove these special case conditions by making InCallPresenter a true state
- // machine. Telecom should also be the component responsible for disconnecting a call
- // with no valid accounts.
- showCallUi |= InCallState.PENDING_OUTGOING == newState && mainUiNotVisible
- && isCallWithNoValidAccounts(mCallList.getPendingOutgoingCall());
-
- // The only time that we have an instance of mInCallActivity and it isn't started is
- // when it is being destroyed. In that case, lets avoid bringing up another instance of
- // the activity. When it is finally destroyed, we double check if we should bring it back
- // up so we aren't going to lose anything by avoiding a second startup here.
- boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted();
- if (activityIsFinishing) {
- Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState);
- return mInCallState;
- }
-
- if (showCallUi || showAccountPicker) {
- Log.i(this, "Start in call UI");
- showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
- } else if (startIncomingCallSequence) {
- Log.i(this, "Start Full Screen in call UI");
-
- // We're about the bring up the in-call UI for an incoming call. If we still have
- // dialogs up, we need to clear them out before showing incoming screen.
- if (isActivityStarted()) {
- mInCallActivity.dismissPendingDialogs();
- }
- if (!startUi(newState)) {
- // startUI refused to start the UI. This indicates that it needed to restart the
- // activity. When it finally restarts, it will call us back, so we do not actually
- // change the state yet (we return mInCallState instead of newState).
- return mInCallState;
- }
- } else if (newState == InCallState.NO_CALLS) {
- // The new state is the no calls state. Tear everything down.
- attemptFinishActivity();
- attemptCleanup();
- }
-
- return newState;
- }
-
- /**
- * Determines whether or not a call has no valid phone accounts that can be used to make the
- * call with. Emergency calls do not require a phone account.
- *
- * @param call to check accounts for.
- * @return {@code true} if the call has no call capable phone accounts set, {@code false} if
- * the call contains a phone account that could be used to initiate it with, or is an emergency
- * call.
- */
- public static boolean isCallWithNoValidAccounts(Call call) {
- if (call != null && !call.isEmergencyCall()) {
- Bundle extras = call.getIntentExtras();
-
- if (extras == null) {
- extras = EMPTY_EXTRAS;
- }
-
- final List<PhoneAccountHandle> phoneAccountHandles = extras
- .getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
-
- if ((call.getAccountHandle() == null &&
- (phoneAccountHandles == null || phoneAccountHandles.isEmpty()))) {
- Log.i(InCallPresenter.getInstance(), "No valid accounts for call " + call);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Sets the DisconnectCause for a call that was disconnected because it was missing a
- * PhoneAccount or PhoneAccounts to select from.
- * @param call
- */
- private void setDisconnectCauseForMissingAccounts(Call call) {
- android.telecom.Call telecomCall = call.getTelecomCall();
-
- Bundle extras = telecomCall.getDetails().getIntentExtras();
- // Initialize the extras bundle to avoid NPE
- if (extras == null) {
- extras = new Bundle();
- }
-
- final List<PhoneAccountHandle> phoneAccountHandles = extras.getParcelableArrayList(
- android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
-
- if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) {
- String scheme = telecomCall.getDetails().getHandle().getScheme();
- final String errorMsg = PhoneAccount.SCHEME_TEL.equals(scheme) ?
- mContext.getString(R.string.callFailed_simError) :
- mContext.getString(R.string.incall_error_supp_service_unknown);
- DisconnectCause disconnectCause =
- new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg);
- call.setDisconnectCause(disconnectCause);
- }
- }
-
- private boolean startUi(InCallState inCallState) {
- boolean isCallWaiting = mCallList.getActiveCall() != null &&
- mCallList.getIncomingCall() != null;
-
- // If the screen is off, we need to make sure it gets turned on for incoming calls.
- // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
- // when the activity is first created. Therefore, to ensure the screen is turned on
- // for the call waiting case, we finish() the current activity and start a new one.
- // There should be no jank from this since the screen is already off and will remain so
- // until our new activity is up.
-
- if (isCallWaiting) {
- if (mProximitySensor.isScreenReallyOff() && isActivityStarted()) {
- Log.i(this, "Restarting InCallActivity to turn screen on for call waiting");
- mInCallActivity.finish();
- // When the activity actually finishes, we will start it again if there are
- // any active calls, so we do not need to start it explicitly here. Note, we
- // actually get called back on this function to restart it.
-
- // We return false to indicate that we did not actually start the UI.
- return false;
- } else {
- showInCall(false, false);
- }
- } else {
- mStatusBarNotifier.updateNotification(inCallState, mCallList);
- }
- return true;
- }
-
- /**
- * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all
- * down.
- */
- private void attemptCleanup() {
- boolean shouldCleanup = (mInCallActivity == null && !mServiceConnected &&
- mInCallState == InCallState.NO_CALLS);
- Log.i(this, "attemptCleanup? " + shouldCleanup);
-
- if (shouldCleanup) {
- mIsActivityPreviouslyStarted = false;
- mIsChangingConfigurations = false;
-
- // blow away stale contact info so that we get fresh data on
- // the next set of calls
- if (mContactInfoCache != null) {
- mContactInfoCache.clearCache();
- }
- mContactInfoCache = null;
-
- if (mProximitySensor != null) {
- removeListener(mProximitySensor);
- mProximitySensor.tearDown();
- }
- mProximitySensor = null;
-
- mAudioModeProvider = null;
-
- if (mStatusBarNotifier != null) {
- removeListener(mStatusBarNotifier);
- }
- if (mExternalCallNotifier != null && mExternalCallList != null) {
- mExternalCallList.removeExternalCallListener(mExternalCallNotifier);
- }
- mStatusBarNotifier = null;
-
- if (mCallList != null) {
- mCallList.removeListener(this);
- mCallList.removeListener(mSpamCallListListener);
- }
- mCallList = null;
-
- mContext = null;
- mInCallActivity = null;
-
- mListeners.clear();
- mIncomingCallListeners.clear();
- mDetailsListeners.clear();
- mCanAddCallListeners.clear();
- mOrientationListeners.clear();
- mInCallEventListeners.clear();
-
- Log.d(this, "Finished InCallPresenter.CleanUp");
- }
- }
-
- public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) {
- Log.i(this, "Showing InCallActivity");
- mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall));
- }
-
- public void onServiceBind() {
- mServiceBound = true;
- }
-
- public void onServiceUnbind() {
- InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
- mServiceBound = false;
- }
-
- public boolean isServiceBound() {
- return mServiceBound;
- }
-
- public void maybeStartRevealAnimation(Intent intent) {
- if (intent == null || mInCallActivity != null) {
- return;
- }
- final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
- if (extras == null) {
- // Incoming call, just show the in-call UI directly.
- return;
- }
-
- if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
- // Account selection dialog will show up so don't show the animation.
- return;
- }
-
- final PhoneAccountHandle accountHandle =
- intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
-
- InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
-
- final Intent incallIntent = getInCallIntent(false, true);
- incallIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
- mContext.startActivity(incallIntent);
- }
-
- public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) {
- final Intent intent = new Intent(Intent.ACTION_MAIN, null);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
-
- intent.setClass(mContext, InCallActivity.class);
- if (showDialpad) {
- intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true);
- }
- intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall);
- return intent;
- }
-
- /**
- * Retrieves the current in-call camera manager instance, creating if necessary.
- *
- * @return The {@link InCallCameraManager}.
- */
- public InCallCameraManager getInCallCameraManager() {
- synchronized(this) {
- if (mInCallCameraManager == null) {
- mInCallCameraManager = new InCallCameraManager(mContext);
- }
-
- return mInCallCameraManager;
- }
- }
-
- /**
- * Notifies listeners of changes in orientation and notify calls of rotation angle change.
- *
- * @param orientation The screen orientation of the device (one of:
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
- */
- public void onDeviceOrientationChange(int orientation) {
- Log.d(this, "onDeviceOrientationChange: orientation= " + orientation);
-
- if (mCallList != null) {
- mCallList.notifyCallsOfDeviceRotation(orientation);
- } else {
- Log.w(this, "onDeviceOrientationChange: CallList is null.");
- }
-
- // Notify listeners of device orientation changed.
- for (InCallOrientationListener listener : mOrientationListeners) {
- listener.onDeviceOrientationChanged(orientation);
- }
- }
-
- /**
- * Configures the in-call UI activity so it can change orientations or not. Enables the
- * orientation event listener if allowOrientationChange is true, disables it if false.
- *
- * @param allowOrientationChange {@code True} if the in-call UI can change between portrait
- * and landscape. {@Code False} if the in-call UI should be locked in portrait.
- */
- public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
- if (mInCallActivity == null) {
- Log.e(this, "InCallActivity is null. Can't set requested orientation.");
- return;
- }
-
- if (!allowOrientationChange) {
- mInCallActivity.setRequestedOrientation(
- InCallOrientationEventListener.NO_SENSOR_SCREEN_ORIENTATION);
- } else {
- // Using SCREEN_ORIENTATION_FULL_SENSOR allows for reverse-portrait orientation, where
- // SCREEN_ORIENTATION_SENSOR does not.
- mInCallActivity.setRequestedOrientation(
- InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
- }
- mInCallActivity.enableInCallOrientationEventListener(allowOrientationChange);
- }
-
- public void enableScreenTimeout(boolean enable) {
- Log.v(this, "enableScreenTimeout: value=" + enable);
- if (mInCallActivity == null) {
- Log.e(this, "enableScreenTimeout: InCallActivity is null.");
- return;
- }
-
- final Window window = mInCallActivity.getWindow();
- if (enable) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- }
-
- /**
- * Returns the space available beside the call card.
- *
- * @return The space beside the call card.
- */
- public float getSpaceBesideCallCard() {
- if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null) {
- return mInCallActivity.getCallCardFragment().getSpaceBesideCallCard();
- }
- return 0;
- }
-
- /**
- * Returns whether the call card fragment is currently visible.
- *
- * @return True if the call card fragment is visible.
- */
- public boolean getCallCardFragmentVisible() {
- if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null) {
- return mInCallActivity.getCallCardFragment().isVisible();
- }
- return false;
- }
-
- /**
- * Hides or shows the conference manager fragment.
- *
- * @param show {@code true} if the conference manager should be shown, {@code false} if it
- * should be hidden.
- */
- public void showConferenceCallManager(boolean show) {
- if (mInCallActivity == null) {
- return;
- }
-
- mInCallActivity.showConferenceFragment(show);
- }
-
- /**
- * Determines if the dialpad is visible.
- *
- * @return {@code true} if the dialpad is visible, {@code false} otherwise.
- */
- public boolean isDialpadVisible() {
- if (mInCallActivity == null) {
- return false;
- }
- return mInCallActivity.isDialpadVisible();
- }
-
- /**
- * @return True if the application is currently running in a right-to-left locale.
- */
- public static boolean isRtl() {
- return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
- View.LAYOUT_DIRECTION_RTL;
- }
-
- /**
- * Extract background color from call object. The theme colors will include a primary color
- * and a secondary color.
- */
- public void setThemeColors() {
- // This method will set the background to default if the color is PhoneAccount.NO_COLOR.
- mThemeColors = getColorsFromCall(mCallList.getFirstCall());
-
- if (mInCallActivity == null) {
- return;
- }
-
- final Resources resources = mInCallActivity.getResources();
- final int color;
- if (resources.getBoolean(R.bool.is_layout_landscape)) {
- // TODO use ResourcesCompat.getColor(Resources, int, Resources.Theme) when available
- // {@link Resources#getColor(int)} used for compatibility
- color = resources.getColor(R.color.statusbar_background_color);
- } else {
- color = mThemeColors.mSecondaryColor;
- }
-
- mInCallActivity.getWindow().setStatusBarColor(color);
- final TaskDescription td = new TaskDescription(
- resources.getString(R.string.notification_ongoing_call), null, color);
- mInCallActivity.setTaskDescription(td);
- }
-
- /**
- * @return A palette for colors to display in the UI.
- */
- public MaterialPalette getThemeColors() {
- return mThemeColors;
- }
-
- private MaterialPalette getColorsFromCall(Call call) {
- if (call == null) {
- return getColorsFromPhoneAccountHandle(mPendingPhoneAccountHandle);
- } else {
- if (call.isSpam()) {
- Resources resources = mContext.getResources();
- return new InCallUIMaterialColorMapUtils(
- resources).calculatePrimaryAndSecondaryColor(
- resources.getColor(R.color.incall_call_spam_background_color));
- } else {
- return getColorsFromPhoneAccountHandle(call.getAccountHandle());
- }
- }
- }
-
- private MaterialPalette getColorsFromPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
- int highlightColor = PhoneAccount.NO_HIGHLIGHT_COLOR;
- if (phoneAccountHandle != null) {
- final TelecomManager tm = getTelecomManager();
-
- if (tm != null) {
- final PhoneAccount account =
- TelecomManagerCompat.getPhoneAccount(tm, phoneAccountHandle);
- // For single-sim devices, there will be no selected highlight color, so the phone
- // account will default to NO_HIGHLIGHT_COLOR.
- if (account != null && CompatUtils.isLollipopMr1Compatible()) {
- highlightColor = account.getHighlightColor();
- }
- }
- }
- return new InCallUIMaterialColorMapUtils(
- mContext.getResources()).calculatePrimaryAndSecondaryColor(highlightColor);
- }
-
- /**
- * @return An instance of TelecomManager.
- */
- public TelecomManager getTelecomManager() {
- if (mTelecomManager == null) {
- mTelecomManager = (TelecomManager)
- mContext.getSystemService(Context.TELECOM_SERVICE);
- }
- return mTelecomManager;
- }
-
- /**
- * @return An instance of TelephonyManager
- */
- public TelephonyManager getTelephonyManager() {
- return mTelephonyManager;
- }
-
- InCallActivity getActivity() {
- return mInCallActivity;
- }
-
- AnswerPresenter getAnswerPresenter() {
- return mAnswerPresenter;
- }
-
- ExternalCallNotifier getExternalCallNotifier() {
- return mExternalCallNotifier;
- }
-
- /**
- * Private constructor. Must use getInstance() to get this singleton.
- */
- private InCallPresenter() {
- }
-
- /**
- * All the main states of InCallActivity.
- */
- public enum InCallState {
- // InCall Screen is off and there are no calls
- NO_CALLS,
-
- // Incoming-call screen is up
- INCOMING,
-
- // In-call experience is showing
- INCALL,
-
- // Waiting for user input before placing outgoing call
- WAITING_FOR_ACCOUNT,
-
- // UI is starting up but no call has been initiated yet.
- // The UI is waiting for Telecom to respond.
- PENDING_OUTGOING,
-
- // User is dialing out
- OUTGOING;
-
- public boolean isIncoming() {
- return (this == INCOMING);
- }
-
- public boolean isConnectingOrConnected() {
- return (this == INCOMING ||
- this == OUTGOING ||
- this == INCALL);
- }
- }
-
- /**
- * Interface implemented by classes that need to know about the InCall State.
- */
- public interface InCallStateListener {
- // TODO: Enhance state to contain the call objects instead of passing CallList
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList);
- }
-
- public interface IncomingCallListener {
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call);
- }
-
- public interface CanAddCallListener {
- public void onCanAddCallChanged(boolean canAddCall);
- }
-
- public interface InCallDetailsListener {
- public void onDetailsChanged(Call call, android.telecom.Call.Details details);
- }
-
- public interface InCallOrientationListener {
- public void onDeviceOrientationChanged(int orientation);
- }
-
- /**
- * Interface implemented by classes that need to know about events which occur within the
- * In-Call UI. Used as a means of communicating between fragments that make up the UI.
- */
- public interface InCallEventListener {
- public void onFullscreenModeChanged(boolean isFullscreenMode);
- public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
- }
-
- public interface InCallUiListener {
- void onUiShowing(boolean showing);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallServiceImpl.java b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
deleted file mode 100644
index 1414bc51d..000000000
--- a/InCallUI/src/com/android/incallui/InCallServiceImpl.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2014 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.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.telecom.Call;
-import android.telecom.CallAudioState;
-import android.telecom.InCallService;
-
-/**
- * Used to receive updates about calls from the Telecom component. This service is bound to
- * Telecom while there exist calls which potentially require UI. This includes ringing (incoming),
- * dialing (outgoing), and active calls. When the last call is disconnected, Telecom will unbind to
- * the service triggering InCallActivity (via CallList) to finish soon after.
- */
-public class InCallServiceImpl extends InCallService {
-
- @Override
- public void onCallAudioStateChanged(CallAudioState audioState) {
- AudioModeProvider.getInstance().onAudioStateChanged(audioState.isMuted(),
- audioState.getRoute(), audioState.getSupportedRouteMask());
- }
-
- @Override
- public void onBringToForeground(boolean showDialpad) {
- InCallPresenter.getInstance().onBringToForeground(showDialpad);
- }
-
- @Override
- public void onCallAdded(Call call) {
- InCallPresenter.getInstance().onCallAdded(call);
- }
-
- @Override
- public void onCallRemoved(Call call) {
- InCallPresenter.getInstance().onCallRemoved(call);
- }
-
- @Override
- public void onCanAddCallChanged(boolean canAddCall) {
- InCallPresenter.getInstance().onCanAddCallChanged(canAddCall);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- final Context context = getApplicationContext();
- final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context);
- InCallPresenter.getInstance().setUp(
- getApplicationContext(),
- CallList.getInstance(),
- new ExternalCallList(),
- AudioModeProvider.getInstance(),
- new StatusBarNotifier(context, contactInfoCache),
- new ExternalCallNotifier(context, contactInfoCache),
- contactInfoCache,
- new ProximitySensor(
- context,
- AudioModeProvider.getInstance(),
- new AccelerometerListener(context))
- );
- InCallPresenter.getInstance().onServiceBind();
- InCallPresenter.getInstance().maybeStartRevealAnimation(intent);
- TelecomAdapter.getInstance().setInCallService(this);
-
- return super.onBind(intent);
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- super.onUnbind(intent);
-
- InCallPresenter.getInstance().onServiceUnbind();
- tearDown();
-
- return false;
- }
-
- private void tearDown() {
- Log.v(this, "tearDown");
- // Tear down the InCall system
- TelecomAdapter.getInstance().clearInCallService();
- InCallPresenter.getInstance().tearDown();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallServiceListener.java b/InCallUI/src/com/android/incallui/InCallServiceListener.java
deleted file mode 100644
index 11a5b08ef..000000000
--- a/InCallUI/src/com/android/incallui/InCallServiceListener.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2014 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.telecom.InCallService;
-
-/**
- * Interface implemented by In-Call components that maintain a reference to the Telecom API
- * {@code InCallService} object. Clarifies the expectations associated with the relevant method
- * calls.
- */
-public interface InCallServiceListener {
-
- /**
- * Called once at {@code InCallService} startup time with a valid instance. At
- * that time, there will be no existing {@code Call}s.
- *
- * @param inCallService The {@code InCallService} object.
- */
- void setInCallService(InCallService inCallService);
-
- /**
- * Called once at {@code InCallService} shutdown time. At that time, any {@code Call}s
- * will have transitioned through the disconnected state and will no longer exist.
- */
- void clearInCallService();
-}
diff --git a/InCallUI/src/com/android/incallui/InCallUIMaterialColorMapUtils.java b/InCallUI/src/com/android/incallui/InCallUIMaterialColorMapUtils.java
deleted file mode 100644
index 9c108b855..000000000
--- a/InCallUI/src/com/android/incallui/InCallUIMaterialColorMapUtils.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.android.incallui;
-
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.telecom.PhoneAccount;
-
-import com.android.contacts.common.util.MaterialColorMapUtils;
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-import com.android.dialer.R;
-
-public class InCallUIMaterialColorMapUtils extends MaterialColorMapUtils {
- private final TypedArray sPrimaryColors;
- private final TypedArray sSecondaryColors;
- private final Resources mResources;
-
- public InCallUIMaterialColorMapUtils(Resources resources) {
- super(resources);
- sPrimaryColors = resources.obtainTypedArray(R.array.background_colors);
- sSecondaryColors = resources.obtainTypedArray(R.array.background_colors_dark);
- mResources = resources;
- }
-
- /**
- * Currently the InCallUI color will only vary by SIM color which is a list of colors
- * defined in the background_colors array, so first search the list for the matching color and
- * fall back to the closest matching color if an exact match does not exist.
- */
- @Override
- public MaterialPalette calculatePrimaryAndSecondaryColor(int color) {
- if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) {
- return getDefaultPrimaryAndSecondaryColors(mResources);
- }
-
- for (int i = 0; i < sPrimaryColors.length(); i++) {
- if (sPrimaryColors.getColor(i, 0) == color) {
- return new MaterialPalette(
- sPrimaryColors.getColor(i, 0),
- sSecondaryColors.getColor(i, 0));
- }
- }
-
- // The color isn't in the list, so use the superclass to find an approximate color.
- return super.calculatePrimaryAndSecondaryColor(color);
- }
-
- /**
- * {@link Resources#getColor(int) used for compatibility
- */
- @SuppressWarnings("deprecation")
- public static MaterialPalette getDefaultPrimaryAndSecondaryColors(Resources resources) {
- final int primaryColor = resources.getColor(R.color.dialer_theme_color);
- final int secondaryColor = resources.getColor(R.color.dialer_theme_color_dark);
- return new MaterialPalette(primaryColor, secondaryColor);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
deleted file mode 100644
index 99e6d5129..000000000
--- a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2014 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.telecom.Connection;
-import android.telecom.Connection.VideoProvider;
-import android.telecom.InCallService.VideoCall;
-import android.telecom.VideoProfile;
-import android.telecom.VideoProfile.CameraCapabilities;
-
-/**
- * Implements the InCallUI VideoCall Callback.
- */
-public class InCallVideoCallCallback extends VideoCall.Callback {
-
- /**
- * The call associated with this {@link InCallVideoCallCallback}.
- */
- private Call mCall;
-
- /**
- * Creates an instance of the call video client, specifying the call it is related to.
- *
- * @param call The call.
- */
- public InCallVideoCallCallback(Call call) {
- mCall = call;
- }
-
- /**
- * Handles an incoming session modification request.
- *
- * @param videoProfile The requested video call profile.
- */
- @Override
- public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
- Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile);
- int previousVideoState = VideoUtils.getUnPausedVideoState(mCall.getVideoState());
- int newVideoState = VideoUtils.getUnPausedVideoState(videoProfile.getVideoState());
-
- boolean wasVideoCall = VideoUtils.isVideoCall(previousVideoState);
- boolean isVideoCall = VideoUtils.isVideoCall(newVideoState);
-
- // Check for upgrades to video.
- if (!wasVideoCall && isVideoCall && previousVideoState != newVideoState) {
- InCallVideoCallCallbackNotifier.getInstance().upgradeToVideoRequest(mCall,
- newVideoState);
- }
- }
-
- /**
- * Handles a session modification response.
- *
- * @param status Status of the session modify request. Valid values are
- * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
- * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
- * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
- * @param requestedProfile
- * @param responseProfile The actual profile changes made by the peer device.
- */
- @Override
- public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile,
- VideoProfile responseProfile) {
- Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile="
- + requestedProfile + " responseProfile=" + responseProfile);
- if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
- // Report the reason the upgrade failed as the new session modification state.
- if (status == VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT) {
- mCall.setSessionModificationState(
- Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT);
- } else {
- if (status == VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE) {
- mCall.setSessionModificationState(
- Call.SessionModificationState.REQUEST_REJECTED);
- } else {
- mCall.setSessionModificationState(
- Call.SessionModificationState.REQUEST_FAILED);
- }
- }
- }
-
- // Finally clear the outstanding request.
- mCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
-
- /**
- * Handles a call session event.
- *
- * @param event The event.
- */
- @Override
- public void onCallSessionEvent(int event) {
- InCallVideoCallCallbackNotifier.getInstance().callSessionEvent(event);
- }
-
- /**
- * Handles a change to the peer video dimensions.
- *
- * @param width The updated peer video width.
- * @param height The updated peer video height.
- */
- @Override
- public void onPeerDimensionsChanged(int width, int height) {
- InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(mCall, width, height);
- }
-
- /**
- * Handles a change to the video quality of the call.
- *
- * @param videoQuality The updated video call quality.
- */
- @Override
- public void onVideoQualityChanged(int videoQuality) {
- InCallVideoCallCallbackNotifier.getInstance().videoQualityChanged(mCall, videoQuality);
- }
-
- /**
- * Handles a change to the call data usage. No implementation as the in-call UI does not
- * display data usage.
- *
- * @param dataUsage The updated data usage.
- */
- @Override
- public void onCallDataUsageChanged(long dataUsage) {
- Log.d(this, "onCallDataUsageChanged: dataUsage = " + dataUsage);
- InCallVideoCallCallbackNotifier.getInstance().callDataUsageChanged(dataUsage);
- }
-
- /**
- * Handles changes to the camera capabilities. No implementation as the in-call UI does not
- * make use of camera capabilities.
- *
- * @param cameraCapabilities The changed camera capabilities.
- */
- @Override
- public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) {
- if (cameraCapabilities != null) {
- InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(
- mCall, cameraCapabilities.getWidth(), cameraCapabilities.getHeight());
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java b/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java
deleted file mode 100644
index bb7529205..000000000
--- a/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2014 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 com.google.common.base.Preconditions;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Class used by {@link InCallService.VideoCallCallback} to notify interested parties of incoming
- * events.
- */
-public class InCallVideoCallCallbackNotifier {
- /**
- * Singleton instance of this class.
- */
- private static InCallVideoCallCallbackNotifier sInstance =
- new InCallVideoCallCallbackNotifier();
-
- /**
- * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
- * load factor before resizing, 1 means we only expect a single thread to
- * access the map so make only a single shard
- */
- private final Set<SessionModificationListener> mSessionModificationListeners =
- Collections.newSetFromMap(new ConcurrentHashMap<SessionModificationListener, Boolean>
- (8, 0.9f, 1));
- private final Set<VideoEventListener> mVideoEventListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<VideoEventListener, Boolean>(8, 0.9f, 1));
- private final Set<SurfaceChangeListener> mSurfaceChangeListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<SurfaceChangeListener, Boolean>(8, 0.9f, 1));
-
- /**
- * Static singleton accessor method.
- */
- public static InCallVideoCallCallbackNotifier getInstance() {
- return sInstance;
- }
-
- /**
- * Private constructor. Instance should only be acquired through getInstance().
- */
- private InCallVideoCallCallbackNotifier() {
- }
-
- /**
- * Adds a new {@link SessionModificationListener}.
- *
- * @param listener The listener.
- */
- public void addSessionModificationListener(SessionModificationListener listener) {
- Preconditions.checkNotNull(listener);
- mSessionModificationListeners.add(listener);
- }
-
- /**
- * Remove a {@link SessionModificationListener}.
- *
- * @param listener The listener.
- */
- public void removeSessionModificationListener(SessionModificationListener listener) {
- if (listener != null) {
- mSessionModificationListeners.remove(listener);
- }
- }
-
- /**
- * Adds a new {@link VideoEventListener}.
- *
- * @param listener The listener.
- */
- public void addVideoEventListener(VideoEventListener listener) {
- Preconditions.checkNotNull(listener);
- mVideoEventListeners.add(listener);
- }
-
- /**
- * Remove a {@link VideoEventListener}.
- *
- * @param listener The listener.
- */
- public void removeVideoEventListener(VideoEventListener listener) {
- if (listener != null) {
- mVideoEventListeners.remove(listener);
- }
- }
-
- /**
- * Adds a new {@link SurfaceChangeListener}.
- *
- * @param listener The listener.
- */
- public void addSurfaceChangeListener(SurfaceChangeListener listener) {
- Preconditions.checkNotNull(listener);
- mSurfaceChangeListeners.add(listener);
- }
-
- /**
- * Remove a {@link SurfaceChangeListener}.
- *
- * @param listener The listener.
- */
- public void removeSurfaceChangeListener(SurfaceChangeListener listener) {
- if (listener != null) {
- mSurfaceChangeListeners.remove(listener);
- }
- }
-
- /**
- * Inform listeners of an upgrade to video request for a call.
- * @param call The call.
- * @param videoState The video state we want to upgrade to.
- */
- public void upgradeToVideoRequest(Call call, int videoState) {
- Log.d(this, "upgradeToVideoRequest call = " + call + " new video state = " + videoState);
- for (SessionModificationListener listener : mSessionModificationListeners) {
- listener.onUpgradeToVideoRequest(call, videoState);
- }
- }
-
- /**
- * Inform listeners of a call session event.
- *
- * @param event The call session event.
- */
- public void callSessionEvent(int event) {
- for (VideoEventListener listener : mVideoEventListeners) {
- listener.onCallSessionEvent(event);
- }
- }
-
- /**
- * Inform listeners of a downgrade to audio.
- *
- * @param call The call.
- * @param paused The paused state.
- */
- public void peerPausedStateChanged(Call call, boolean paused) {
- for (VideoEventListener listener : mVideoEventListeners) {
- listener.onPeerPauseStateChanged(call, paused);
- }
- }
-
- /**
- * Inform listeners of any change in the video quality of the call
- *
- * @param call The call.
- * @param videoQuality The updated video quality of the call.
- */
- public void videoQualityChanged(Call call, int videoQuality) {
- for (VideoEventListener listener : mVideoEventListeners) {
- listener.onVideoQualityChanged(call, videoQuality);
- }
- }
-
- /**
- * Inform listeners of a change to peer dimensions.
- *
- * @param call The call.
- * @param width New peer width.
- * @param height New peer height.
- */
- public void peerDimensionsChanged(Call call, int width, int height) {
- for (SurfaceChangeListener listener : mSurfaceChangeListeners) {
- listener.onUpdatePeerDimensions(call, width, height);
- }
- }
-
- /**
- * Inform listeners of a change to camera dimensions.
- *
- * @param call The call.
- * @param width The new camera video width.
- * @param height The new camera video height.
- */
- public void cameraDimensionsChanged(Call call, int width, int height) {
- for (SurfaceChangeListener listener : mSurfaceChangeListeners) {
- listener.onCameraDimensionsChange(call, width, height);
- }
- }
-
- /**
- * Inform listeners of a change to call data usage.
- *
- * @param dataUsage data usage value
- */
- public void callDataUsageChanged(long dataUsage) {
- for (VideoEventListener listener : mVideoEventListeners) {
- listener.onCallDataUsageChange(dataUsage);
- }
- }
-
- /**
- * Listener interface for any class that wants to be notified of upgrade to video request.
- */
- public interface SessionModificationListener {
- /**
- * Called when a peer request is received to upgrade an audio-only call to a video call.
- *
- * @param call The call the request was received for.
- * @param videoState The requested video state.
- */
- public void onUpgradeToVideoRequest(Call call, int videoState);
- }
-
- /**
- * Listener interface for any class that wants to be notified of video events, including pause
- * and un-pause of peer video, video quality changes.
- */
- public interface VideoEventListener {
- /**
- * Called when the peer pauses or un-pauses video transmission.
- *
- * @param call The call which paused or un-paused video transmission.
- * @param paused {@code True} when the video transmission is paused, {@code false}
- * otherwise.
- */
- public void onPeerPauseStateChanged(Call call, boolean paused);
-
- /**
- * Called when the video quality changes.
- *
- * @param call The call whose video quality changes.
- * @param videoCallQuality - values are QUALITY_HIGH, MEDIUM, LOW and UNKNOWN.
- */
- public void onVideoQualityChanged(Call call, int videoCallQuality);
-
- /*
- * Called when call data usage value is requested or when call data usage value is updated
- * because of a call state change
- *
- * @param dataUsage call data usage value
- */
- public void onCallDataUsageChange(long dataUsage);
-
- /**
- * Called when call session event is raised.
- *
- * @param event The call session event.
- */
- public void onCallSessionEvent(int event);
- }
-
- /**
- * Listener interface for any class that wants to be notified of changes to the video surfaces.
- */
- public interface SurfaceChangeListener {
- /**
- * Called when the peer video feed changes dimensions. This can occur when the peer rotates
- * their device, changing the aspect ratio of the video signal.
- *
- * @param call The call which experienced a peer video
- * @param width
- * @param height
- */
- public void onUpdatePeerDimensions(Call call, int width, int height);
-
- /**
- * Called when the local camera changes dimensions. This occurs when a change in camera
- * occurs.
- *
- * @param call The call which experienced the camera dimension change.
- * @param width The new camera video width.
- * @param height The new camera video height.
- */
- public void onCameraDimensionsChange(Call call, int width, int height);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/LatencyReport.java b/InCallUI/src/com/android/incallui/LatencyReport.java
deleted file mode 100644
index 655372a8f..000000000
--- a/InCallUI/src/com/android/incallui/LatencyReport.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.os.Bundle;
-import android.os.SystemClock;
-
-import com.android.incalluibind.ObjectFactory;
-
-/**
- * Tracks latency information for a call.
- */
-public class LatencyReport {
- // The following are hidden constants from android.telecom.TelecomManager.
- private static final String EXTRA_CALL_CREATED_TIME_MILLIS =
- "android.telecom.extra.CALL_CREATED_TIME_MILLIS";
- private static final String EXTRA_CALL_TELECOM_ROUTING_START_TIME_MILLIS =
- "android.telecom.extra.CALL_TELECOM_ROUTING_START_TIME_MILLIS";
- private static final String EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS =
- "android.telecom.extra.CALL_TELECOM_ROUTING_END_TIME_MILLIS";
-
- public static final long INVALID_TIME = -1;
-
- private final boolean mWasIncoming;
-
- // Time elapsed since boot when the call was created by the connection service.
- private final long mCreatedTimeMillis;
-
- // Time elapsed since boot when telecom began processing the call.
- private final long mTelecomRoutingStartTimeMillis;
-
- // Time elapsed since boot when telecom finished processing the call. This includes things like
- // looking up contact info and call blocking but before showing any UI.
- private final long mTelecomRoutingEndTimeMillis;
-
- // Time elapsed since boot when the call was added to the InCallUi.
- private final long mCallAddedTimeMillis;
-
- // Time elapsed since boot when the call was added and call blocking evaluation was completed.
- private long mCallBlockingTimeMillis = INVALID_TIME;
-
- // Time elapsed since boot when the call notification was shown.
- private long mCallNotificationTimeMillis = INVALID_TIME;
-
- // Time elapsed since boot when the InCallUI was shown.
- private long mInCallUiShownTimeMillis = INVALID_TIME;
-
- // Whether the call was shown to the user as a heads up notification instead of a full screen
- // UI.
- private boolean mDidDisplayHeadsUpNotification;
-
- public LatencyReport() {
- mWasIncoming = false;
- mCreatedTimeMillis = INVALID_TIME;
- mTelecomRoutingStartTimeMillis = INVALID_TIME;
- mTelecomRoutingEndTimeMillis = INVALID_TIME;
- mCallAddedTimeMillis = SystemClock.elapsedRealtime();
- }
-
- public LatencyReport(android.telecom.Call telecomCall) {
- mWasIncoming = telecomCall.getState() == android.telecom.Call.STATE_RINGING;
- Bundle extras = telecomCall.getDetails().getIntentExtras();
- if (extras == null) {
- mCreatedTimeMillis = INVALID_TIME;
- mTelecomRoutingStartTimeMillis = INVALID_TIME;
- mTelecomRoutingEndTimeMillis = INVALID_TIME;
- } else {
- mCreatedTimeMillis = extras.getLong(EXTRA_CALL_CREATED_TIME_MILLIS, INVALID_TIME);
- mTelecomRoutingStartTimeMillis = extras.getLong(
- EXTRA_CALL_TELECOM_ROUTING_START_TIME_MILLIS, INVALID_TIME);
- mTelecomRoutingEndTimeMillis = extras.getLong(
- EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS, INVALID_TIME);
- }
- mCallAddedTimeMillis = SystemClock.elapsedRealtime();
- }
-
- public boolean getWasIncoming() {
- return mWasIncoming;
- }
-
- public long getCreatedTimeMillis() {
- return mCreatedTimeMillis;
- }
-
- public long getTelecomRoutingStartTimeMillis() {
- return mTelecomRoutingStartTimeMillis;
- }
-
- public long getTelecomRoutingEndTimeMillis() {
- return mTelecomRoutingEndTimeMillis;
- }
-
- public long getCallAddedTimeMillis() {
- return mCallAddedTimeMillis;
- }
-
- public long getCallBlockingTimeMillis() {
- return mCallBlockingTimeMillis;
- }
-
- public void onCallBlockingDone() {
- if (mCallBlockingTimeMillis == INVALID_TIME) {
- mCallBlockingTimeMillis = SystemClock.elapsedRealtime();
- }
- }
-
- public long getCallNotificationTimeMillis() {
- return mCallNotificationTimeMillis;
- }
-
- public void onNotificationShown() {
- if (mCallNotificationTimeMillis == INVALID_TIME) {
- mCallNotificationTimeMillis = SystemClock.elapsedRealtime();
- }
- }
-
- public long getInCallUiShownTimeMillis() {
- return mInCallUiShownTimeMillis;
- }
-
- public void onInCallUiShown(boolean forFullScreenIntent) {
- if (mInCallUiShownTimeMillis == INVALID_TIME) {
- mInCallUiShownTimeMillis = SystemClock.elapsedRealtime();
- mDidDisplayHeadsUpNotification = mWasIncoming && !forFullScreenIntent;
- }
- }
-
- public boolean getDidDisplayHeadsUpNotification() {
- return mDidDisplayHeadsUpNotification;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/Log.java b/InCallUI/src/com/android/incallui/Log.java
deleted file mode 100644
index 07a0e61ca..000000000
--- a/InCallUI/src/com/android/incallui/Log.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2013 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.net.Uri;
-import android.telecom.PhoneAccount;
-import android.telephony.PhoneNumberUtils;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/**
- * Manages logging for the entire class.
- */
-public class Log {
-
- // Generic tag for all In Call logging
- public static final String TAG = "InCall";
-
- public static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
- public static final boolean DEBUG = FORCE_DEBUG ||
- android.util.Log.isLoggable(TAG, android.util.Log.DEBUG);
- public static final boolean VERBOSE = FORCE_DEBUG ||
- android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE);
- public static final String TAG_DELIMETER = " - ";
-
- public static void d(String tag, String msg) {
- if (DEBUG) {
- android.util.Log.d(TAG, delimit(tag) + msg);
- }
- }
-
- public static void d(Object obj, String msg) {
- if (DEBUG) {
- android.util.Log.d(TAG, getPrefix(obj) + msg);
- }
- }
-
- public static void d(Object obj, String str1, Object str2) {
- if (DEBUG) {
- android.util.Log.d(TAG, getPrefix(obj) + str1 + str2);
- }
- }
-
- public static void v(Object obj, String msg) {
- if (VERBOSE) {
- android.util.Log.v(TAG, getPrefix(obj) + msg);
- }
- }
-
- public static void v(Object obj, String str1, Object str2) {
- if (VERBOSE) {
- android.util.Log.d(TAG, getPrefix(obj) + str1 + str2);
- }
- }
-
- public static void e(String tag, String msg, Exception e) {
- android.util.Log.e(TAG, delimit(tag) + msg, e);
- }
-
- public static void e(String tag, String msg) {
- android.util.Log.e(TAG, delimit(tag) + msg);
- }
-
- public static void e(Object obj, String msg, Exception e) {
- android.util.Log.e(TAG, getPrefix(obj) + msg, e);
- }
-
- public static void e(Object obj, String msg) {
- android.util.Log.e(TAG, getPrefix(obj) + msg);
- }
-
- public static void i(String tag, String msg) {
- android.util.Log.i(TAG, delimit(tag) + msg);
- }
-
- public static void i(Object obj, String msg) {
- android.util.Log.i(TAG, getPrefix(obj) + msg);
- }
-
- public static void w(Object obj, String msg) {
- android.util.Log.w(TAG, getPrefix(obj) + msg);
- }
-
- public static void wtf(Object obj, String msg) {
- android.util.Log.wtf(TAG, getPrefix(obj) + msg);
- }
-
- public static String piiHandle(Object pii) {
- if (pii == null || VERBOSE) {
- return String.valueOf(pii);
- }
-
- if (pii instanceof Uri) {
- Uri uri = (Uri) pii;
-
- // All Uri's which are not "tel" go through normal pii() method.
- if (!PhoneAccount.SCHEME_TEL.equals(uri.getScheme())) {
- return pii(pii);
- } else {
- pii = uri.getSchemeSpecificPart();
- }
- }
-
- String originalString = String.valueOf(pii);
- StringBuilder stringBuilder = new StringBuilder(originalString.length());
- for (char c : originalString.toCharArray()) {
- if (PhoneNumberUtils.isDialable(c)) {
- stringBuilder.append('*');
- } else {
- stringBuilder.append(c);
- }
- }
- return stringBuilder.toString();
- }
-
- /**
- * Redact personally identifiable information for production users.
- * If we are running in verbose mode, return the original string, otherwise
- * return a SHA-1 hash of the input string.
- */
- public static String pii(Object pii) {
- if (pii == null || VERBOSE) {
- return String.valueOf(pii);
- }
- return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
- }
-
- private static String secureHash(byte[] input) {
- MessageDigest messageDigest;
- try {
- messageDigest = MessageDigest.getInstance("SHA-1");
- } catch (NoSuchAlgorithmException e) {
- return null;
- }
- messageDigest.update(input);
- byte[] result = messageDigest.digest();
- return encodeHex(result);
- }
-
- private static String encodeHex(byte[] bytes) {
- StringBuffer hex = new StringBuffer(bytes.length * 2);
-
- for (int i = 0; i < bytes.length; i++) {
- int byteIntValue = bytes[i] & 0xff;
- if (byteIntValue < 0x10) {
- hex.append("0");
- }
- hex.append(Integer.toString(byteIntValue, 16));
- }
-
- return hex.toString();
- }
-
- private static String getPrefix(Object obj) {
- return (obj == null ? "" : (obj.getClass().getSimpleName() + TAG_DELIMETER));
- }
-
- private static String delimit(String tag) {
- return tag + TAG_DELIMETER;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/NeededForReflection.java b/InCallUI/src/com/android/incallui/NeededForReflection.java
deleted file mode 100644
index 363a0a548..000000000
--- a/InCallUI/src/com/android/incallui/NeededForReflection.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2014 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 java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that the class, constructor, method or field is used for reflection and therefore cannot
- * be removed by tools like ProGuard.
- */
-@Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
-public @interface NeededForReflection {}
diff --git a/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java b/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java
deleted file mode 100644
index 27f71159d..000000000
--- a/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 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.VideoProfile;
-
-/**
- * Accepts broadcast Intents which will be prepared by {@link StatusBarNotifier} and thus
- * sent from the notification manager.
- * This should be visible from outside, but shouldn't be exported.
- */
-public class NotificationBroadcastReceiver extends BroadcastReceiver {
-
- /**
- * Intent Action used for hanging up the current call from Notification bar. This will
- * choose first ringing call, first active call, or first background call (typically in
- * STATE_HOLDING state).
- */
- public static final String ACTION_DECLINE_INCOMING_CALL =
- "com.android.incallui.ACTION_DECLINE_INCOMING_CALL";
- public static final String ACTION_HANG_UP_ONGOING_CALL =
- "com.android.incallui.ACTION_HANG_UP_ONGOING_CALL";
- public static final String ACTION_ANSWER_VIDEO_INCOMING_CALL =
- "com.android.incallui.ACTION_ANSWER_VIDEO_INCOMING_CALL";
- public static final String ACTION_ANSWER_VOICE_INCOMING_CALL =
- "com.android.incallui.ACTION_ANSWER_VOICE_INCOMING_CALL";
- public static final String ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST =
- "com.android.incallui.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST";
- public static final String ACTION_DECLINE_VIDEO_UPGRADE_REQUEST =
- "com.android.incallui.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST";
- public static final String ACTION_PULL_EXTERNAL_CALL =
- "com.android.incallui.ACTION_PULL_EXTERNAL_CALL";
- public static final String EXTRA_NOTIFICATION_ID =
- "com.android.incallui.extra.EXTRA_NOTIFICATION_ID";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- Log.i(this, "Broadcast from Notification: " + action);
-
- // TODO: Commands of this nature should exist in the CallList.
- if (action.equals(ACTION_ANSWER_VIDEO_INCOMING_CALL)) {
- InCallPresenter.getInstance().answerIncomingCall(
- context, VideoProfile.STATE_BIDIRECTIONAL);
- } else if (action.equals(ACTION_ANSWER_VOICE_INCOMING_CALL)) {
- InCallPresenter.getInstance().answerIncomingCall(
- context, VideoProfile.STATE_AUDIO_ONLY);
- } else if (action.equals(ACTION_DECLINE_INCOMING_CALL)) {
- InCallPresenter.getInstance().declineIncomingCall(context);
- } else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
- InCallPresenter.getInstance().hangUpOngoingCall(context);
- } else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) {
- //TODO: Change calltype after adding support for TX and RX
- InCallPresenter.getInstance().acceptUpgradeRequest(
- VideoProfile.STATE_BIDIRECTIONAL, context);
- } else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) {
- InCallPresenter.getInstance().declineUpgradeRequest(context);
- } else if (action.equals(ACTION_PULL_EXTERNAL_CALL)) {
- int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
- InCallPresenter.getInstance().getExternalCallNotifier()
- .pullExternalCall(notificationId);
- }
- }
-
-}
diff --git a/InCallUI/src/com/android/incallui/PostCharDialogFragment.java b/InCallUI/src/com/android/incallui/PostCharDialogFragment.java
deleted file mode 100644
index 6f904ad9e..000000000
--- a/InCallUI/src/com/android/incallui/PostCharDialogFragment.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2013 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.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-import com.android.dialer.R;
-
-/**
- * Pop up an alert dialog with OK and Cancel buttons to allow user to Accept or Reject the WAIT
- * inserted as part of the Dial string.
- */
-public class PostCharDialogFragment extends DialogFragment {
-
- private static final String STATE_CALL_ID = "CALL_ID";
- private static final String STATE_POST_CHARS = "POST_CHARS";
-
- private String mCallId;
- private String mPostDialStr;
-
- public PostCharDialogFragment() {
- }
-
- public PostCharDialogFragment(String callId, String postDialStr) {
- mCallId = callId;
- mPostDialStr = postDialStr;
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- super.onCreateDialog(savedInstanceState);
-
- if (mPostDialStr == null && savedInstanceState != null) {
- mCallId = savedInstanceState.getString(STATE_CALL_ID);
- mPostDialStr = savedInstanceState.getString(STATE_POST_CHARS);
- }
-
- final StringBuilder buf = new StringBuilder();
- buf.append(getResources().getText(R.string.wait_prompt_str));
- buf.append(mPostDialStr);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setMessage(buf.toString());
-
- builder.setPositiveButton(R.string.pause_prompt_yes, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- TelecomAdapter.getInstance().postDialContinue(mCallId, true);
- }
- });
- builder.setNegativeButton(R.string.pause_prompt_no, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dialog.cancel();
- }
- });
-
- final AlertDialog dialog = builder.create();
- return dialog;
- }
-
- @Override
- public void onCancel(DialogInterface dialog) {
- super.onCancel(dialog);
-
- TelecomAdapter.getInstance().postDialContinue(mCallId, false);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putString(STATE_CALL_ID, mCallId);
- outState.putString(STATE_POST_CHARS, mPostDialStr);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/Presenter.java b/InCallUI/src/com/android/incallui/Presenter.java
deleted file mode 100644
index 4e1fa978d..000000000
--- a/InCallUI/src/com/android/incallui/Presenter.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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.os.Bundle;
-
-/**
- * Base class for Presenters.
- */
-public abstract class Presenter<U extends Ui> {
-
- private U mUi;
-
- /**
- * Called after the UI view has been created. That is when fragment.onViewCreated() is called.
- *
- * @param ui The Ui implementation that is now ready to be used.
- */
- public void onUiReady(U ui) {
- mUi = ui;
- }
-
- /**
- * Called when the UI view is destroyed in Fragment.onDestroyView().
- */
- public final void onUiDestroy(U ui) {
- onUiUnready(ui);
- mUi = null;
- }
-
- /**
- * To be overriden by Presenter implementations. Called when the fragment is being
- * destroyed but before ui is set to null.
- */
- public void onUiUnready(U ui) {
- }
-
- public void onSaveInstanceState(Bundle outState) {}
-
- public void onRestoreInstanceState(Bundle savedInstanceState) {}
-
- public U getUi() {
- return mUi;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ProximitySensor.java b/InCallUI/src/com/android/incallui/ProximitySensor.java
deleted file mode 100644
index 3c9fd9370..000000000
--- a/InCallUI/src/com/android/incallui/ProximitySensor.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Copyright (C) 2013 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 com.google.common.base.Objects;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.PowerManager;
-import android.telecom.CallAudioState;
-import android.view.Display;
-
-import com.android.incallui.AudioModeProvider.AudioModeListener;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.InCallPresenter.InCallStateListener;
-
-/**
- * Class manages the proximity sensor for the in-call UI.
- * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off
- * the touchscreen and display when the user is close to the screen to prevent user's cheek from
- * causing touch events.
- * The class requires special knowledge of the activity and device state to know when the proximity
- * sensor should be enabled and disabled. Most of that state is fed into this class through
- * public methods.
- */
-public class ProximitySensor implements AccelerometerListener.OrientationListener,
- InCallStateListener, AudioModeListener {
- private static final String TAG = ProximitySensor.class.getSimpleName();
-
- private final PowerManager mPowerManager;
- private final PowerManager.WakeLock mProximityWakeLock;
- private final AudioModeProvider mAudioModeProvider;
- private final AccelerometerListener mAccelerometerListener;
- private final ProximityDisplayListener mDisplayListener;
- private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
- private boolean mUiShowing = false;
- private boolean mIsPhoneOffhook = false;
- private boolean mDialpadVisible;
-
- // True if the keyboard is currently *not* hidden
- // Gets updated whenever there is a Configuration change
- private boolean mIsHardKeyboardOpen;
-
- public ProximitySensor(Context context, AudioModeProvider audioModeProvider,
- AccelerometerListener accelerometerListener) {
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
- mProximityWakeLock = mPowerManager.newWakeLock(
- PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
- } else {
- Log.w(TAG, "Device does not support proximity wake lock.");
- mProximityWakeLock = null;
- }
- mAccelerometerListener = accelerometerListener;
- mAccelerometerListener.setListener(this);
-
- mDisplayListener = new ProximityDisplayListener(
- (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE));
- mDisplayListener.register();
-
- mAudioModeProvider = audioModeProvider;
- mAudioModeProvider.addListener(this);
- }
-
- public void tearDown() {
- mAudioModeProvider.removeListener(this);
-
- mAccelerometerListener.enable(false);
- mDisplayListener.unregister();
-
- turnOffProximitySensor(true);
- }
-
- /**
- * Called to identify when the device is laid down flat.
- */
- @Override
- public void orientationChanged(int orientation) {
- mOrientation = orientation;
- updateProximitySensorMode();
- }
-
- /**
- * Called to keep track of the overall UI state.
- */
- @Override
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
- // We ignore incoming state because we do not want to enable proximity
- // sensor during incoming call screen. We check hasLiveCall() because a disconnected call
- // can also put the in-call screen in the INCALL state.
- boolean hasOngoingCall = InCallState.INCALL == newState && callList.hasLiveCall();
- boolean isOffhook = (InCallState.OUTGOING == newState) || hasOngoingCall;
-
- if (isOffhook != mIsPhoneOffhook) {
- mIsPhoneOffhook = isOffhook;
-
- mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
- mAccelerometerListener.enable(mIsPhoneOffhook);
-
- updateProximitySensorMode();
- }
- }
-
- @Override
- public void onSupportedAudioMode(int modeMask) {
- }
-
- @Override
- public void onMute(boolean muted) {
- }
-
- /**
- * Called when the audio mode changes during a call.
- */
- @Override
- public void onAudioMode(int mode) {
- updateProximitySensorMode();
- }
-
- public void onDialpadVisible(boolean visible) {
- mDialpadVisible = visible;
- updateProximitySensorMode();
- }
-
- /**
- * Called by InCallActivity to listen for hard keyboard events.
- */
- public void onConfigurationChanged(Configuration newConfig) {
- mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
-
- // Update the Proximity sensor based on keyboard state
- updateProximitySensorMode();
- }
-
- /**
- * Used to save when the UI goes in and out of the foreground.
- */
- public void onInCallShowing(boolean showing) {
- if (showing) {
- mUiShowing = true;
-
- // We only consider the UI not showing for instances where another app took the foreground.
- // If we stopped showing because the screen is off, we still consider that showing.
- } else if (mPowerManager.isScreenOn()) {
- mUiShowing = false;
- }
- updateProximitySensorMode();
- }
-
- void onDisplayStateChanged(boolean isDisplayOn) {
- Log.i(this, "isDisplayOn: " + isDisplayOn);
- mAccelerometerListener.enable(isDisplayOn);
- }
-
- /**
- * TODO: There is no way to determine if a screen is off due to proximity or if it is
- * legitimately off, but if ever we can do that in the future, it would be useful here.
- * Until then, this function will simply return true of the screen is off.
- * TODO: Investigate whether this can be replaced with the ProximityDisplayListener.
- */
- public boolean isScreenReallyOff() {
- return !mPowerManager.isScreenOn();
- }
-
- private void turnOnProximitySensor() {
- if (mProximityWakeLock != null) {
- if (!mProximityWakeLock.isHeld()) {
- Log.i(this, "Acquiring proximity wake lock");
- mProximityWakeLock.acquire();
- } else {
- Log.i(this, "Proximity wake lock already acquired");
- }
- }
- }
-
- private void turnOffProximitySensor(boolean screenOnImmediately) {
- if (mProximityWakeLock != null) {
- if (mProximityWakeLock.isHeld()) {
- Log.i(this, "Releasing proximity wake lock");
- int flags =
- (screenOnImmediately ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
- mProximityWakeLock.release(flags);
- } else {
- Log.i(this, "Proximity wake lock already released");
- }
- }
- }
-
- /**
- * Updates the wake lock used to control proximity sensor behavior,
- * based on the current state of the phone.
- *
- * On devices that have a proximity sensor, to avoid false touches
- * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock
- * whenever the phone is off hook. (When held, that wake lock causes
- * the screen to turn off automatically when the sensor detects an
- * object close to the screen.)
- *
- * This method is a no-op for devices that don't have a proximity
- * sensor.
- *
- * Proximity wake lock will *not* be held if any one of the
- * conditions is true while on a call:
- * 1) If the audio is routed via Bluetooth
- * 2) If a wired headset is connected
- * 3) if the speaker is ON
- * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden)
- */
- private synchronized void updateProximitySensorMode() {
- final int audioMode = mAudioModeProvider.getAudioMode();
-
- // turn proximity sensor off and turn screen on immediately if
- // we are using a headset, the keyboard is open, or the device
- // is being held in a horizontal position.
- boolean screenOnImmediately = (CallAudioState.ROUTE_WIRED_HEADSET == audioMode
- || CallAudioState.ROUTE_SPEAKER == audioMode
- || CallAudioState.ROUTE_BLUETOOTH == audioMode
- || mIsHardKeyboardOpen);
-
- // We do not keep the screen off when the user is outside in-call screen and we are
- // horizontal, but we do not force it on when we become horizontal until the
- // proximity sensor goes negative.
- final boolean horizontal =
- (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
- screenOnImmediately |= !mUiShowing && horizontal;
-
- // We do not keep the screen off when dialpad is visible, we are horizontal, and
- // the in-call screen is being shown.
- // At that moment we're pretty sure users want to use it, instead of letting the
- // proximity sensor turn off the screen by their hands.
- screenOnImmediately |= mDialpadVisible && horizontal;
-
- Log.v(this, "screenonImmediately: ", screenOnImmediately);
-
- Log.i(this, Objects.toStringHelper(this)
- .add("keybrd", mIsHardKeyboardOpen ? 1 : 0)
- .add("dpad", mDialpadVisible ? 1 : 0)
- .add("offhook", mIsPhoneOffhook ? 1 : 0)
- .add("hor", horizontal ? 1 : 0)
- .add("ui", mUiShowing ? 1 : 0)
- .add("aud", CallAudioState.audioRouteToString(audioMode))
- .toString());
-
- if (mIsPhoneOffhook && !screenOnImmediately) {
- Log.d(this, "Turning on proximity sensor");
- // Phone is in use! Arrange for the screen to turn off
- // automatically when the sensor detects a close object.
- turnOnProximitySensor();
- } else {
- Log.d(this, "Turning off proximity sensor");
- // Phone is either idle, or ringing. We don't want any special proximity sensor
- // behavior in either case.
- turnOffProximitySensor(screenOnImmediately);
- }
- }
-
- /**
- * Implementation of a {@link DisplayListener} that maintains a binary state:
- * Screen on vs screen off. Used by the proximity sensor manager to decide whether or not
- * it needs to listen to accelerometer events.
- */
- public class ProximityDisplayListener implements DisplayListener {
- private DisplayManager mDisplayManager;
- private boolean mIsDisplayOn = true;
-
- ProximityDisplayListener(DisplayManager displayManager) {
- mDisplayManager = displayManager;
- }
-
- void register() {
- mDisplayManager.registerDisplayListener(this, null);
- }
-
- void unregister() {
- mDisplayManager.unregisterDisplayListener(this);
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- if (displayId == Display.DEFAULT_DISPLAY) {
- final Display display = mDisplayManager.getDisplay(displayId);
-
- final boolean isDisplayOn = display.getState() != Display.STATE_OFF;
- // For call purposes, we assume that as long as the screen is not truly off, it is
- // considered on, even if it is in an unknown or low power idle state.
- if (isDisplayOn != mIsDisplayOn) {
- mIsDisplayOn = isDisplayOn;
- onDisplayStateChanged(mIsDisplayOn);
- }
- }
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
deleted file mode 100644
index cc87dd414..000000000
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ /dev/null
@@ -1,793 +0,0 @@
-/*
- * Copyright (C) 2013 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 static com.android.contacts.common.compat.CallSdkCompat.Details.PROPERTY_ENTERPRISE_CALL;
-import static com.android.incallui.NotificationBroadcastReceiver.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST;
-import static com.android.incallui.NotificationBroadcastReceiver.ACTION_ANSWER_VIDEO_INCOMING_CALL;
-import static com.android.incallui.NotificationBroadcastReceiver.ACTION_ANSWER_VOICE_INCOMING_CALL;
-import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_INCOMING_CALL;
-import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST;
-import static com.android.incallui.NotificationBroadcastReceiver.ACTION_HANG_UP_ONGOING_CALL;
-
-import com.google.common.base.Preconditions;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.media.AudioAttributes;
-import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.support.annotation.Nullable;
-import android.telecom.Call.Details;
-import android.telecom.PhoneAccount;
-import android.telecom.TelecomManager;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.ContactsUtils.UserType;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.contacts.common.util.BitmapUtil;
-import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.dialer.R;
-import com.android.dialer.service.ExtendedCallInfoService;
-import com.android.incallui.ContactInfoCache.ContactCacheEntry;
-import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.async.PausableExecutorImpl;
-import com.android.incallui.ringtone.DialerRingtoneManager;
-import com.android.incallui.ringtone.InCallTonePlayer;
-import com.android.incallui.ringtone.ToneGeneratorFactory;
-
-import java.util.Objects;
-
-/**
- * This class adds Notifications to the status bar for the in-call experience.
- */
-public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
- CallList.CallUpdateListener {
-
- // Notification types
- // Indicates that no notification is currently showing.
- private static final int NOTIFICATION_NONE = 0;
- // Notification for an active call. This is non-interruptive, but cannot be dismissed.
- private static final int NOTIFICATION_IN_CALL = 1;
- // Notification for incoming calls. This is interruptive and will show up as a HUN.
- private static final int NOTIFICATION_INCOMING_CALL = 2;
-
- private static final int PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN = 0;
- private static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
-
- private static final long[] VIBRATE_PATTERN = new long[] {0, 1000, 1000};
-
- private final Context mContext;
- @Nullable private ContactsPreferences mContactsPreferences;
- private final ContactInfoCache mContactInfoCache;
- private final NotificationManager mNotificationManager;
- private final DialerRingtoneManager mDialerRingtoneManager;
- private int mCurrentNotification = NOTIFICATION_NONE;
- private int mCallState = Call.State.INVALID;
- private int mSavedIcon = 0;
- private String mSavedContent = null;
- private Bitmap mSavedLargeIcon;
- private String mSavedContentTitle;
- private String mCallId = null;
- private InCallState mInCallState;
- private Uri mRingtone;
-
- public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache) {
- Preconditions.checkNotNull(context);
- mContext = context;
- mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
- mContactInfoCache = contactInfoCache;
- mNotificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- mDialerRingtoneManager = new DialerRingtoneManager(
- new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()),
- CallList.getInstance());
- mCurrentNotification = NOTIFICATION_NONE;
- }
-
- /**
- * Creates notifications according to the state we receive from {@link InCallPresenter}.
- */
- @Override
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
- Log.d(this, "onStateChange");
- mInCallState = newState;
- updateNotification(newState, callList);
- }
-
- /**
- * Updates the phone app's status bar notification *and* launches the
- * incoming call UI in response to a new incoming call.
- *
- * If an incoming call is ringing (or call-waiting), the notification
- * will also include a "fullScreenIntent" that will cause the
- * InCallScreen to be launched, unless the current foreground activity
- * is marked as "immersive".
- *
- * (This is the mechanism that actually brings up the incoming call UI
- * when we receive a "new ringing connection" event from the telephony
- * layer.)
- *
- * Also note that this method is safe to call even if the phone isn't
- * actually ringing (or, more likely, if an incoming call *was*
- * ringing briefly but then disconnected). In that case, we'll simply
- * update or cancel the in-call notification based on the current
- * phone state.
- *
- * @see #updateInCallNotification(InCallState,CallList)
- */
- public void updateNotification(InCallState state, CallList callList) {
- updateInCallNotification(state, callList);
- }
-
- /**
- * Take down the in-call notification.
- * @see #updateInCallNotification(InCallState,CallList)
- */
- private void cancelNotification() {
- if (!TextUtils.isEmpty(mCallId)) {
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- mCallId = null;
- }
- if (mCurrentNotification != NOTIFICATION_NONE) {
- Log.d(this, "cancelInCall()...");
- mNotificationManager.cancel(mCurrentNotification);
- }
- mCurrentNotification = NOTIFICATION_NONE;
- }
-
- /**
- * Should only be called from a irrecoverable state where it is necessary to dismiss all
- * notifications.
- */
- static void clearAllCallNotifications(Context backupContext) {
- Log.i(StatusBarNotifier.class.getSimpleName(),
- "Something terrible happened. Clear all InCall notifications");
-
- NotificationManager notificationManager =
- (NotificationManager) backupContext.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(NOTIFICATION_IN_CALL);
- notificationManager.cancel(NOTIFICATION_INCOMING_CALL);
- }
-
- /**
- * Helper method for updateInCallNotification() and
- * updateNotification(): Update the phone app's
- * status bar notification based on the current telephony state, or
- * cancels the notification if the phone is totally idle.
- */
- private void updateInCallNotification(final InCallState state, CallList callList) {
- Log.d(this, "updateInCallNotification...");
-
- final Call call = getCallToShow(callList);
-
- if (call != null) {
- showNotification(call);
- } else {
- cancelNotification();
- }
- }
-
- private void showNotification(final Call call) {
- final boolean isIncoming = (call.getState() == Call.State.INCOMING ||
- call.getState() == Call.State.CALL_WAITING);
- if (!TextUtils.isEmpty(mCallId)) {
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- }
- mCallId = call.getId();
- CallList.getInstance().addCallUpdateListener(call.getId(), this);
-
- // we make a call to the contact info cache to query for supplemental data to what the
- // call provides. This includes the contact name and photo.
- // This callback will always get called immediately and synchronously with whatever data
- // it has available, and may make a subsequent call later (same thread) if it had to
- // call into the contacts provider for more data.
- mContactInfoCache.findInfo(call, isIncoming, new ContactInfoCacheCallback() {
- @Override
- public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
- Call call = CallList.getInstance().getCallById(callId);
- if (call != null) {
- call.getLogState().contactLookupResult = entry.contactLookupResult;
- buildAndSendNotification(call, entry);
- }
- }
-
- @Override
- public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
- Call call = CallList.getInstance().getCallById(callId);
- if (call != null) {
- buildAndSendNotification(call, entry);
- }
- }
-
- @Override
- public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) {}
- });
- }
-
- /**
- * Sets up the main Ui for the notification
- */
- private void buildAndSendNotification(Call originalCall, ContactCacheEntry contactInfo) {
- // This can get called to update an existing notification after contact information has come
- // back. However, it can happen much later. Before we continue, we need to make sure that
- // the call being passed in is still the one we want to show in the notification.
- final Call call = getCallToShow(CallList.getInstance());
- if (call == null || !call.getId().equals(originalCall.getId())) {
- return;
- }
-
- final int callState = call.getState();
- // Dont' show as spam if the number is in local contact.
- if (contactInfo.contactLookupResult == Call.LogState.LOOKUP_LOCAL_CONTACT) {
- call.setSpam(false);
- }
-
- // Check if data has changed; if nothing is different, don't issue another notification.
- final int iconResId = getIconToDisplay(call);
- Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call);
- final String content =
- getContentString(call, contactInfo.userType);
- final String contentTitle = getContentTitle(contactInfo, call);
-
- final boolean isVideoUpgradeRequest = call.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
- final int notificationType;
- if (callState == Call.State.INCOMING || callState == Call.State.CALL_WAITING
- || isVideoUpgradeRequest) {
- notificationType = NOTIFICATION_INCOMING_CALL;
- } else {
- notificationType = NOTIFICATION_IN_CALL;
- }
-
- if (!checkForChangeAndSaveData(iconResId, content, largeIcon, contentTitle, callState,
- notificationType, contactInfo.contactRingtoneUri)) {
- return;
- }
-
- if (largeIcon != null) {
- largeIcon = getRoundedIcon(largeIcon);
- }
-
- /*
- * This builder is used for the notification shown when the device is locked and the user
- * has set their notification settings to 'hide sensitive content'
- * {@see Notification.Builder#setPublicVersion}.
- */
- Notification.Builder publicBuilder = new Notification.Builder(mContext);
- publicBuilder.setSmallIcon(iconResId)
- .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
- // Hide work call state for the lock screen notification
- .setContentTitle(getContentString(call, ContactsUtils.USER_TYPE_CURRENT));
- setNotificationWhen(call, callState, publicBuilder);
-
- /*
- * Builder for the notification shown when the device is unlocked or the user has set their
- * notification settings to 'show all notification content'.
- */
- final Notification.Builder builder = getNotificationBuilder();
- builder.setPublicVersion(publicBuilder.build());
-
- // Set up the main intent to send the user to the in-call screen
- builder.setContentIntent(createLaunchPendingIntent(false /* isFullScreen */));
-
- // Set the intent as a full screen intent as well if a call is incoming
- if (notificationType == NOTIFICATION_INCOMING_CALL
- && !InCallPresenter.getInstance().isShowingInCallUi()) {
- configureFullScreenIntent(
- builder, createLaunchPendingIntent(true /* isFullScreen */), call);
- // Set the notification category for incoming calls
- builder.setCategory(Notification.CATEGORY_CALL);
- }
-
- // Set the content
- builder.setContentText(content);
- builder.setSmallIcon(iconResId);
- builder.setContentTitle(contentTitle);
- builder.setLargeIcon(largeIcon);
- builder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color));
-
- if (isVideoUpgradeRequest) {
- builder.setUsesChronometer(false);
- addDismissUpgradeRequestAction(builder);
- addAcceptUpgradeRequestAction(builder);
- } else {
- createIncomingCallNotification(call, callState, builder);
- }
-
- addPersonReference(builder, contactInfo, call);
-
- /*
- * Fire off the notification
- */
- Notification notification = builder.build();
-
- if (mDialerRingtoneManager.shouldPlayRingtone(callState, contactInfo.contactRingtoneUri)) {
- notification.flags |= Notification.FLAG_INSISTENT;
- notification.sound = contactInfo.contactRingtoneUri;
- AudioAttributes.Builder audioAttributes = new AudioAttributes.Builder();
- audioAttributes.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
- audioAttributes.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
- notification.audioAttributes = audioAttributes.build();
- if (mDialerRingtoneManager.shouldVibrate(mContext.getContentResolver())) {
- notification.vibrate = VIBRATE_PATTERN;
- }
- }
- if (mDialerRingtoneManager.shouldPlayCallWaitingTone(callState)) {
- Log.v(this, "Playing call waiting tone");
- mDialerRingtoneManager.playCallWaitingTone();
- }
- if (mCurrentNotification != notificationType && mCurrentNotification != NOTIFICATION_NONE) {
- Log.i(this, "Previous notification already showing - cancelling "
- + mCurrentNotification);
- mNotificationManager.cancel(mCurrentNotification);
- }
-
- Log.i(this, "Displaying notification for " + notificationType);
- mNotificationManager.notify(notificationType, notification);
- call.getLatencyReport().onNotificationShown();
- mCurrentNotification = notificationType;
- }
-
- private void createIncomingCallNotification(
- Call call, int state, Notification.Builder builder) {
- setNotificationWhen(call, state, builder);
-
- // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
- if (state == Call.State.ACTIVE ||
- state == Call.State.ONHOLD ||
- Call.State.isDialing(state)) {
- addHangupAction(builder);
- } else if (state == Call.State.INCOMING || state == Call.State.CALL_WAITING) {
- addDismissAction(builder);
- if (call.isVideoCall(mContext)) {
- addVoiceAction(builder);
- addVideoCallAction(builder);
- } else {
- addAnswerAction(builder);
- }
- }
- }
-
- /*
- * Sets the notification's when section as needed. For active calls, this is explicitly set as
- * the duration of the call. For all other states, the notification will automatically show the
- * time at which the notification was created.
- */
- private void setNotificationWhen(Call call, int state, Notification.Builder builder) {
- if (state == Call.State.ACTIVE) {
- builder.setUsesChronometer(true);
- builder.setWhen(call.getConnectTimeMillis());
- } else {
- builder.setUsesChronometer(false);
- }
- }
-
- /**
- * Checks the new notification data and compares it against any notification that we
- * are already displaying. If the data is exactly the same, we return false so that
- * we do not issue a new notification for the exact same data.
- */
- private boolean checkForChangeAndSaveData(int icon, String content, Bitmap largeIcon,
- String contentTitle, int state, int notificationType, Uri ringtone) {
-
- // The two are different:
- // if new title is not null, it should be different from saved version OR
- // if new title is null, the saved version should not be null
- final boolean contentTitleChanged =
- (contentTitle != null && !contentTitle.equals(mSavedContentTitle)) ||
- (contentTitle == null && mSavedContentTitle != null);
-
- // any change means we are definitely updating
- boolean retval = (mSavedIcon != icon) || !Objects.equals(mSavedContent, content)
- || (mCallState != state) || (mSavedLargeIcon != largeIcon)
- || contentTitleChanged || !Objects.equals(mRingtone, ringtone);
-
- // If we aren't showing a notification right now or the notification type is changing,
- // definitely do an update.
- if (mCurrentNotification != notificationType) {
- if (mCurrentNotification == NOTIFICATION_NONE) {
- Log.d(this, "Showing notification for first time.");
- }
- retval = true;
- }
-
- mSavedIcon = icon;
- mSavedContent = content;
- mCallState = state;
- mSavedLargeIcon = largeIcon;
- mSavedContentTitle = contentTitle;
- mRingtone = ringtone;
-
- if (retval) {
- Log.d(this, "Data changed. Showing notification");
- }
-
- return retval;
- }
-
- /**
- * Returns the main string to use in the notification.
- */
- @NeededForTesting
- String getContentTitle(ContactCacheEntry contactInfo, Call call) {
- if (call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) {
- return mContext.getResources().getString(R.string.card_title_conf_call);
- }
-
- String preferredName = ContactDisplayUtils.getPreferredDisplayName(contactInfo.namePrimary,
- contactInfo.nameAlternative, mContactsPreferences);
- if (TextUtils.isEmpty(preferredName)) {
- return TextUtils.isEmpty(contactInfo.number) ? null : BidiFormatter.getInstance()
- .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
- }
- return preferredName;
- }
-
- private void addPersonReference(Notification.Builder builder, ContactCacheEntry contactInfo,
- Call call) {
- // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
- // So, do not pass {@link Contacts#CONTENT_LOOKUP_URI} to NotificationManager to avoid
- // NotificationManager using it.
- if (contactInfo.lookupUri != null && contactInfo.userType != ContactsUtils.USER_TYPE_WORK) {
- builder.addPerson(contactInfo.lookupUri.toString());
- } else if (!TextUtils.isEmpty(call.getNumber())) {
- builder.addPerson(Uri.fromParts(PhoneAccount.SCHEME_TEL,
- call.getNumber(), null).toString());
- }
- }
-
- /**
- * Gets a large icon from the contact info object to display in the notification.
- */
- private Bitmap getLargeIconToDisplay(ContactCacheEntry contactInfo, Call call) {
- Bitmap largeIcon = null;
- if (call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) {
- largeIcon = BitmapFactory.decodeResource(mContext.getResources(),
- R.drawable.img_conference);
- }
- if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
- largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
- }
- if (call.isSpam()) {
- Drawable drawable = mContext.getResources().getDrawable(R.drawable.blocked_contact);
- largeIcon = CallCardFragment.drawableToBitmap(drawable);
- }
- return largeIcon;
- }
-
- private Bitmap getRoundedIcon(Bitmap bitmap) {
- if (bitmap == null) {
- return null;
- }
- final int height = (int) mContext.getResources().getDimension(
- android.R.dimen.notification_large_icon_height);
- final int width = (int) mContext.getResources().getDimension(
- android.R.dimen.notification_large_icon_width);
- return BitmapUtil.getRoundedBitmap(bitmap, width, height);
- }
-
- /**
- * Returns the appropriate icon res Id to display based on the call for which
- * we want to display information.
- */
- private int getIconToDisplay(Call call) {
- // Even if both lines are in use, we only show a single item in
- // the expanded Notifications UI. It's labeled "Ongoing call"
- // (or "On hold" if there's only one call, and it's on hold.)
- // Also, we don't have room to display caller-id info from two
- // different calls. So if both lines are in use, display info
- // from the foreground call. And if there's a ringing call,
- // display that regardless of the state of the other calls.
- if (call.getState() == Call.State.ONHOLD) {
- return R.drawable.ic_phone_paused_white_24dp;
- } else if (call.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- return R.drawable.ic_videocam;
- }
- return R.drawable.ic_call_white_24dp;
- }
-
- /**
- * Returns the message to use with the notification.
- */
- private String getContentString(Call call, @UserType long userType) {
- boolean isIncomingOrWaiting = call.getState() == Call.State.INCOMING ||
- call.getState() == Call.State.CALL_WAITING;
-
- if (isIncomingOrWaiting &&
- call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED) {
-
- if (!TextUtils.isEmpty(call.getChildNumber())) {
- return mContext.getString(R.string.child_number, call.getChildNumber());
- } else if (!TextUtils.isEmpty(call.getCallSubject()) && call.isCallSubjectSupported()) {
- return call.getCallSubject();
- }
- }
-
- int resId = R.string.notification_ongoing_call;
- if (call.hasProperty(Details.PROPERTY_WIFI)) {
- resId = R.string.notification_ongoing_call_wifi;
- }
-
- if (isIncomingOrWaiting) {
- if (call.hasProperty(Details.PROPERTY_WIFI)) {
- resId = R.string.notification_incoming_call_wifi;
- } else {
- if (call.isSpam()) {
- resId = R.string.notification_incoming_spam_call;
- } else {
- resId = R.string.notification_incoming_call;
- }
- }
- } else if (call.getState() == Call.State.ONHOLD) {
- resId = R.string.notification_on_hold;
- } else if (Call.State.isDialing(call.getState())) {
- resId = R.string.notification_dialing;
- } else if (call.getSessionModificationState()
- == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- resId = R.string.notification_requesting_video_call;
- }
-
- // Is the call placed through work connection service.
- boolean isWorkCall = call.hasProperty(PROPERTY_ENTERPRISE_CALL);
- if(userType == ContactsUtils.USER_TYPE_WORK || isWorkCall) {
- resId = getWorkStringFromPersonalString(resId);
- }
-
- return mContext.getString(resId);
- }
-
- private static int getWorkStringFromPersonalString(int resId) {
- if (resId == R.string.notification_ongoing_call) {
- return R.string.notification_ongoing_work_call;
- } else if (resId == R.string.notification_ongoing_call_wifi) {
- return R.string.notification_ongoing_work_call_wifi;
- } else if (resId == R.string.notification_incoming_call_wifi) {
- return R.string.notification_incoming_work_call_wifi;
- } else if (resId == R.string.notification_incoming_call) {
- return R.string.notification_incoming_work_call;
- } else {
- return resId;
- }
- }
-
- /**
- * Gets the most relevant call to display in the notification.
- */
- private Call getCallToShow(CallList callList) {
- if (callList == null) {
- return null;
- }
- Call call = callList.getIncomingCall();
- if (call == null) {
- call = callList.getOutgoingCall();
- }
- if (call == null) {
- call = callList.getVideoUpgradeRequestCall();
- }
- if (call == null) {
- call = callList.getActiveOrBackgroundCall();
- }
- return call;
- }
-
- private void addAnswerAction(Notification.Builder builder) {
- Log.d(this, "Will show \"answer\" action in the incoming call Notification");
-
- PendingIntent answerVoicePendingIntent = createNotificationPendingIntent(
- mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
- builder.addAction(R.drawable.ic_call_white_24dp,
- mContext.getText(R.string.notification_action_answer),
- answerVoicePendingIntent);
- }
-
- private void addDismissAction(Notification.Builder builder) {
- Log.d(this, "Will show \"dismiss\" action in the incoming call Notification");
-
- PendingIntent declinePendingIntent =
- createNotificationPendingIntent(mContext, ACTION_DECLINE_INCOMING_CALL);
- builder.addAction(R.drawable.ic_close_dk,
- mContext.getText(R.string.notification_action_dismiss),
- declinePendingIntent);
- }
-
- private void addHangupAction(Notification.Builder builder) {
- Log.d(this, "Will show \"hang-up\" action in the ongoing active call Notification");
-
- PendingIntent hangupPendingIntent =
- createNotificationPendingIntent(mContext, ACTION_HANG_UP_ONGOING_CALL);
- builder.addAction(R.drawable.ic_call_end_white_24dp,
- mContext.getText(R.string.notification_action_end_call),
- hangupPendingIntent);
- }
-
- private void addVideoCallAction(Notification.Builder builder) {
- Log.i(this, "Will show \"video\" action in the incoming call Notification");
-
- PendingIntent answerVideoPendingIntent = createNotificationPendingIntent(
- mContext, ACTION_ANSWER_VIDEO_INCOMING_CALL);
- builder.addAction(R.drawable.ic_videocam,
- mContext.getText(R.string.notification_action_answer_video),
- answerVideoPendingIntent);
- }
-
- private void addVoiceAction(Notification.Builder builder) {
- Log.d(this, "Will show \"voice\" action in the incoming call Notification");
-
- PendingIntent answerVoicePendingIntent = createNotificationPendingIntent(
- mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
- builder.addAction(R.drawable.ic_call_white_24dp,
- mContext.getText(R.string.notification_action_answer_voice),
- answerVoicePendingIntent);
- }
-
- private void addAcceptUpgradeRequestAction(Notification.Builder builder) {
- Log.i(this, "Will show \"accept upgrade\" action in the incoming call Notification");
-
- PendingIntent acceptVideoPendingIntent = createNotificationPendingIntent(
- mContext, ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST);
- builder.addAction(0, mContext.getText(R.string.notification_action_accept),
- acceptVideoPendingIntent);
- }
-
- private void addDismissUpgradeRequestAction(Notification.Builder builder) {
- Log.i(this, "Will show \"dismiss upgrade\" action in the incoming call Notification");
-
- PendingIntent declineVideoPendingIntent = createNotificationPendingIntent(
- mContext, ACTION_DECLINE_VIDEO_UPGRADE_REQUEST);
- builder.addAction(0, mContext.getText(R.string.notification_action_dismiss),
- declineVideoPendingIntent);
- }
-
- /**
- * Adds fullscreen intent to the builder.
- */
- private void configureFullScreenIntent(Notification.Builder builder, PendingIntent intent,
- Call call) {
- // Ok, we actually want to launch the incoming call
- // UI at this point (in addition to simply posting a notification
- // to the status bar). Setting fullScreenIntent will cause
- // the InCallScreen to be launched immediately *unless* the
- // current foreground activity is marked as "immersive".
- Log.d(this, "- Setting fullScreenIntent: " + intent);
- builder.setFullScreenIntent(intent, true);
-
- // Ugly hack alert:
- //
- // The NotificationManager has the (undocumented) behavior
- // that it will *ignore* the fullScreenIntent field if you
- // post a new Notification that matches the ID of one that's
- // already active. Unfortunately this is exactly what happens
- // when you get an incoming call-waiting call: the
- // "ongoing call" notification is already visible, so the
- // InCallScreen won't get launched in this case!
- // (The result: if you bail out of the in-call UI while on a
- // call and then get a call-waiting call, the incoming call UI
- // won't come up automatically.)
- //
- // The workaround is to just notice this exact case (this is a
- // call-waiting call *and* the InCallScreen is not in the
- // foreground) and manually cancel the in-call notification
- // before (re)posting it.
- //
- // TODO: there should be a cleaner way of avoiding this
- // problem (see discussion in bug 3184149.)
-
- // If a call is onhold during an incoming call, the call actually comes in as
- // INCOMING. For that case *and* traditional call-waiting, we want to
- // cancel the notification.
- boolean isCallWaiting = (call.getState() == Call.State.CALL_WAITING ||
- (call.getState() == Call.State.INCOMING &&
- CallList.getInstance().getBackgroundCall() != null));
-
- if (isCallWaiting) {
- Log.i(this, "updateInCallNotification: call-waiting! force relaunch...");
- // Cancel the IN_CALL_NOTIFICATION immediately before
- // (re)posting it; this seems to force the
- // NotificationManager to launch the fullScreenIntent.
- mNotificationManager.cancel(NOTIFICATION_IN_CALL);
- }
- }
-
- private Notification.Builder getNotificationBuilder() {
- final Notification.Builder builder = new Notification.Builder(mContext);
- builder.setOngoing(true);
-
- // Make the notification prioritized over the other normal notifications.
- builder.setPriority(Notification.PRIORITY_HIGH);
-
- return builder;
- }
-
- private PendingIntent createLaunchPendingIntent(boolean isFullScreen) {
- Intent intent = InCallPresenter.getInstance().getInCallIntent(
- false /* showDialpad */, false /* newOutgoingCall */);
-
- int requestCode = PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN;
- if (isFullScreen) {
- intent.putExtra(InCallActivity.FOR_FULL_SCREEN_INTENT, true);
- // Use a unique request code so that the pending intent isn't clobbered by the
- // non-full screen pending intent.
- requestCode = PENDING_INTENT_REQUEST_CODE_FULL_SCREEN;
- }
-
- // PendingIntent that can be used to launch the InCallActivity. The
- // system fires off this intent if the user pulls down the windowshade
- // and clicks the notification's expanded view. It's also used to
- // launch the InCallActivity immediately when when there's an incoming
- // call (see the "fullScreenIntent" field below).
- return PendingIntent.getActivity(mContext, requestCode, intent, 0);
- }
-
- /**
- * Returns PendingIntent for answering a phone call. This will typically be used from
- * Notification context.
- */
- private static PendingIntent createNotificationPendingIntent(Context context, String action) {
- final Intent intent = new Intent(action, null,
- context, NotificationBroadcastReceiver.class);
- return PendingIntent.getBroadcast(context, 0, intent, 0);
- }
-
- @Override
- public void onCallChanged(Call call) {
- if (CallList.getInstance().getIncomingCall() == null) {
- mDialerRingtoneManager.stopCallWaitingTone();
- }
- }
-
- /**
- * Responds to changes in the session modification state for the call by dismissing the
- * status bar notification as required.
- *
- * @param sessionModificationState The new session modification state.
- */
- @Override
- public void onSessionModificationStateChange(int sessionModificationState) {
- if (sessionModificationState == Call.SessionModificationState.NO_REQUEST) {
- if (mCallId != null) {
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
- }
-
- updateNotification(mInCallState, CallList.getInstance());
- }
- }
-
- @Override
- public void onLastForwardedNumberChange() {
- // no-op
- }
-
- @Override
- public void onChildNumberChange() {
- // no-op
- }
-}
diff --git a/InCallUI/src/com/android/incallui/TelecomAdapter.java b/InCallUI/src/com/android/incallui/TelecomAdapter.java
deleted file mode 100644
index f172270dd..000000000
--- a/InCallUI/src/com/android/incallui/TelecomAdapter.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2014 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 com.google.common.base.Preconditions;
-
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Looper;
-import android.telecom.InCallService;
-import android.telecom.PhoneAccountHandle;
-
-import java.util.List;
-
-final class TelecomAdapter implements InCallServiceListener {
- private static final String ADD_CALL_MODE_KEY = "add_call_mode";
-
- private static TelecomAdapter sInstance;
- private InCallService mInCallService;
-
- static TelecomAdapter getInstance() {
- Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread());
- if (sInstance == null) {
- sInstance = new TelecomAdapter();
- }
- return sInstance;
- }
-
- private TelecomAdapter() {
- }
-
- @Override
- public void setInCallService(InCallService inCallService) {
- mInCallService = inCallService;
- }
-
- @Override
- public void clearInCallService() {
- mInCallService = null;
- }
-
- private android.telecom.Call getTelecomCallById(String callId) {
- Call call = CallList.getInstance().getCallById(callId);
- return call == null ? null : call.getTelecomCall();
- }
-
- void answerCall(String callId, int videoState) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.answer(videoState);
- } else {
- Log.e(this, "error answerCall, call not in call list: " + callId);
- }
- }
-
- void rejectCall(String callId, boolean rejectWithMessage, String message) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.reject(rejectWithMessage, message);
- } else {
- Log.e(this, "error rejectCall, call not in call list: " + callId);
- }
- }
-
- void disconnectCall(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.disconnect();
- } else {
- Log.e(this, "error disconnectCall, call not in call list " + callId);
- }
- }
-
- void holdCall(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.hold();
- } else {
- Log.e(this, "error holdCall, call not in call list " + callId);
- }
- }
-
- void unholdCall(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.unhold();
- } else {
- Log.e(this, "error unholdCall, call not in call list " + callId);
- }
- }
-
- void mute(boolean shouldMute) {
- if (mInCallService != null) {
- mInCallService.setMuted(shouldMute);
- } else {
- Log.e(this, "error mute, mInCallService is null");
- }
- }
-
- void setAudioRoute(int route) {
- if (mInCallService != null) {
- mInCallService.setAudioRoute(route);
- } else {
- Log.e(this, "error setAudioRoute, mInCallService is null");
- }
- }
-
- void separateCall(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.splitFromConference();
- } else {
- Log.e(this, "error separateCall, call not in call list " + callId);
- }
- }
-
- void merge(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- List<android.telecom.Call> conferenceable = call.getConferenceableCalls();
- if (!conferenceable.isEmpty()) {
- call.conference(conferenceable.get(0));
- } else {
- if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE)) {
- call.mergeConference();
- }
- }
- } else {
- Log.e(this, "error merge, call not in call list " + callId);
- }
- }
-
- void swap(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)) {
- call.swapConference();
- }
- } else {
- Log.e(this, "error swap, call not in call list " + callId);
- }
- }
-
- void addCall() {
- if (mInCallService != null) {
- Intent intent = new Intent(Intent.ACTION_DIAL);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- // when we request the dialer come up, we also want to inform
- // it that we're going through the "add call" option from the
- // InCallScreen / PhoneUtils.
- intent.putExtra(ADD_CALL_MODE_KEY, true);
- try {
- Log.d(this, "Sending the add Call intent");
- mInCallService.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- // This is rather rare but possible.
- // Note: this method is used even when the phone is encrypted. At that moment
- // the system may not find any Activity which can accept this Intent.
- Log.e(this, "Activity for adding calls isn't found.", e);
- }
- }
- }
-
- void playDtmfTone(String callId, char digit) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.playDtmfTone(digit);
- } else {
- Log.e(this, "error playDtmfTone, call not in call list " + callId);
- }
- }
-
- void stopDtmfTone(String callId) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.stopDtmfTone();
- } else {
- Log.e(this, "error stopDtmfTone, call not in call list " + callId);
- }
- }
-
- void postDialContinue(String callId, boolean proceed) {
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.postDialContinue(proceed);
- } else {
- Log.e(this, "error postDialContinue, call not in call list " + callId);
- }
- }
-
- void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle, boolean setDefault) {
- if (accountHandle == null) {
- Log.e(this, "error phoneAccountSelected, accountHandle is null");
- // TODO: Do we really want to send null accountHandle?
- }
-
- android.telecom.Call call = getTelecomCallById(callId);
- if (call != null) {
- call.phoneAccountSelected(accountHandle, setDefault);
- } else {
- Log.e(this, "error phoneAccountSelected, call not in call list " + callId);
- }
- }
-
- boolean canAddCall() {
- if (mInCallService != null) {
- return mInCallService.canAddCall();
- }
- return false;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/Ui.java b/InCallUI/src/com/android/incallui/Ui.java
deleted file mode 100644
index e453ccb1c..000000000
--- a/InCallUI/src/com/android/incallui/Ui.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2013 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;
-
-/**
- * Base class for all presenter ui.
- */
-public interface Ui {
-
-}
diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java
deleted file mode 100644
index 6a46a423d..000000000
--- a/InCallUI/src/com/android/incallui/VideoCallFragment.java
+++ /dev/null
@@ -1,901 +0,0 @@
-/*
- * Copyright (C) 2014 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.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.SurfaceTexture;
-import android.os.Bundle;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.dialer.R;
-import com.android.phone.common.animation.AnimUtils;
-import com.google.common.base.Objects;
-
-/**
- * Fragment containing video calling surfaces.
- */
-public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
- VideoCallPresenter.VideoCallUi> implements VideoCallPresenter.VideoCallUi {
- private static final String TAG = VideoCallFragment.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- /**
- * Used to indicate that the surface dimensions are not set.
- */
- private static final int DIMENSIONS_NOT_SET = -1;
-
- /**
- * Surface ID for the display surface.
- */
- public static final int SURFACE_DISPLAY = 1;
-
- /**
- * Surface ID for the preview surface.
- */
- public static final int SURFACE_PREVIEW = 2;
-
- /**
- * Used to indicate that the UI rotation is unknown.
- */
- public static final int ORIENTATION_UNKNOWN = -1;
-
- // Static storage used to retain the video surfaces across Activity restart.
- // TextureViews are not parcelable, so it is not possible to store them in the saved state.
- private static boolean sVideoSurfacesInUse = false;
- private static VideoCallSurface sPreviewSurface = null;
- private static VideoCallSurface sDisplaySurface = null;
- private static Point sDisplaySize = null;
-
- /**
- * {@link ViewStub} holding the video call surfaces. This is the parent for the
- * {@link VideoCallFragment}. Used to ensure that the video surfaces are only inflated when
- * required.
- */
- private ViewStub mVideoViewsStub;
-
- /**
- * Inflated view containing the video call surfaces represented by the {@link ViewStub}.
- */
- private View mVideoViews;
-
- /**
- * The {@link FrameLayout} containing the preview surface.
- */
- private View mPreviewVideoContainer;
-
- /**
- * Icon shown to indicate that the outgoing camera has been turned off.
- */
- private View mCameraOff;
-
- /**
- * {@link ImageView} containing the user's profile photo.
- */
- private ImageView mPreviewPhoto;
-
- /**
- * {@code True} when the layout of the activity has been completed.
- */
- private boolean mIsLayoutComplete = false;
-
- /**
- * {@code True} if in landscape mode.
- */
- private boolean mIsLandscape;
-
- private int mAnimationDuration;
-
- /**
- * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and
- * {@link Surface}. Used to manage the lifecycle of these objects across device orientation
- * changes.
- */
- private static class VideoCallSurface implements TextureView.SurfaceTextureListener,
- View.OnClickListener, View.OnAttachStateChangeListener {
- private int mSurfaceId;
- private VideoCallPresenter mPresenter;
- private TextureView mTextureView;
- private SurfaceTexture mSavedSurfaceTexture;
- private Surface mSavedSurface;
- private boolean mIsDoneWithSurface;
- private int mWidth = DIMENSIONS_NOT_SET;
- private int mHeight = DIMENSIONS_NOT_SET;
-
- /**
- * Creates an instance of a {@link VideoCallSurface}.
- *
- * @param surfaceId The surface ID of the surface.
- * @param textureView The {@link TextureView} for the surface.
- */
- public VideoCallSurface(VideoCallPresenter presenter, int surfaceId,
- TextureView textureView) {
- this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
- }
-
- /**
- * Creates an instance of a {@link VideoCallSurface}.
- *
- * @param surfaceId The surface ID of the surface.
- * @param textureView The {@link TextureView} for the surface.
- * @param width The width of the surface.
- * @param height The height of the surface.
- */
- public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView,
- int width, int height) {
- Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId +
- " width=" + width + " height=" + height);
- mPresenter = presenter;
- mWidth = width;
- mHeight = height;
- mSurfaceId = surfaceId;
-
- recreateView(textureView);
- }
-
- /**
- * Recreates a {@link VideoCallSurface} after a device orientation change. Re-applies the
- * saved {@link SurfaceTexture} to the
- *
- * @param view The {@link TextureView}.
- */
- public void recreateView(TextureView view) {
- if (DEBUG) {
- Log.i(TAG, "recreateView: " + view);
- }
-
- if (mTextureView == view) {
- return;
- }
-
- mTextureView = view;
- mTextureView.setSurfaceTextureListener(this);
- mTextureView.setOnClickListener(this);
-
- final boolean areSameSurfaces =
- Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture());
- Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture
- + " areSameSurfaces=" + areSameSurfaces);
- if (mSavedSurfaceTexture != null && !areSameSurfaces) {
- mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
- if (createSurface(mWidth, mHeight)) {
- onSurfaceCreated();
- }
- }
- mIsDoneWithSurface = false;
- }
-
- public void resetPresenter(VideoCallPresenter presenter) {
- Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter="
- + presenter);
- mPresenter = presenter;
- }
-
- /**
- * Handles {@link SurfaceTexture} callback to indicate that a {@link SurfaceTexture} has
- * been successfully created.
- *
- * @param surfaceTexture The {@link SurfaceTexture} which has been created.
- * @param width The width of the {@link SurfaceTexture}.
- * @param height The height of the {@link SurfaceTexture}.
- */
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
- int height) {
- boolean surfaceCreated;
- if (DEBUG) {
- Log.i(TAG, "onSurfaceTextureAvailable: " + surfaceTexture);
- }
- // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
- // If a saved {@link SurfaceTexture} is available, we are re-creating after an
- // orientation change.
- Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture="
- + surfaceTexture + " width=" + width
- + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture);
- Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter);
- if (mSavedSurfaceTexture == null) {
- mSavedSurfaceTexture = surfaceTexture;
- surfaceCreated = createSurface(width, height);
- } else {
- // A saved SurfaceTexture was found.
- Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface...");
- mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
- surfaceCreated = true;
- }
-
- // Inform presenter that the surface is available.
- if (surfaceCreated) {
- onSurfaceCreated();
- }
- }
-
- private void onSurfaceCreated() {
- if (mPresenter != null) {
- mPresenter.onSurfaceCreated(mSurfaceId);
- } else {
- Log.e(this, "onSurfaceTextureAvailable: Presenter is null");
- }
- }
-
- /**
- * Handles a change in the {@link SurfaceTexture}'s size.
- *
- * @param surfaceTexture The {@link SurfaceTexture}.
- * @param width The new width.
- * @param height The new height.
- */
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width,
- int height) {
- // Not handled
- }
-
- /**
- * Handles {@link SurfaceTexture} destruct callback, indicating that it has been destroyed.
- *
- * @param surfaceTexture The {@link SurfaceTexture}.
- * @return {@code True} if the {@link TextureView} can release the {@link SurfaceTexture}.
- */
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
- /**
- * Destroying the surface texture; inform the presenter so it can null the surfaces.
- */
- Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture="
- + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture
- + " SavedSurface=" + mSavedSurface);
- Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter);
-
- // Notify presenter if it is not null.
- onSurfaceDestroyed();
-
- if (mIsDoneWithSurface) {
- onSurfaceReleased();
- if (mSavedSurface != null) {
- mSavedSurface.release();
- mSavedSurface = null;
- }
- }
- return mIsDoneWithSurface;
- }
-
- private void onSurfaceDestroyed() {
- if (mPresenter != null) {
- mPresenter.onSurfaceDestroyed(mSurfaceId);
- } else {
- Log.e(this, "onSurfaceTextureDestroyed: Presenter is null.");
- }
- }
-
- /**
- * Handles {@link SurfaceTexture} update callback.
- * @param surface
- */
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- // Not Handled
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- if (DEBUG) {
- Log.i(TAG, "OnViewAttachedToWindow");
- }
- if (mSavedSurfaceTexture != null) {
- mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
- }
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {}
-
- /**
- * Retrieves the current {@link TextureView}.
- *
- * @return The {@link TextureView}.
- */
- public TextureView getTextureView() {
- return mTextureView;
- }
-
- /**
- * Called by the user presenter to indicate that the surface is no longer required due to a
- * change in video state. Releases and clears out the saved surface and surface textures.
- */
- public void setDoneWithSurface() {
- Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface
- + " SavedSurfaceTexture=" + mSavedSurfaceTexture);
- mIsDoneWithSurface = true;
- if (mTextureView != null && mTextureView.isAvailable()) {
- return;
- }
-
- if (mSavedSurface != null) {
- onSurfaceReleased();
- mSavedSurface.release();
- mSavedSurface = null;
- }
- if (mSavedSurfaceTexture != null) {
- mSavedSurfaceTexture.release();
- mSavedSurfaceTexture = null;
- }
- }
-
- private void onSurfaceReleased() {
- if (mPresenter != null) {
- mPresenter.onSurfaceReleased(mSurfaceId);
- } else {
- Log.d(this, "setDoneWithSurface: Presenter is null.");
- }
- }
-
- /**
- * Retrieves the saved surface instance.
- *
- * @return The surface.
- */
- public Surface getSurface() {
- return mSavedSurface;
- }
-
- /**
- * Sets the dimensions of the surface.
- *
- * @param width The width of the surface, in pixels.
- * @param height The height of the surface, in pixels.
- */
- public void setSurfaceDimensions(int width, int height) {
- Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height);
- mWidth = width;
- mHeight = height;
-
- if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
- && mSavedSurfaceTexture != null) {
- Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null.");
- mSavedSurfaceTexture.setDefaultBufferSize(width, height);
- }
- }
-
- /**
- * Creates the {@link Surface}, adjusting the {@link SurfaceTexture} buffer size.
- * @param width The width of the surface to create.
- * @param height The height of the surface to create.
- */
- private boolean createSurface(int width, int height) {
- Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture
- + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height);
- if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
- && mSavedSurfaceTexture != null) {
- mSavedSurfaceTexture.setDefaultBufferSize(width, height);
- mSavedSurface = new Surface(mSavedSurfaceTexture);
- return true;
- }
- return false;
- }
-
- /**
- * Handles a user clicking the surface, which is the trigger to toggle the full screen
- * Video UI.
- *
- * @param view The view receiving the click.
- */
- @Override
- public void onClick(View view) {
- if (mPresenter != null) {
- mPresenter.onSurfaceClick(mSurfaceId);
- } else {
- Log.e(this, "onClick: Presenter is null.");
- }
- }
-
- /**
- * Returns the dimensions of the surface.
- *
- * @return The dimensions of the surface.
- */
- public Point getSurfaceDimensions() {
- return new Point(mWidth, mHeight);
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
- }
-
- /**
- * Handles creation of the activity and initialization of the presenter.
- *
- * @param savedInstanceState The saved instance state.
- */
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape);
- Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
- getPresenter().init(getActivity());
-
- super.onActivityCreated(savedInstanceState);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
-
- final View view = inflater.inflate(R.layout.video_call_fragment, container, false);
-
- return view;
- }
-
- /**
- * Centers the display view vertically for portrait orientations. The view is centered within
- * the available space not occupied by the call card. This is a no-op for landscape mode.
- *
- * @param displayVideo The video view to center.
- */
- private void centerDisplayView(View displayVideo) {
- if (!mIsLandscape) {
- ViewGroup.LayoutParams p = displayVideo.getLayoutParams();
- int height = p.height;
-
- float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
- // If space beside call card is zeo, layout hasn't happened yet so there is no point
- // in attempting to center the view.
- if (Math.abs(spaceBesideCallCard - 0.0f) < 0.0001) {
- return;
- }
- float videoViewTranslation = height / 2 - spaceBesideCallCard / 2;
- displayVideo.setTranslationY(videoViewTranslation);
- }
- }
-
- /**
- * After creation of the fragment view, retrieves the required views.
- *
- * @param view The fragment view.
- * @param savedInstanceState The saved instance state.
- */
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse);
-
- mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- Log.d(this, "onStop:");
- }
-
- @Override
- public void onPause() {
- super.onPause();
- Log.d(this, "onPause:");
- getPresenter().cancelAutoFullScreen();
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- Log.d(this, "onDestroyView:");
- }
-
- /**
- * Creates the presenter for the {@link VideoCallFragment}.
- * @return The presenter instance.
- */
- @Override
- public VideoCallPresenter createPresenter() {
- Log.d(this, "createPresenter");
- VideoCallPresenter presenter = new VideoCallPresenter();
- onPresenterChanged(presenter);
- return presenter;
- }
-
- /**
- * @return The user interface for the presenter, which is this fragment.
- */
- @Override
- public VideoCallPresenter.VideoCallUi getUi() {
- return this;
- }
-
- /**
- * Inflate video surfaces.
- *
- * @param show {@code True} if the video surfaces should be shown.
- */
- private void inflateVideoUi(boolean show) {
- int visibility = show ? View.VISIBLE : View.GONE;
- getView().setVisibility(visibility);
-
- if (show) {
- inflateVideoCallViews();
- }
-
- if (mVideoViews != null) {
- mVideoViews.setVisibility(visibility);
- }
- }
-
- /**
- * Hides and shows the incoming video view and changes the outgoing video view's state based on
- * whether outgoing view is enabled or not.
- */
- @Override
- public void showVideoViews(boolean previewPaused, boolean showIncoming) {
- inflateVideoUi(true);
-
- View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo);
- if (incomingVideoView != null) {
- incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE);
- }
- if (mCameraOff != null) {
- mCameraOff.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
- }
- if (mPreviewPhoto != null) {
- mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
- }
- }
-
- /**
- * Hide all video views.
- */
- @Override
- public void hideVideoUi() {
- inflateVideoUi(false);
- }
-
- /**
- * Cleans up the video telephony surfaces. Used when the presenter indicates a change to an
- * audio-only state. Since the surfaces are static, it is important to ensure they are cleaned
- * up promptly.
- */
- @Override
- public void cleanupSurfaces() {
- Log.d(this, "cleanupSurfaces");
- if (sDisplaySurface != null) {
- sDisplaySurface.setDoneWithSurface();
- sDisplaySurface = null;
- }
- if (sPreviewSurface != null) {
- sPreviewSurface.setDoneWithSurface();
- sPreviewSurface = null;
- }
- sVideoSurfacesInUse = false;
- }
-
- @Override
- public ImageView getPreviewPhotoView() {
- return mPreviewPhoto;
- }
-
- /**
- * Adjusts the location of the video preview view by the specified offset.
- *
- * @param shiftUp {@code true} if the preview should shift up, {@code false} if it should shift
- * down.
- * @param offset The offset.
- */
- @Override
- public void adjustPreviewLocation(boolean shiftUp, int offset) {
- if (sPreviewSurface == null || mPreviewVideoContainer == null) {
- return;
- }
-
- // Set the position of the secondary call info card to its starting location.
- mPreviewVideoContainer.setTranslationY(shiftUp ? 0 : -offset);
-
- // Animate the secondary card info slide up/down as it appears and disappears.
- mPreviewVideoContainer.animate()
- .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
- .setDuration(mAnimationDuration)
- .translationY(shiftUp ? -offset : 0)
- .start();
- }
-
- private void onPresenterChanged(VideoCallPresenter presenter) {
- Log.d(this, "onPresenterChanged: Presenter=" + presenter);
- if (sDisplaySurface != null) {
- sDisplaySurface.resetPresenter(presenter);;
- }
- if (sPreviewSurface != null) {
- sPreviewSurface.resetPresenter(presenter);
- }
- }
-
- /**
- * @return {@code True} if the display video surface has been created.
- */
- @Override
- public boolean isDisplayVideoSurfaceCreated() {
- boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null;
- Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret);
- return ret;
- }
-
- /**
- * @return {@code True} if the preview video surface has been created.
- */
- @Override
- public boolean isPreviewVideoSurfaceCreated() {
- boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null;
- Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret);
- return ret;
- }
-
- /**
- * {@link android.view.Surface} on which incoming video for a video call is displayed.
- * {@code Null} until the video views {@link android.view.ViewStub} is inflated.
- */
- @Override
- public Surface getDisplayVideoSurface() {
- return sDisplaySurface == null ? null : sDisplaySurface.getSurface();
- }
-
- /**
- * {@link android.view.Surface} on which a preview of the outgoing video for a video call is
- * displayed. {@code Null} until the video views {@link android.view.ViewStub} is inflated.
- */
- @Override
- public Surface getPreviewVideoSurface() {
- return sPreviewSurface == null ? null : sPreviewSurface.getSurface();
- }
-
- /**
- * Changes the dimensions of the preview surface. Called when the dimensions change due to a
- * device orientation change.
- *
- * @param width The new width.
- * @param height The new height.
- */
- @Override
- public void setPreviewSize(int width, int height) {
- Log.d(this, "setPreviewSize: width=" + width + " height=" + height);
- if (sPreviewSurface != null) {
- TextureView preview = sPreviewSurface.getTextureView();
-
- if (preview == null ) {
- return;
- }
-
- // Set the dimensions of both the video surface and the FrameLayout containing it.
- ViewGroup.LayoutParams params = preview.getLayoutParams();
- params.width = width;
- params.height = height;
- preview.setLayoutParams(params);
-
- if (mPreviewVideoContainer != null) {
- ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams();
- containerParams.width = width;
- containerParams.height = height;
- mPreviewVideoContainer.setLayoutParams(containerParams);
- }
-
- // The width and height are interchanged outside of this method based on the current
- // orientation, so we can transform using "width", which will be either the width or
- // the height.
- Matrix transform = new Matrix();
- transform.setScale(-1, 1, width/2, 0);
- preview.setTransform(transform);
- }
- }
-
- /**
- * Sets the rotation of the preview surface. Called when the dimensions change due to a
- * device orientation change.
- *
- * Please note that the screen orientation passed in is subtracted from 360 to get the actual
- * preview rotation values.
- *
- * @param rotation The screen orientation. One of -
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
- */
- @Override
- public void setPreviewRotation(int orientation) {
- Log.d(this, "setPreviewRotation: orientation=" + orientation);
- if (sPreviewSurface != null) {
- TextureView preview = sPreviewSurface.getTextureView();
-
- if (preview == null ) {
- return;
- }
-
- preview.setRotation(orientation);
- }
- }
-
- @Override
- public void setPreviewSurfaceSize(int width, int height) {
- final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
- Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height +
- " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable);
- if (isPreviewSurfaceAvailable) {
- sPreviewSurface.setSurfaceDimensions(width, height);
- }
- }
-
- /**
- * returns UI's current orientation.
- */
- @Override
- public int getCurrentRotation() {
- try {
- return getActivity().getWindowManager().getDefaultDisplay().getRotation();
- } catch (Exception e) {
- Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e);
- }
- return ORIENTATION_UNKNOWN;
- }
-
- /**
- * Changes the dimensions of the display video surface. Called when the dimensions change due to
- * a peer resolution update
- *
- * @param width The new width.
- * @param height The new height.
- */
- @Override
- public void setDisplayVideoSize(int width, int height) {
- Log.v(this, "setDisplayVideoSize: width=" + width + " height=" + height);
- if (sDisplaySurface != null) {
- TextureView displayVideo = sDisplaySurface.getTextureView();
- if (displayVideo == null) {
- Log.e(this, "Display Video texture view is null. Bail out");
- return;
- }
- sDisplaySize = new Point(width, height);
- setSurfaceSizeAndTranslation(displayVideo, sDisplaySize);
- } else {
- Log.e(this, "Display Video Surface is null. Bail out");
- }
- }
-
- /**
- * Determines the size of the device screen.
- *
- * @return {@link Point} specifying the width and height of the screen.
- */
- @Override
- public Point getScreenSize() {
- // Get current screen size.
- Display display = getActivity().getWindowManager().getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
-
- return size;
- }
-
- /**
- * Determines the size of the preview surface.
- *
- * @return {@link Point} specifying the width and height of the preview surface.
- */
- @Override
- public Point getPreviewSize() {
- if (sPreviewSurface == null) {
- return null;
- }
- return sPreviewSurface.getSurfaceDimensions();
- }
-
- /**
- * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
- * and creates {@link VideoCallSurface} instances to track the surfaces.
- */
- private void inflateVideoCallViews() {
- Log.d(this, "inflateVideoCallViews");
- if (mVideoViews == null ) {
- mVideoViews = mVideoViewsStub.inflate();
- }
-
- if (mVideoViews != null) {
- mPreviewVideoContainer = mVideoViews.findViewById(R.id.previewVideoContainer);
- mCameraOff = mVideoViews.findViewById(R.id.previewCameraOff);
- mPreviewPhoto = (ImageView) mVideoViews.findViewById(R.id.previewProfilePhoto);
-
- TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);
-
- Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
- //If peer adjusted screen size is not available, set screen size to default display size
- Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize;
- setSurfaceSizeAndTranslation(displaySurface, screenSize);
-
- if (!sVideoSurfacesInUse) {
- // Where the video surfaces are not already in use (first time creating them),
- // setup new VideoCallSurface instances to track them.
- Log.d(this, " inflateVideoCallViews screenSize" + screenSize);
-
- sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY,
- (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
- screenSize.y);
- sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW,
- (TextureView) mVideoViews.findViewById(R.id.previewVideo));
- sVideoSurfacesInUse = true;
- } else {
- // In this case, the video surfaces are already in use (we are recreating the
- // Fragment after a destroy/create cycle resulting from a rotation.
- sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById(
- R.id.incomingVideo));
- sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById(
- R.id.previewVideo));
- }
-
- // Attempt to center the incoming video view, if it is in the layout.
- final ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
- observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- // Check if the layout includes the incoming video surface -- this will only be the
- // case for a video call.
- View displayVideo = mVideoViews.findViewById(R.id.incomingVideo);
- if (displayVideo != null) {
- centerDisplayView(displayVideo);
- }
- mIsLayoutComplete = true;
-
- // Remove the listener so we don't continually re-layout.
- ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
- if (observer.isAlive()) {
- observer.removeOnGlobalLayoutListener(this);
- }
- }
- });
- }
- }
-
- /**
- * Resizes a surface so that it has the same size as the full screen and so that it is
- * centered vertically below the call card.
- *
- * @param textureView The {@link TextureView} to resize and position.
- * @param size The size of the screen.
- */
- private void setSurfaceSizeAndTranslation(TextureView textureView, Point size) {
- // Set the surface to have that size.
- ViewGroup.LayoutParams params = textureView.getLayoutParams();
- params.width = size.x;
- params.height = size.y;
- textureView.setLayoutParams(params);
- Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" +
- mIsLayoutComplete + "IsLandscape=" + mIsLandscape);
-
- // It is only possible to center the display view if layout of the views has completed.
- // It is only after layout is complete that the dimensions of the Call Card has been
- // established, which is a prerequisite to centering the view.
- // Incoming video calls will center the view
- if (mIsLayoutComplete) {
- centerDisplayView(textureView);
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
deleted file mode 100644
index 06e3e4440..000000000
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2014 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.Context;
-import android.database.Cursor;
-import android.graphics.Point;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.ContactsContract;
-import android.telecom.Connection;
-import android.telecom.InCallService.VideoCall;
-import android.telecom.VideoProfile;
-import android.telecom.VideoProfile.CameraCapabilities;
-import android.view.Surface;
-import android.widget.ImageView;
-
-import com.android.contacts.common.ContactPhotoManager;
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.dialer.R;
-import com.android.incallui.InCallPresenter.InCallDetailsListener;
-import com.android.incallui.InCallPresenter.InCallOrientationListener;
-import com.android.incallui.InCallPresenter.InCallStateListener;
-import com.android.incallui.InCallPresenter.IncomingCallListener;
-import com.android.incallui.InCallVideoCallCallbackNotifier.SurfaceChangeListener;
-import com.android.incallui.InCallVideoCallCallbackNotifier.VideoEventListener;
-
-import java.util.Objects;
-
-/**
- * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling
- * surfaces based on other user interface events and incoming events from the
- * {@class VideoCallListener}.
- * <p>
- * When a call's video state changes to bi-directional video, the
- * {@link com.android.incallui.VideoCallPresenter} performs the following negotiation with the
- * telephony layer:
- * <ul>
- * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.</li>
- * <li>{@code VideoCallPresenter} creates the preview surface.</li>
- * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.</li>
- * <li>Telephony layer sends {@link CameraCapabilities}, including the
- * dimensions of the video for the current camera.</li>
- * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect
- * ratio of the camera.</li>
- * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.</li>
- * </ul>
- * <p>
- * When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both
- * surfaces.
- */
-public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi> implements
- IncomingCallListener, InCallOrientationListener, InCallStateListener,
- InCallDetailsListener, SurfaceChangeListener, VideoEventListener,
- InCallPresenter.InCallEventListener {
- public static final String TAG = "VideoCallPresenter";
-
- public static final boolean DEBUG = false;
-
- /**
- * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto
- * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit
- * the dialpad).
- */
- private Runnable mAutoFullscreenRunnable = new Runnable() {
- @Override
- public void run() {
- if (mAutoFullScreenPending && !InCallPresenter.getInstance().isDialpadVisible()
- && mIsVideoMode) {
-
- Log.v(this, "Automatically entering fullscreen mode.");
- InCallPresenter.getInstance().setFullScreen(true);
- mAutoFullScreenPending = false;
- } else {
- Log.v(this, "Skipping scheduled fullscreen mode.");
- }
- }
- };
-
- /**
- * Defines the state of the preview surface negotiation with the telephony layer.
- */
- private class PreviewSurfaceState {
- /**
- * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet
- * started.
- */
- private static final int NONE = 0;
-
- /**
- * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet
- * been received.
- */
- private static final int CAMERA_SET = 1;
-
- /**
- * The camera capabilties have been received from telephony, but the surface has not yet
- * been set on the {@link VideoCall}.
- */
- private static final int CAPABILITIES_RECEIVED = 2;
-
- /**
- * The surface has been set on the {@link VideoCall}.
- */
- private static final int SURFACE_SET = 3;
- }
-
- /**
- * The minimum width or height of the preview surface. Used when re-sizing the preview surface
- * to match the aspect ratio of the currently selected camera.
- */
- private float mMinimumVideoDimension;
-
- /**
- * The current context.
- */
- private Context mContext;
-
- /**
- * The call the video surfaces are currently related to
- */
- private Call mPrimaryCall;
-
- /**
- * The {@link VideoCall} used to inform the video telephony layer of changes to the video
- * surfaces.
- */
- private VideoCall mVideoCall;
-
- /**
- * Determines if the current UI state represents a video call.
- */
- private int mCurrentVideoState;
-
- /**
- * Call's current state
- */
- private int mCurrentCallState = Call.State.INVALID;
-
- /**
- * Determines the device orientation (portrait/lanscape).
- */
- private int mDeviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_0;
-
- /**
- * Tracks the state of the preview surface negotiation with the telephony layer.
- */
- private int mPreviewSurfaceState = PreviewSurfaceState.NONE;
-
- private static boolean mIsVideoMode = false;
-
- /**
- * Contact photo manager to retrieve cached contact photo information.
- */
- private ContactPhotoManager mContactPhotoManager = null;
-
- /**
- * The URI for the user's profile photo, or {@code null} if not specified.
- */
- private ContactInfoCache.ContactCacheEntry mProfileInfo = null;
-
- /**
- * UI thread handler used for delayed task execution.
- */
- private Handler mHandler;
-
- /**
- * Determines whether video calls should automatically enter full screen mode after
- * {@link #mAutoFullscreenTimeoutMillis} milliseconds.
- */
- private boolean mIsAutoFullscreenEnabled = false;
-
- /**
- * Determines the number of milliseconds after which a video call will automatically enter
- * fullscreen mode. Requires {@link #mIsAutoFullscreenEnabled} to be {@code true}.
- */
- private int mAutoFullscreenTimeoutMillis = 0;
-
- /**
- * Determines if the countdown is currently running to automatically enter full screen video
- * mode.
- */
- private boolean mAutoFullScreenPending = false;
-
- /**
- * Initializes the presenter.
- *
- * @param context The current context.
- */
- public void init(Context context) {
- mContext = context;
- mMinimumVideoDimension = mContext.getResources().getDimension(
- R.dimen.video_preview_small_dimension);
- mHandler = new Handler(Looper.getMainLooper());
- mIsAutoFullscreenEnabled = mContext.getResources()
- .getBoolean(R.bool.video_call_auto_fullscreen);
- mAutoFullscreenTimeoutMillis = mContext.getResources().getInteger(
- R.integer.video_call_auto_fullscreen_timeout);
- }
-
- /**
- * Called when the user interface is ready to be used.
- *
- * @param ui The Ui implementation that is now ready to be used.
- */
- @Override
- public void onUiReady(VideoCallUi ui) {
- super.onUiReady(ui);
- Log.d(this, "onUiReady:");
-
- // Do not register any listeners if video calling is not compatible to safeguard against
- // any accidental calls of video calling code.
- if (!CompatUtils.isVideoCompatible()) {
- return;
- }
-
- // Register for call state changes last
- InCallPresenter.getInstance().addListener(this);
- InCallPresenter.getInstance().addDetailsListener(this);
- InCallPresenter.getInstance().addIncomingCallListener(this);
- InCallPresenter.getInstance().addOrientationListener(this);
- // To get updates of video call details changes
- InCallPresenter.getInstance().addDetailsListener(this);
- InCallPresenter.getInstance().addInCallEventListener(this);
-
- // Register for surface and video events from {@link InCallVideoCallListener}s.
- InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
- InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this);
- mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
- mCurrentCallState = Call.State.INVALID;
-
- final InCallPresenter.InCallState inCallState =
- InCallPresenter.getInstance().getInCallState();
- onStateChange(inCallState, inCallState, CallList.getInstance());
- }
-
- /**
- * Called when the user interface is no longer ready to be used.
- *
- * @param ui The Ui implementation that is no longer ready to be used.
- */
- @Override
- public void onUiUnready(VideoCallUi ui) {
- super.onUiUnready(ui);
- Log.d(this, "onUiUnready:");
-
- if (!CompatUtils.isVideoCompatible()) {
- return;
- }
-
- cancelAutoFullScreen();
-
- InCallPresenter.getInstance().removeListener(this);
- InCallPresenter.getInstance().removeDetailsListener(this);
- InCallPresenter.getInstance().removeIncomingCallListener(this);
- InCallPresenter.getInstance().removeOrientationListener(this);
- InCallPresenter.getInstance().removeInCallEventListener(this);
-
- InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
- InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
- }
-
- /**
- * Handles the creation of a surface in the {@link VideoCallFragment}.
- *
- * @param surface The surface which was created.
- */
- public void onSurfaceCreated(int surface) {
- Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall);
- Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState);
- Log.d(this, "onSurfaceCreated presenter=" + this);
-
- final VideoCallUi ui = getUi();
- if (ui == null || mVideoCall == null) {
- Log.w(this, "onSurfaceCreated: Error bad state VideoCallUi=" + ui + " mVideoCall="
- + mVideoCall);
- return;
- }
-
- // If the preview surface has just been created and we have already received camera
- // capabilities, but not yet set the surface, we will set the surface now.
- if (surface == VideoCallFragment.SURFACE_PREVIEW ) {
- if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
- mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
- mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
- } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){
- enableCamera(mVideoCall, true);
- }
- } else if (surface == VideoCallFragment.SURFACE_DISPLAY) {
- mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
- }
- }
-
- /**
- * Handles structural changes (format or size) to a surface.
- *
- * @param surface The surface which changed.
- * @param format The new PixelFormat of the surface.
- * @param width The new width of the surface.
- * @param height The new height of the surface.
- */
- public void onSurfaceChanged(int surface, int format, int width, int height) {
- //Do stuff
- }
-
- /**
- * Handles the destruction of a surface in the {@link VideoCallFragment}.
- * Note: The surface is being released, that is, it is no longer valid.
- *
- * @param surface The surface which was destroyed.
- */
- public void onSurfaceReleased(int surface) {
- Log.d(this, "onSurfaceReleased: mSurfaceId=" + surface);
- if ( mVideoCall == null) {
- Log.w(this, "onSurfaceReleased: VideoCall is null. mSurfaceId=" +
- surface);
- return;
- }
-
- if (surface == VideoCallFragment.SURFACE_DISPLAY) {
- mVideoCall.setDisplaySurface(null);
- } else if (surface == VideoCallFragment.SURFACE_PREVIEW) {
- mVideoCall.setPreviewSurface(null);
- enableCamera(mVideoCall, false);
- }
- }
-
- /**
- * Called by {@link VideoCallFragment} when the surface is detached from UI (TextureView).
- * Note: The surface will be cached by {@link VideoCallFragment}, so we don't immediately
- * null out incoming video surface.
- * @see VideoCallPresenter#onSurfaceReleased(int)
- *
- * @param surface The surface which was detached.
- */
- public void onSurfaceDestroyed(int surface) {
- Log.d(this, "onSurfaceDestroyed: mSurfaceId=" + surface);
- if (mVideoCall == null) {
- return;
- }
-
- final boolean isChangingConfigurations =
- InCallPresenter.getInstance().isChangingConfigurations();
- Log.d(this, "onSurfaceDestroyed: isChangingConfigurations=" + isChangingConfigurations);
-
- if (surface == VideoCallFragment.SURFACE_PREVIEW) {
- if (!isChangingConfigurations) {
- enableCamera(mVideoCall, false);
- } else {
- Log.w(this, "onSurfaceDestroyed: Activity is being destroyed due "
- + "to configuration changes. Not closing the camera.");
- }
- }
- }
-
- /**
- * Handles clicks on the video surfaces by toggling full screen state.
- * Informs the {@link InCallPresenter} of the change so that it can inform the
- * {@link CallCardPresenter} of the change.
- *
- * @param surfaceId The video surface receiving the click.
- */
- public void onSurfaceClick(int surfaceId) {
- boolean isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
- Log.v(this, "toggleFullScreen = " + isFullscreen);
- }
-
- /**
- * Handles incoming calls.
- *
- * @param oldState The old in call state.
- * @param newState The new in call state.
- * @param call The call.
- */
- @Override
- public void onIncomingCall(InCallPresenter.InCallState oldState,
- InCallPresenter.InCallState newState, Call call) {
- // same logic should happen as with onStateChange()
- onStateChange(oldState, newState, CallList.getInstance());
- }
-
- /**
- * Handles state changes (including incoming calls)
- *
- * @param newState The in call state.
- * @param callList The call list.
- */
- @Override
- public void onStateChange(InCallPresenter.InCallState oldState,
- InCallPresenter.InCallState newState, CallList callList) {
- Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState +
- " isVideoMode=" + isVideoMode());
-
- if (newState == InCallPresenter.InCallState.NO_CALLS) {
- if (isVideoMode()) {
- exitVideoMode();
- }
-
- cleanupSurfaces();
- }
-
- // Determine the primary active call).
- Call primary = null;
-
- // Determine the call which is the focus of the user's attention. In the case of an
- // incoming call waiting call, the primary call is still the active video call, however
- // the determination of whether we should be in fullscreen mode is based on the type of the
- // incoming call, not the active video call.
- Call currentCall = null;
-
- if (newState == InCallPresenter.InCallState.INCOMING) {
- // We don't want to replace active video call (primary call)
- // with a waiting call, since user may choose to ignore/decline the waiting call and
- // this should have no impact on current active video call, that is, we should not
- // change the camera or UI unless the waiting VT call becomes active.
- primary = callList.getActiveCall();
- currentCall = callList.getIncomingCall();
- if (!VideoUtils.isActiveVideoCall(primary)) {
- primary = callList.getIncomingCall();
- }
- } else if (newState == InCallPresenter.InCallState.OUTGOING) {
- currentCall = primary = callList.getOutgoingCall();
- } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
- currentCall = primary = callList.getPendingOutgoingCall();
- } else if (newState == InCallPresenter.InCallState.INCALL) {
- currentCall = primary = callList.getActiveCall();
- }
-
- final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary);
- Log.d(this, "onStateChange primaryChanged=" + primaryChanged);
- Log.d(this, "onStateChange primary= " + primary);
- Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall);
- if (primaryChanged) {
- onPrimaryCallChanged(primary);
- } else if (mPrimaryCall != null) {
- updateVideoCall(primary);
- }
- updateCallCache(primary);
-
- // If the call context changed, potentially exit fullscreen or schedule auto enter of
- // fullscreen mode.
- // If the current call context is no longer a video call, exit fullscreen mode.
- maybeExitFullscreen(currentCall);
- // Schedule auto-enter of fullscreen mode if the current call context is a video call
- maybeAutoEnterFullscreen(currentCall);
- }
-
- /**
- * Handles a change to the fullscreen mode of the app.
- *
- * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise.
- */
- @Override
- public void onFullscreenModeChanged(boolean isFullscreenMode) {
- cancelAutoFullScreen();
- }
-
- /**
- * Handles changes to the visibility of the secondary caller info bar.
- *
- * @param isVisible {@code true} if the secondary caller info is showing, {@code false}
- * otherwise.
- * @param height the height of the secondary caller info bar.
- */
- @Override
- public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
- Log.d(this,
- "onSecondaryCallerInfoVisibilityChanged : isVisible = " + isVisible + " height = "
- + height);
- getUi().adjustPreviewLocation(isVisible /* shiftUp */, height);
- }
-
- private void checkForVideoStateChange(Call call) {
- final boolean isVideoCall = VideoUtils.isVideoCall(call);
- final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
-
- Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall
- + " hasVideoStateChanged=" + hasVideoStateChanged + " isVideoMode="
- + isVideoMode() + " previousVideoState: " +
- VideoProfile.videoStateToString(mCurrentVideoState) + " newVideoState: "
- + VideoProfile.videoStateToString(call.getVideoState()));
-
- if (!hasVideoStateChanged) {
- return;
- }
-
- updateCameraSelection(call);
-
- if (isVideoCall) {
- adjustVideoMode(call);
- } else if (isVideoMode()) {
- exitVideoMode();
- }
- }
-
- private void checkForCallStateChange(Call call) {
- final boolean isVideoCall = VideoUtils.isVideoCall(call);
- final boolean hasCallStateChanged = mCurrentCallState != call.getState();
-
- Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall
- + " hasCallStateChanged=" +
- hasCallStateChanged + " isVideoMode=" + isVideoMode());
-
- if (!hasCallStateChanged) {
- return;
- }
-
- if (isVideoCall) {
- final InCallCameraManager cameraManager = InCallPresenter.getInstance().
- getInCallCameraManager();
-
- String prevCameraId = cameraManager.getActiveCameraId();
- updateCameraSelection(call);
- String newCameraId = cameraManager.getActiveCameraId();
-
- if (!Objects.equals(prevCameraId, newCameraId) && VideoUtils.isActiveVideoCall(call)) {
- enableCamera(call.getVideoCall(), true);
- }
- }
-
- // Make sure we hide or show the video UI if needed.
- showVideoUi(call.getVideoState(), call.getState());
- }
-
- private void cleanupSurfaces() {
- final VideoCallUi ui = getUi();
- if (ui == null) {
- Log.w(this, "cleanupSurfaces");
- return;
- }
- ui.cleanupSurfaces();
- }
-
- private void onPrimaryCallChanged(Call newPrimaryCall) {
- final boolean isVideoCall = VideoUtils.isVideoCall(newPrimaryCall);
- final boolean isVideoMode = isVideoMode();
-
- Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode="
- + isVideoMode);
-
- if (!isVideoCall && isVideoMode) {
- // Terminate video mode if new primary call is not a video call
- // and we are currently in video mode.
- Log.d(this, "onPrimaryCallChanged: Exiting video mode...");
- exitVideoMode();
- } else if (isVideoCall) {
- Log.d(this, "onPrimaryCallChanged: Entering video mode...");
-
- updateCameraSelection(newPrimaryCall);
- adjustVideoMode(newPrimaryCall);
- }
- checkForOrientationAllowedChange(newPrimaryCall);
- }
-
- private boolean isVideoMode() {
- return mIsVideoMode;
- }
-
- private void updateCallCache(Call call) {
- if (call == null) {
- mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
- mCurrentCallState = Call.State.INVALID;
- mVideoCall = null;
- mPrimaryCall = null;
- } else {
- mCurrentVideoState = call.getVideoState();
- mVideoCall = call.getVideoCall();
- mCurrentCallState = call.getState();
- mPrimaryCall = call;
- }
- }
-
- /**
- * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in
- * changes to the video state.
- *
- * @param call The call for which the details changed.
- * @param details The new call details.
- */
- @Override
- public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
- Log.d(this, " onDetailsChanged call=" + call + " details=" + details + " mPrimaryCall="
- + mPrimaryCall);
- if (call == null) {
- return;
- }
- // If the details change is not for the currently active call no update is required.
- if (!call.equals(mPrimaryCall)) {
- Log.d(this, " onDetailsChanged: Details not for current active call so returning. ");
- return;
- }
-
- updateVideoCall(call);
-
- updateCallCache(call);
- }
-
- private void updateVideoCall(Call call) {
- checkForVideoCallChange(call);
- checkForVideoStateChange(call);
- checkForCallStateChange(call);
- checkForOrientationAllowedChange(call);
- }
-
- private void checkForOrientationAllowedChange(Call call) {
- InCallPresenter.getInstance().setInCallAllowsOrientationChange(
- VideoUtils.isVideoCall(call));
- }
-
- /**
- * Checks for a change to the video call and changes it if required.
- */
- private void checkForVideoCallChange(Call call) {
- final VideoCall videoCall = call.getTelecomCall().getVideoCall();
- Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall="
- + mVideoCall);
- if (!Objects.equals(videoCall, mVideoCall)) {
- changeVideoCall(call);
- }
- }
-
- /**
- * Handles a change to the video call. Sets the surfaces on the previous call to null and sets
- * the surfaces on the new video call accordingly.
- *
- * @param call The new video call.
- */
- private void changeVideoCall(Call call) {
- final VideoCall videoCall = call.getTelecomCall().getVideoCall();
- Log.d(this, "changeVideoCall to videoCall=" + videoCall + " mVideoCall=" + mVideoCall);
- // Null out the surfaces on the previous video call.
- if (mVideoCall != null) {
- // Log.d(this, "Null out the surfaces on the previous video call.");
- // mVideoCall.setDisplaySurface(null);
- // mVideoCall.setPreviewSurface(null);
- }
-
- final boolean hasChanged = mVideoCall == null && videoCall != null;
-
- mVideoCall = videoCall;
- if (mVideoCall == null || call == null) {
- Log.d(this, "Video call or primary call is null. Return");
- return;
- }
-
- if (VideoUtils.isVideoCall(call) && hasChanged) {
- adjustVideoMode(call);
- }
- }
-
- private static boolean isCameraRequired(int videoState) {
- return VideoProfile.isBidirectional(videoState)
- || VideoProfile.isTransmissionEnabled(videoState);
- }
-
- private boolean isCameraRequired() {
- return mPrimaryCall != null && isCameraRequired(mPrimaryCall.getVideoState());
- }
-
- /**
- * Adjusts the current video mode by setting up the preview and display surfaces as necessary.
- * Expected to be called whenever the video state associated with a call changes (e.g. a user
- * turns their camera on or off) to ensure the correct surfaces are shown/hidden.
- * TODO(vt): Need to adjust size and orientation of preview surface here.
- */
- private void adjustVideoMode(Call call) {
- VideoCall videoCall = call.getVideoCall();
- int newVideoState = call.getVideoState();
-
- Log.d(this, "adjustVideoMode videoCall= " + videoCall + " videoState: " + newVideoState);
- VideoCallUi ui = getUi();
- if (ui == null) {
- Log.e(this, "Error VideoCallUi is null so returning");
- return;
- }
-
- showVideoUi(newVideoState, call.getState());
-
- // Communicate the current camera to telephony and make a request for the camera
- // capabilities.
- if (videoCall != null) {
- if (ui.isDisplayVideoSurfaceCreated()) {
- Log.d(this, "Calling setDisplaySurface with " + ui.getDisplayVideoSurface());
- videoCall.setDisplaySurface(ui.getDisplayVideoSurface());
- }
-
- videoCall.setDeviceOrientation(mDeviceOrientation);
- enableCamera(videoCall, isCameraRequired(newVideoState));
- }
- int previousVideoState = mCurrentVideoState;
- mCurrentVideoState = newVideoState;
- mIsVideoMode = true;
-
- // adjustVideoMode may be called if we are already in a 1-way video state. In this case
- // we do not want to trigger auto-fullscreen mode.
- if (!VideoUtils.isVideoCall(previousVideoState) && VideoUtils.isVideoCall(newVideoState)) {
- maybeAutoEnterFullscreen(call);
- }
- }
-
- private void enableCamera(VideoCall videoCall, boolean isCameraRequired) {
- Log.d(this, "enableCamera: VideoCall=" + videoCall + " enabling=" + isCameraRequired);
- if (videoCall == null) {
- Log.w(this, "enableCamera: VideoCall is null.");
- return;
- }
-
- if (isCameraRequired) {
- InCallCameraManager cameraManager = InCallPresenter.getInstance().
- getInCallCameraManager();
- videoCall.setCamera(cameraManager.getActiveCameraId());
- mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
-
- videoCall.requestCameraCapabilities();
- } else {
- mPreviewSurfaceState = PreviewSurfaceState.NONE;
- videoCall.setCamera(null);
- }
- }
-
- /**
- * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio).
- */
- private void exitVideoMode() {
- Log.d(this, "exitVideoMode");
-
- showVideoUi(VideoProfile.STATE_AUDIO_ONLY, Call.State.ACTIVE);
- enableCamera(mVideoCall, false);
- InCallPresenter.getInstance().setFullScreen(false);
-
- mIsVideoMode = false;
- }
-
- /**
- * Based on the current video state and call state, show or hide the incoming and
- * outgoing video surfaces. The outgoing video surface is shown any time video is transmitting.
- * The incoming video surface is shown whenever the video is un-paused and active.
- *
- * @param videoState The video state.
- * @param callState The call state.
- */
- private void showVideoUi(int videoState, int callState) {
- VideoCallUi ui = getUi();
- if (ui == null) {
- Log.e(this, "showVideoUi, VideoCallUi is null returning");
- return;
- }
- boolean showIncomingVideo = showIncomingVideo(videoState, callState);
- boolean showOutgoingVideo = showOutgoingVideo(videoState);
- Log.v(this, "showVideoUi : showIncoming = " + showIncomingVideo + " showOutgoing = "
- + showOutgoingVideo);
- if (showIncomingVideo || showOutgoingVideo) {
- ui.showVideoViews(showOutgoingVideo, showIncomingVideo);
-
- if (VideoProfile.isReceptionEnabled(videoState)) {
- loadProfilePhotoAsync();
- }
- } else {
- ui.hideVideoUi();
- }
-
- InCallPresenter.getInstance().enableScreenTimeout(
- VideoProfile.isAudioOnly(videoState));
- }
-
- /**
- * Determines if the incoming video surface should be shown based on the current videoState and
- * callState. The video surface is shown when incoming video is not paused, the call is active,
- * and video reception is enabled.
- *
- * @param videoState The current video state.
- * @param callState The current call state.
- * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise.
- */
- public static boolean showIncomingVideo(int videoState, int callState) {
- if (!CompatUtils.isVideoCompatible()) {
- return false;
- }
-
- boolean isPaused = VideoProfile.isPaused(videoState);
- boolean isCallActive = callState == Call.State.ACTIVE;
-
- return !isPaused && isCallActive && VideoProfile.isReceptionEnabled(videoState);
- }
-
- /**
- * Determines if the outgoing video surface should be shown based on the current videoState.
- * The video surface is shown if video transmission is enabled.
- *
- * @param videoState The current video state.
- * @return {@code true} if the the outgoing video surface should be shown, {@code false}
- * otherwise.
- */
- public static boolean showOutgoingVideo(int videoState) {
- if (!CompatUtils.isVideoCompatible()) {
- return false;
- }
-
- return VideoProfile.isTransmissionEnabled(videoState);
- }
-
- /**
- * Handles peer video pause state changes.
- *
- * @param call The call which paused or un-pausedvideo transmission.
- * @param paused {@code True} when the video transmission is paused, {@code false} when video
- * transmission resumes.
- */
- @Override
- public void onPeerPauseStateChanged(Call call, boolean paused) {
- if (!call.equals(mPrimaryCall)) {
- return;
- }
-
- // TODO(vt): Show/hide the peer contact photo.
- }
-
- /**
- * Handles peer video dimension changes.
- *
- * @param call The call which experienced a peer video dimension change.
- * @param width The new peer video width .
- * @param height The new peer video height.
- */
- @Override
- public void onUpdatePeerDimensions(Call call, int width, int height) {
- Log.d(this, "onUpdatePeerDimensions: width= " + width + " height= " + height);
- VideoCallUi ui = getUi();
- if (ui == null) {
- Log.e(this, "VideoCallUi is null. Bail out");
- return;
- }
- if (!call.equals(mPrimaryCall)) {
- Log.e(this, "Current call is not equal to primary call. Bail out");
- return;
- }
-
- // Change size of display surface to match the peer aspect ratio
- if (width > 0 && height > 0) {
- setDisplayVideoSize(width, height);
- }
- }
-
- /**
- * Handles any video quality changes in the call.
- *
- * @param call The call which experienced a video quality change.
- * @param videoQuality The new video call quality.
- */
- @Override
- public void onVideoQualityChanged(Call call, int videoQuality) {
- // No-op
- }
-
- /**
- * Handles a change to the dimensions of the local camera. Receiving the camera capabilities
- * triggers the creation of the video
- *
- * @param call The call which experienced the camera dimension change.
- * @param width The new camera video width.
- * @param height The new camera video height.
- */
- @Override
- public void onCameraDimensionsChange(Call call, int width, int height) {
- Log.d(this, "onCameraDimensionsChange call=" + call + " width=" + width + " height="
- + height);
- VideoCallUi ui = getUi();
- if (ui == null) {
- Log.e(this, "onCameraDimensionsChange ui is null");
- return;
- }
-
- if (!call.equals(mPrimaryCall)) {
- Log.e(this, "Call is not primary call");
- return;
- }
-
- mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
- changePreviewDimensions(width, height);
-
- // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
- // If it not yet ready, it will be set when when creation completes.
- if (ui.isPreviewVideoSurfaceCreated()) {
- mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
- mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
- }
- }
-
- /**
- * Changes the dimensions of the preview surface.
- *
- * @param width The new width.
- * @param height The new height.
- */
- private void changePreviewDimensions(int width, int height) {
- VideoCallUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- // Resize the surface used to display the preview video
- ui.setPreviewSurfaceSize(width, height);
-
- // Configure the preview surface to the correct aspect ratio.
- float aspectRatio = 1.0f;
- if (width > 0 && height > 0) {
- aspectRatio = (float) width / (float) height;
- }
-
- // Resize the textureview housing the preview video and rotate it appropriately based on
- // the device orientation
- setPreviewSize(mDeviceOrientation, aspectRatio);
- }
-
- /**
- * Called when call session event is raised.
- *
- * @param event The call session event.
- */
- @Override
- public void onCallSessionEvent(int event) {
- StringBuilder sb = new StringBuilder();
- sb.append("onCallSessionEvent = ");
-
- switch (event) {
- case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
- sb.append("rx_pause");
- break;
- case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
- sb.append("rx_resume");
- break;
- case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
- sb.append("camera_failure");
- break;
- case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
- sb.append("camera_ready");
- break;
- default:
- sb.append("unknown event = ");
- sb.append(event);
- break;
- }
- Log.d(this, sb.toString());
- }
-
- /**
- * Handles a change to the call data usage
- *
- * @param dataUsage call data usage value
- */
- @Override
- public void onCallDataUsageChange(long dataUsage) {
- Log.d(this, "onCallDataUsageChange dataUsage=" + dataUsage);
- }
-
- /**
- * Handles changes to the device orientation.
- * @param orientation The screen orientation of the device (one of:
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
- */
- @Override
- public void onDeviceOrientationChanged(int orientation) {
- mDeviceOrientation = orientation;
-
- VideoCallUi ui = getUi();
- if (ui == null) {
- Log.e(this, "onDeviceOrientationChanged: VideoCallUi is null");
- return;
- }
-
- Point previewDimensions = ui.getPreviewSize();
- if (previewDimensions == null) {
- return;
- }
- Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation + " size: "
- + previewDimensions);
- changePreviewDimensions(previewDimensions.x, previewDimensions.y);
-
- ui.setPreviewRotation(mDeviceOrientation);
- }
-
- /**
- * Sets the preview surface size based on the current device orientation.
- * See: {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
- *
- * @param orientation The device orientation
- * @param aspectRatio The aspect ratio of the camera (width / height).
- */
- private void setPreviewSize(int orientation, float aspectRatio) {
- VideoCallUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- int height;
- int width;
-
- if (orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_90 ||
- orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_270) {
- width = (int) (mMinimumVideoDimension * aspectRatio);
- height = (int) mMinimumVideoDimension;
- } else {
- // Portrait or reverse portrait orientation.
- width = (int) mMinimumVideoDimension;
- height = (int) (mMinimumVideoDimension * aspectRatio);
- }
- ui.setPreviewSize(width, height);
- }
-
- /**
- * Sets the display video surface size based on peer width and height
- *
- * @param width peer width
- * @param height peer height
- */
- private void setDisplayVideoSize(int width, int height) {
- Log.v(this, "setDisplayVideoSize: Received peer width=" + width + " height=" + height);
- VideoCallUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- // Get current display size
- Point size = ui.getScreenSize();
- Log.v(this, "setDisplayVideoSize: windowmgr width=" + size.x
- + " windowmgr height=" + size.y);
- if (size.y * width > size.x * height) {
- // current display height is too much. Correct it
- size.y = (int) (size.x * height / width);
- } else if (size.y * width < size.x * height) {
- // current display width is too much. Correct it
- size.x = (int) (size.y * width / height);
- }
- ui.setDisplayVideoSize(size.x, size.y);
- }
-
- /**
- * Exits fullscreen mode if the current call context has changed to a non-video call.
- *
- * @param call The call.
- */
- protected void maybeExitFullscreen(Call call) {
- if (call == null) {
- return;
- }
-
- if (!VideoUtils.isVideoCall(call) || call.getState() == Call.State.INCOMING) {
- InCallPresenter.getInstance().setFullScreen(false);
- }
- }
-
- /**
- * Schedules auto-entering of fullscreen mode.
- * Will not enter full screen mode if any of the following conditions are met:
- * 1. No call
- * 2. Call is not active
- * 3. Call is not video call
- * 4. Already in fullscreen mode
- * 5. The current video state is not bi-directional (if the remote party stops transmitting,
- * the user's contact photo would dominate in fullscreen mode).
- *
- * @param call The current call.
- */
- protected void maybeAutoEnterFullscreen(Call call) {
- if (!mIsAutoFullscreenEnabled) {
- return;
- }
-
- if (call == null || (
- call != null && (call.getState() != Call.State.ACTIVE ||
- !VideoUtils.isVideoCall(call)) ||
- InCallPresenter.getInstance().isFullscreen()) ||
- !VideoUtils.isBidirectionalVideoCall(call)) {
- // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
- cancelAutoFullScreen();
- return;
- }
-
- if (mAutoFullScreenPending) {
- Log.v(this, "maybeAutoEnterFullscreen : already pending.");
- return;
- }
- Log.v(this, "maybeAutoEnterFullscreen : scheduled");
- mAutoFullScreenPending = true;
- mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis);
- }
-
- /**
- * Cancels pending auto fullscreen mode.
- */
- public void cancelAutoFullScreen() {
- if (!mAutoFullScreenPending) {
- Log.v(this, "cancelAutoFullScreen : none pending.");
- return;
- }
- Log.v(this, "cancelAutoFullScreen : cancelling pending");
- mAutoFullScreenPending = false;
- }
-
- private static void updateCameraSelection(Call call) {
- Log.d(TAG, "updateCameraSelection: call=" + call);
- Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call));
-
- final Call activeCall = CallList.getInstance().getActiveCall();
- int cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
-
- // this function should never be called with null call object, however if it happens we
- // should handle it gracefully.
- if (call == null) {
- cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
- com.android.incallui.Log.e(TAG, "updateCameraSelection: Call object is null."
- + " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
- }
-
- // Clear camera direction if this is not a video call.
- else if (VideoUtils.isAudioCall(call)) {
- cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
- call.getVideoSettings().setCameraDir(cameraDir);
- }
-
- // If this is a waiting video call, default to active call's camera,
- // since we don't want to change the current camera for waiting call
- // without user's permission.
- else if (VideoUtils.isVideoCall(activeCall) && VideoUtils.isIncomingVideoCall(call)) {
- cameraDir = activeCall.getVideoSettings().getCameraDir();
- }
-
- // Infer the camera direction from the video state and store it,
- // if this is an outgoing video call.
- else if (VideoUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call) ) {
- cameraDir = toCameraDirection(call.getVideoState());
- call.getVideoSettings().setCameraDir(cameraDir);
- }
-
- // Use the stored camera dir if this is an outgoing video call for which camera direction
- // is set.
- else if (VideoUtils.isOutgoingVideoCall(call)) {
- cameraDir = call.getVideoSettings().getCameraDir();
- }
-
- // Infer the camera direction from the video state and store it,
- // if this is an active video call and camera direction is not set.
- else if (VideoUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
- cameraDir = toCameraDirection(call.getVideoState());
- call.getVideoSettings().setCameraDir(cameraDir);
- }
-
- // Use the stored camera dir if this is an active video call for which camera direction
- // is set.
- else if (VideoUtils.isActiveVideoCall(call)) {
- cameraDir = call.getVideoSettings().getCameraDir();
- }
-
- // For all other cases infer the camera direction but don't store it in the call object.
- else {
- cameraDir = toCameraDirection(call.getVideoState());
- }
-
- com.android.incallui.Log.d(TAG, "updateCameraSelection: Setting camera direction to " +
- cameraDir + " Call=" + call);
- final InCallCameraManager cameraManager = InCallPresenter.getInstance().
- getInCallCameraManager();
- cameraManager.setUseFrontFacingCamera(cameraDir ==
- Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING);
- }
-
- private static int toCameraDirection(int videoState) {
- return VideoProfile.isTransmissionEnabled(videoState) &&
- !VideoProfile.isBidirectional(videoState)
- ? Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING
- : Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING;
- }
-
- private static boolean isCameraDirectionSet(Call call) {
- return VideoUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir()
- != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
- }
-
- private static String toSimpleString(Call call) {
- return call == null ? null : call.toSimpleString();
- }
-
- /**
- * Starts an asynchronous load of the user's profile photo.
- */
- public void loadProfilePhotoAsync() {
- final VideoCallUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- /**
- * Performs asynchronous load of the user profile information.
- *
- * @param params The parameters of the task.
- *
- * @return {@code null}.
- */
- @Override
- protected Void doInBackground(Void... params) {
- if (mProfileInfo == null) {
- // Try and read the photo URI from the local profile.
- mProfileInfo = new ContactInfoCache.ContactCacheEntry();
- final Cursor cursor = mContext.getContentResolver().query(
- ContactsContract.Profile.CONTENT_URI, new String[]{
- ContactsContract.CommonDataKinds.Phone._ID,
- ContactsContract.CommonDataKinds.Phone.PHOTO_URI,
- ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY,
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_ALTERNATIVE
- }, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- mProfileInfo.lookupKey = cursor.getString(cursor.getColumnIndex(
- ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY));
- String photoUri = cursor.getString(cursor.getColumnIndex(
- ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
- mProfileInfo.displayPhotoUri = photoUri == null ? null
- : Uri.parse(photoUri);
- mProfileInfo.namePrimary = cursor.getString(cursor.getColumnIndex(
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
- mProfileInfo.nameAlternative = cursor.getString(
- cursor.getColumnIndex(ContactsContract.CommonDataKinds
- .Phone.DISPLAY_NAME_ALTERNATIVE));
- }
- } finally {
- cursor.close();
- }
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- // If user profile information was found, issue an async request to load the user's
- // profile photo.
- if (mProfileInfo != null) {
- if (mContactPhotoManager == null) {
- mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
- }
- ContactPhotoManager.DefaultImageRequest imageRequest = (mProfileInfo != null)
- ? null :
- new ContactPhotoManager.DefaultImageRequest(mProfileInfo.namePrimary,
- mProfileInfo.lookupKey, false /* isCircularPhoto */);
-
- ImageView photoView = ui.getPreviewPhotoView();
- if (photoView == null) {
- return;
- }
- mContactPhotoManager.loadDirectoryPhoto(photoView,
- mProfileInfo.displayPhotoUri,
- false /* darkTheme */, false /* isCircular */, imageRequest);
- }
- }
- };
-
- task.execute();
- }
-
- /**
- * Defines the VideoCallUI interactions.
- */
- public interface VideoCallUi extends Ui {
- void showVideoViews(boolean showPreview, boolean showIncoming);
- void hideVideoUi();
- boolean isDisplayVideoSurfaceCreated();
- boolean isPreviewVideoSurfaceCreated();
- Surface getDisplayVideoSurface();
- Surface getPreviewVideoSurface();
- int getCurrentRotation();
- void setPreviewSize(int width, int height);
- void setPreviewSurfaceSize(int width, int height);
- void setDisplayVideoSize(int width, int height);
- Point getScreenSize();
- Point getPreviewSize();
- void cleanupSurfaces();
- ImageView getPreviewPhotoView();
- void adjustPreviewLocation(boolean shiftUp, int offset);
- void setPreviewRotation(int orientation);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java
deleted file mode 100644
index fb873500e..000000000
--- a/InCallUI/src/com/android/incallui/VideoPauseController.java
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * Copyright (C) 2015 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 com.android.incallui.Call.State;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.InCallPresenter.InCallStateListener;
-import com.android.incallui.InCallPresenter.IncomingCallListener;
-import com.android.incallui.InCallVideoCallCallbackNotifier.SessionModificationListener;
-import com.google.common.base.Preconditions;
-
-import android.telecom.VideoProfile;
-
-/**
- * This class is responsible for generating video pause/resume requests when the InCall UI is sent
- * to the background and subsequently brought back to the foreground.
- */
-class VideoPauseController implements InCallStateListener, IncomingCallListener {
- private static final String TAG = "VideoPauseController";
-
- /**
- * Keeps track of the current active/foreground call.
- */
- private class CallContext {
- public CallContext(Call call) {
- Preconditions.checkNotNull(call);
- update(call);
- }
-
- public void update(Call call) {
- mCall = Preconditions.checkNotNull(call);
- mState = call.getState();
- mVideoState = call.getVideoState();
- }
-
- public int getState() {
- return mState;
- }
-
- public int getVideoState() {
- return mVideoState;
- }
-
- public String toString() {
- return String.format("CallContext {CallId=%s, State=%s, VideoState=%d}",
- mCall.getId(), mState, mVideoState);
- }
-
- public Call getCall() {
- return mCall;
- }
-
- private int mState = State.INVALID;
- private int mVideoState;
- private Call mCall;
- }
-
- private InCallPresenter mInCallPresenter;
- private static VideoPauseController sVideoPauseController;
-
- /**
- * The current call context, if applicable.
- */
- private CallContext mPrimaryCallContext = null;
-
- /**
- * Tracks whether the application is in the background. {@code True} if the application is in
- * the background, {@code false} otherwise.
- */
- private boolean mIsInBackground = false;
-
- /**
- * Singleton accessor for the {@link VideoPauseController}.
- * @return Singleton instance of the {@link VideoPauseController}.
- */
- /*package*/
- static synchronized VideoPauseController getInstance() {
- if (sVideoPauseController == null) {
- sVideoPauseController = new VideoPauseController();
- }
- return sVideoPauseController;
- }
-
- /**
- * Configures the {@link VideoPauseController} to listen to call events. Configured via the
- * {@link com.android.incallui.InCallPresenter}.
- *
- * @param inCallPresenter The {@link com.android.incallui.InCallPresenter}.
- */
- public void setUp(InCallPresenter inCallPresenter) {
- log("setUp");
- mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
- mInCallPresenter.addListener(this);
- mInCallPresenter.addIncomingCallListener(this);
- }
-
- /**
- * Cleans up the {@link VideoPauseController} by removing all listeners and clearing its
- * internal state. Called from {@link com.android.incallui.InCallPresenter}.
- */
- public void tearDown() {
- log("tearDown...");
- mInCallPresenter.removeListener(this);
- mInCallPresenter.removeIncomingCallListener(this);
- clear();
- }
-
- /**
- * Clears the internal state for the {@link VideoPauseController}.
- */
- private void clear() {
- mInCallPresenter = null;
- mPrimaryCallContext = null;
- mIsInBackground = false;
- }
-
- /**
- * Handles changes in the {@link InCallState}. Triggers pause and resumption of video for the
- * current foreground call.
- *
- * @param oldState The previous {@link InCallState}.
- * @param newState The current {@link InCallState}.
- * @param callList List of current call.
- */
- @Override
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
- log("onStateChange, OldState=" + oldState + " NewState=" + newState);
-
- Call call = null;
- if (newState == InCallState.INCOMING) {
- call = callList.getIncomingCall();
- } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
- call = callList.getWaitingForAccountCall();
- } else if (newState == InCallState.PENDING_OUTGOING) {
- call = callList.getPendingOutgoingCall();
- } else if (newState == InCallState.OUTGOING) {
- call = callList.getOutgoingCall();
- } else {
- call = callList.getActiveCall();
- }
-
- boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
- boolean canVideoPause = VideoUtils.canVideoPause(call);
- log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
- log("onStateChange, canVideoPause=" + canVideoPause);
- log("onStateChange, IsInBackground=" + mIsInBackground);
-
- if (hasPrimaryCallChanged) {
- onPrimaryCallChanged(call);
- return;
- }
-
- if (isDialing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
- // Bring UI to foreground if outgoing request becomes active while UI is in
- // background.
- bringToForeground();
- } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
- // Bring UI to foreground if VoLTE call becomes active while UI is in
- // background.
- bringToForeground();
- }
-
- updatePrimaryCallContext(call);
- }
-
- /**
- * Handles a change to the primary call.
- * <p>
- * Reject incoming or hangup dialing call: Where the previous call was an incoming call or a
- * call in dialing state, resume the new primary call.
- * Call swap: Where the new primary call is incoming, pause video on the previous primary call.
- *
- * @param call The new primary call.
- */
- private void onPrimaryCallChanged(Call call) {
- log("onPrimaryCallChanged: New call = " + call);
- log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
- log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
-
- Preconditions.checkState(!areSame(call, mPrimaryCallContext));
- final boolean canVideoPause = VideoUtils.canVideoPause(call);
-
- if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext) ||
- (call != null && VideoProfile.isPaused(call.getVideoState())))
- && canVideoPause && !mIsInBackground) {
- // Send resume request for the active call, if user rejects incoming call, ends dialing
- // call, or the call was previously in a paused state and UI is in the foreground.
- sendRequest(call, true);
- } else if (isIncomingCall(call) && canVideoPause(mPrimaryCallContext)) {
- // Send pause request if there is an active video call, and we just received a new
- // incoming call.
- sendRequest(mPrimaryCallContext.getCall(), false);
- }
-
- updatePrimaryCallContext(call);
- }
-
- /**
- * Handles new incoming calls by triggering a change in the primary call.
- *
- * @param oldState the old {@link InCallState}.
- * @param newState the new {@link InCallState}.
- * @param call the incoming call.
- */
- @Override
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
- log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call);
-
- if (areSame(call, mPrimaryCallContext)) {
- return;
- }
-
- onPrimaryCallChanged(call);
- }
-
- /**
- * Caches a reference to the primary call and stores its previous state.
- *
- * @param call The new primary call.
- */
- private void updatePrimaryCallContext(Call call) {
- if (call == null) {
- mPrimaryCallContext = null;
- } else if (mPrimaryCallContext != null) {
- mPrimaryCallContext.update(call);
- } else {
- mPrimaryCallContext = new CallContext(call);
- }
- }
-
- /**
- * Called when UI goes in/out of the foreground.
- * @param showing true if UI is in the foreground, false otherwise.
- */
- public void onUiShowing(boolean showing) {
- // Only send pause/unpause requests if we are in the INCALL state.
- if (mInCallPresenter == null) {
- return;
- }
- final boolean isInCall = mInCallPresenter.getInCallState() == InCallState.INCALL;
- if (showing) {
- onResume(isInCall);
- } else {
- onPause(isInCall);
- }
- }
-
- /**
- * Called when UI is brought to the foreground. Sends a session modification request to resume
- * the outgoing video.
- * @param isInCall true if phone state is INCALL, false otherwise
- */
- private void onResume(boolean isInCall) {
- log("onResume");
-
- mIsInBackground = false;
- if (canVideoPause(mPrimaryCallContext) && isInCall) {
- sendRequest(mPrimaryCallContext.getCall(), true);
- } else {
- log("onResume. Ignoring...");
- }
- }
-
- /**
- * Called when UI is sent to the background. Sends a session modification request to pause the
- * outgoing video.
- * @param isInCall true if phone state is INCALL, false otherwise
- */
- private void onPause(boolean isInCall) {
- log("onPause");
-
- mIsInBackground = true;
- if (canVideoPause(mPrimaryCallContext) && isInCall) {
- sendRequest(mPrimaryCallContext.getCall(), false);
- } else {
- log("onPause, Ignoring...");
- }
- }
-
- private void bringToForeground() {
- if (mInCallPresenter != null) {
- log("Bringing UI to foreground");
- mInCallPresenter.bringToForeground(false);
- } else {
- loge("InCallPresenter is null. Cannot bring UI to foreground");
- }
- }
-
- /**
- * Sends Pause/Resume request.
- *
- * @param call Call to be paused/resumed.
- * @param resume If true resume request will be sent, otherwise pause request.
- */
- private void sendRequest(Call call, boolean resume) {
- // Check if this call supports pause/un-pause.
- if (!call.can(android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO)) {
- return;
- }
-
- if (resume) {
- log("sending resume request, call=" + call);
- call.getVideoCall()
- .sendSessionModifyRequest(VideoUtils.makeVideoUnPauseProfile(call));
- } else {
- log("sending pause request, call=" + call);
- call.getVideoCall().sendSessionModifyRequest(VideoUtils.makeVideoPauseProfile(call));
- }
- }
-
- /**
- * Determines if a given call is the same one stored in a {@link CallContext}.
- *
- * @param call The call.
- * @param callContext The call context.
- * @return {@code true} if the {@link Call} is the same as the one referenced in the
- * {@link CallContext}.
- */
- private static boolean areSame(Call call, CallContext callContext) {
- if (call == null && callContext == null) {
- return true;
- } else if (call == null || callContext == null) {
- return false;
- }
- return call.equals(callContext.getCall());
- }
-
- /**
- * Determines if a video call can be paused. Only a video call which is active can be paused.
- *
- * @param callContext The call context to check.
- * @return {@code true} if the call is an active video call.
- */
- private static boolean canVideoPause(CallContext callContext) {
- return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE;
- }
-
- /**
- * Determines if a call referenced by a {@link CallContext} is a video call.
- *
- * @param callContext The call context.
- * @return {@code true} if the call is a video call, {@code false} otherwise.
- */
- private static boolean isVideoCall(CallContext callContext) {
- return callContext != null && VideoUtils.isVideoCall(callContext.getVideoState());
- }
-
- /**
- * Determines if call is in incoming/waiting state.
- *
- * @param call The call context.
- * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
- */
- private static boolean isIncomingCall(CallContext call) {
- return call != null && isIncomingCall(call.getCall());
- }
-
- /**
- * Determines if a call is in incoming/waiting state.
- *
- * @param call The call.
- * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
- */
- private static boolean isIncomingCall(Call call) {
- return call != null && (call.getState() == Call.State.CALL_WAITING
- || call.getState() == Call.State.INCOMING);
- }
-
- /**
- * Determines if a call is dialing.
- *
- * @param call The call context.
- * @return {@code true} if the call is dialing, {@code false} otherwise.
- */
- private static boolean isDialing(CallContext call) {
- return call != null && Call.State.isDialing(call.getState());
- }
-
- /**
- * Determines if a call is holding.
- *
- * @param call The call context.
- * @return {@code true} if the call is holding, {@code false} otherwise.
- */
- private static boolean isHolding(CallContext call) {
- return call != null && call.getState() == Call.State.ONHOLD;
- }
-
- /**
- * Logs a debug message.
- *
- * @param msg The message.
- */
- private void log(String msg) {
- Log.d(this, TAG + msg);
- }
-
- /**
- * Logs an error message.
- *
- * @param msg The message.
- */
- private void loge(String msg) {
- Log.e(this, TAG + msg);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/VideoUtils.java b/InCallUI/src/com/android/incallui/VideoUtils.java
deleted file mode 100644
index a2eb8bcf2..000000000
--- a/InCallUI/src/com/android/incallui/VideoUtils.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2015 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.telecom.VideoProfile;
-
-import com.android.contacts.common.compat.CompatUtils;
-
-import com.google.common.base.Preconditions;
-
-public class VideoUtils {
-
- public static boolean isVideoCall(Call call) {
- return call != null && isVideoCall(call.getVideoState());
- }
-
- public static boolean isVideoCall(int videoState) {
- if (!CompatUtils.isVideoCompatible()) {
- return false;
- }
-
- return VideoProfile.isTransmissionEnabled(videoState)
- || VideoProfile.isReceptionEnabled(videoState);
- }
-
- public static boolean isBidirectionalVideoCall(Call call) {
- if (!CompatUtils.isVideoCompatible()) {
- return false;
- }
-
- return VideoProfile.isBidirectional(call.getVideoState());
- }
-
- public static boolean isTransmissionEnabled(Call call) {
- if (!CompatUtils.isVideoCompatible()) {
- return false;
- }
-
- return VideoProfile.isTransmissionEnabled(call.getVideoState());
- }
-
- public static boolean isIncomingVideoCall(Call call) {
- if (!VideoUtils.isVideoCall(call)) {
- return false;
- }
- final int state = call.getState();
- return (state == Call.State.INCOMING) || (state == Call.State.CALL_WAITING);
- }
-
- public static boolean isActiveVideoCall(Call call) {
- return VideoUtils.isVideoCall(call) && call.getState() == Call.State.ACTIVE;
- }
-
- public static boolean isOutgoingVideoCall(Call call) {
- if (!VideoUtils.isVideoCall(call)) {
- return false;
- }
- final int state = call.getState();
- return Call.State.isDialing(state) || state == Call.State.CONNECTING
- || state == Call.State.SELECT_PHONE_ACCOUNT;
- }
-
- public static boolean isAudioCall(Call call) {
- if (!CompatUtils.isVideoCompatible()) {
- return true;
- }
-
- return call != null && VideoProfile.isAudioOnly(call.getVideoState());
- }
-
- // TODO (ims-vt) Check if special handling is needed for CONF calls.
- public static boolean canVideoPause(Call call) {
- return isVideoCall(call) && call.getState() == Call.State.ACTIVE;
- }
-
- public static VideoProfile makeVideoPauseProfile(Call call) {
- Preconditions.checkNotNull(call);
- Preconditions.checkState(!VideoProfile.isAudioOnly(call.getVideoState()));
- return new VideoProfile(getPausedVideoState(call.getVideoState()));
- }
-
- public static VideoProfile makeVideoUnPauseProfile(Call call) {
- Preconditions.checkNotNull(call);
- return new VideoProfile(getUnPausedVideoState(call.getVideoState()));
- }
-
- public static int getUnPausedVideoState(int videoState) {
- return videoState & (~VideoProfile.STATE_PAUSED);
- }
-
- public static int getPausedVideoState(int videoState) {
- return videoState | VideoProfile.STATE_PAUSED;
- }
-
-}
diff --git a/InCallUI/src/com/android/incallui/async/PausableExecutor.java b/InCallUI/src/com/android/incallui/async/PausableExecutor.java
deleted file mode 100644
index 1b8201a79..000000000
--- a/InCallUI/src/com/android/incallui/async/PausableExecutor.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.async;
-
-import com.android.contacts.common.testing.NeededForTesting;
-
-import java.util.concurrent.Executor;
-
-/**
- * Executor that can be used to easily synchronize testing and production code. Production code
- * should call {@link #milestone()} at points in the code where the state of the system is worthy of
- * testing. In a test scenario, this method will pause execution until the test acknowledges the
- * milestone through the use of {@link #ackMilestoneForTesting()}.
- */
-public interface PausableExecutor extends Executor {
-
- /**
- * Method called from asynchronous production code to inform this executor that it has
- * reached a point that puts the system into a state worth testing. TestableExecutors intended
- * for use in a testing environment should cause the calling thread to block. In the production
- * environment this should be a no-op.
- */
- void milestone();
-
- /**
- * Method called from the test code to inform this executor that the state of the production
- * system at the current milestone has been sufficiently tested. Every milestone must be
- * acknowledged.
- */
- @NeededForTesting
- void ackMilestoneForTesting();
-
- /**
- * Method called from the test code to inform this executor that the tests are finished with all
- * milestones. Future calls to {@link #milestone()} or {@link #awaitMilestoneForTesting()}
- * should return immediately.
- */
- @NeededForTesting
- void ackAllMilestonesForTesting();
-
- /**
- * Method called from the test code to block until a milestone has been reached in the
- * production code.
- */
- @NeededForTesting
- void awaitMilestoneForTesting() throws InterruptedException;
-}
diff --git a/InCallUI/src/com/android/incallui/async/PausableExecutorImpl.java b/InCallUI/src/com/android/incallui/async/PausableExecutorImpl.java
deleted file mode 100644
index 15900e57b..000000000
--- a/InCallUI/src/com/android/incallui/async/PausableExecutorImpl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.async;
-
-import java.util.concurrent.Executors;
-
-/**
- * {@link PausableExecutor} intended for use in production environments.
- */
-public class PausableExecutorImpl implements PausableExecutor {
-
- @Override
- public void milestone() {}
-
- @Override
- public void ackMilestoneForTesting() {}
-
- @Override
- public void ackAllMilestonesForTesting() {}
-
- @Override
- public void awaitMilestoneForTesting() {}
-
- @Override
- public void execute(Runnable command) {
- Executors.newSingleThreadExecutor().execute(command);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ringtone/DialerRingtoneManager.java b/InCallUI/src/com/android/incallui/ringtone/DialerRingtoneManager.java
deleted file mode 100644
index 39844e5a2..000000000
--- a/InCallUI/src/com/android/incallui/ringtone/DialerRingtoneManager.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.ringtone;
-
-import com.google.common.base.Preconditions;
-
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.provider.Settings;
-import android.support.annotation.Nullable;
-
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.incallui.Call;
-import com.android.incallui.Call.State;
-import com.android.incallui.CallList;
-
-/**
- * Class that determines when ringtones should be played and can play the call waiting tone when
- * necessary.
- */
-public class DialerRingtoneManager {
-
- /*
- * Flag used to determine if the Dialer is responsible for playing ringtones for incoming calls.
- * Once we're ready to enable Dialer Ringing, these flags should be removed.
- */
- private static final boolean IS_DIALER_RINGING_ENABLED = false;
- private Boolean mIsDialerRingingEnabledForTesting;
-
- private final InCallTonePlayer mInCallTonePlayer;
- private final CallList mCallList;
-
- /**
- * Creates the DialerRingtoneManager with the given {@link InCallTonePlayer}.
- *
- * @param inCallTonePlayer the tone player used to play in-call tones.
- * @param callList the CallList used to check for {@link State#CALL_WAITING}
- * @throws NullPointerException if inCallTonePlayer or callList are null
- */
- public DialerRingtoneManager(InCallTonePlayer inCallTonePlayer, CallList callList) {
- mInCallTonePlayer = Preconditions.checkNotNull(inCallTonePlayer);
- mCallList = Preconditions.checkNotNull(callList);
- }
-
- /**
- * Determines if a ringtone should be played for the given call state (see {@link State}) and
- * {@link Uri}.
- *
- * @param callState the call state for the call being checked.
- * @param ringtoneUri the ringtone to potentially play.
- * @return {@code true} if the ringtone should be played, {@code false} otherwise.
- */
- public boolean shouldPlayRingtone(int callState, @Nullable Uri ringtoneUri) {
- return isDialerRingingEnabled()
- && translateCallStateForCallWaiting(callState) == State.INCOMING
- && ringtoneUri != null;
- }
-
- /**
- * Determines if an incoming call should vibrate as well as ring.
- *
- * @param resolver {@link ContentResolver} used to look up the
- * {@link Settings.System#VIBRATE_WHEN_RINGING} setting.
- * @return {@code true} if the call should vibrate, {@code false} otherwise.
- */
- public boolean shouldVibrate(ContentResolver resolver) {
- return Settings.System.getInt(resolver, Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
- }
-
- /**
- * The incoming callState is never set as {@link State#CALL_WAITING} because
- * {@link Call#translateState(int)} doesn't account for that case, check for it here
- */
- private int translateCallStateForCallWaiting(int callState) {
- if (callState != State.INCOMING) {
- return callState;
- }
- return mCallList.getActiveCall() == null ? State.INCOMING : State.CALL_WAITING;
- }
-
- private boolean isDialerRingingEnabled() {
- if (mIsDialerRingingEnabledForTesting != null) {
- return mIsDialerRingingEnabledForTesting;
- }
- return CompatUtils.isNCompatible() && IS_DIALER_RINGING_ENABLED;
- }
-
- /**
- * Determines if a call waiting tone should be played for the the given call state
- * (see {@link State}).
- *
- * @param callState the call state for the call being checked.
- * @return {@code true} if the call waiting tone should be played, {@code false} otherwise.
- */
- public boolean shouldPlayCallWaitingTone(int callState) {
- return isDialerRingingEnabled()
- && translateCallStateForCallWaiting(callState) == State.CALL_WAITING
- && !mInCallTonePlayer.isPlayingTone();
- }
-
- /**
- * Plays the call waiting tone.
- */
- public void playCallWaitingTone() {
- if (!isDialerRingingEnabled()) {
- return;
- }
- mInCallTonePlayer.play(InCallTonePlayer.TONE_CALL_WAITING);
- }
-
- /**
- * Stops playing the call waiting tone.
- */
- public void stopCallWaitingTone() {
- if (!isDialerRingingEnabled()) {
- return;
- }
- mInCallTonePlayer.stop();
- }
-
- @NeededForTesting
- void setDialerRingingEnabledForTesting(boolean status) {
- mIsDialerRingingEnabledForTesting = status;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java b/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java
deleted file mode 100644
index 3a8b03d91..000000000
--- a/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.ringtone;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-
-import android.media.AudioManager;
-import android.media.ToneGenerator;
-import android.support.annotation.Nullable;
-
-import com.android.incallui.Log;
-import com.android.incallui.async.PausableExecutor;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Class responsible for playing in-call related tones in a background thread. This class only
- * allows one tone to be played at a time.
- */
-public class InCallTonePlayer {
-
- public static final int TONE_CALL_WAITING = 4;
-
- public static final int VOLUME_RELATIVE_HIGH_PRIORITY = 80;
-
- private final ToneGeneratorFactory mToneGeneratorFactory;
- private final PausableExecutor mExecutor;
- private @Nullable CountDownLatch mNumPlayingTones;
-
- /**
- * Creates a new InCallTonePlayer.
- *
- * @param toneGeneratorFactory the {@link ToneGeneratorFactory} used to create
- * {@link ToneGenerator}s.
- * @param executor the {@link PausableExecutor} used to play tones in a background thread.
- * @throws NullPointerException if audioModeProvider, toneGeneratorFactory, or executor are
- * {@code null}.
- */
- public InCallTonePlayer(ToneGeneratorFactory toneGeneratorFactory, PausableExecutor executor) {
- mToneGeneratorFactory = Preconditions.checkNotNull(toneGeneratorFactory);
- mExecutor = Preconditions.checkNotNull(executor);
- }
-
- /**
- * @return {@code true} if a tone is currently playing, {@code false} otherwise.
- */
- public boolean isPlayingTone() {
- return mNumPlayingTones != null && mNumPlayingTones.getCount() > 0;
- }
-
- /**
- * Plays the given tone in a background thread.
- *
- * @param tone the tone to play.
- * @throws IllegalStateException if a tone is already playing.
- * @throws IllegalArgumentException if the tone is invalid.
- */
- public void play(int tone) {
- if (isPlayingTone()) {
- throw new IllegalStateException("Tone already playing");
- }
- final ToneGeneratorInfo info = getToneGeneratorInfo(tone);
- mNumPlayingTones = new CountDownLatch(1);
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- playOnBackgroundThread(info);
- }
- });
- }
-
- private ToneGeneratorInfo getToneGeneratorInfo(int tone) {
- switch (tone) {
- case TONE_CALL_WAITING:
- /*
- * Call waiting tones play until they're stopped either by the user accepting or
- * declining the call so the tone length is set at what's effectively forever. The
- * tone is played at a high priority volume and through STREAM_VOICE_CALL since it's
- * call related and using that stream will route it through bluetooth devices
- * appropriately.
- */
- return new ToneGeneratorInfo(ToneGenerator.TONE_SUP_CALL_WAITING,
- VOLUME_RELATIVE_HIGH_PRIORITY,
- Integer.MAX_VALUE,
- AudioManager.STREAM_VOICE_CALL);
- default:
- throw new IllegalArgumentException("Bad tone: " + tone);
- }
- }
-
- private void playOnBackgroundThread(ToneGeneratorInfo info) {
- ToneGenerator toneGenerator = null;
- try {
- Log.v(this, "Starting tone " + info);
- toneGenerator = mToneGeneratorFactory.newInCallToneGenerator(info.stream, info.volume);
- toneGenerator.startTone(info.tone);
- /*
- * During tests, this will block until the tests call mExecutor.ackMilestone. This call
- * allows for synchronization to the point where the tone has started playing.
- */
- mExecutor.milestone();
- if (mNumPlayingTones != null) {
- mNumPlayingTones.await(info.toneLengthMillis, TimeUnit.MILLISECONDS);
- // Allows for synchronization to the point where the tone has completed playing.
- mExecutor.milestone();
- }
- } catch (InterruptedException e) {
- Log.w(this, "Interrupted while playing in-call tone.");
- } finally {
- if (toneGenerator != null) {
- toneGenerator.release();
- }
- if (mNumPlayingTones != null) {
- mNumPlayingTones.countDown();
- }
- // Allows for synchronization to the point where this background thread has cleaned up.
- mExecutor.milestone();
- }
- }
-
- /**
- * Stops playback of the current tone.
- */
- public void stop() {
- if (mNumPlayingTones != null) {
- mNumPlayingTones.countDown();
- }
- }
-
- private static class ToneGeneratorInfo {
- public final int tone;
- public final int volume;
- public final int toneLengthMillis;
- public final int stream;
-
- public ToneGeneratorInfo(int toneGeneratorType, int volume, int toneLengthMillis,
- int stream) {
- this.tone = toneGeneratorType;
- this.volume = volume;
- this.toneLengthMillis = toneLengthMillis;
- this.stream = stream;
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("tone", tone)
- .add("volume", volume)
- .add("toneLengthMillis", toneLengthMillis).toString();
- }
- }
-}
diff --git a/InCallUI/src/com/android/incallui/ringtone/ToneGeneratorFactory.java b/InCallUI/src/com/android/incallui/ringtone/ToneGeneratorFactory.java
deleted file mode 100644
index ac47c8a7d..000000000
--- a/InCallUI/src/com/android/incallui/ringtone/ToneGeneratorFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.ringtone;
-
-import android.media.ToneGenerator;
-
-/**
- * Factory used to create {@link ToneGenerator}s.
- */
-public class ToneGeneratorFactory {
-
- /**
- * Creates a new {@link ToneGenerator} to use while in a call.
- *
- * @param stream the stream through which to play tones.
- * @param volume the volume at which to play tones.
- * @return a new ToneGenerator.
- */
- public ToneGenerator newInCallToneGenerator(int stream, int volume) {
- return new ToneGenerator(stream, volume);
- }
-}
diff --git a/InCallUI/src/com/android/incallui/service/PhoneNumberService.java b/InCallUI/src/com/android/incallui/service/PhoneNumberService.java
deleted file mode 100644
index 70da4ef3a..000000000
--- a/InCallUI/src/com/android/incallui/service/PhoneNumberService.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2013 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.service;
-
-import android.graphics.Bitmap;
-
-/**
- * Provides phone number lookup services.
- */
-public interface PhoneNumberService {
-
- /**
- * Get a phone number number asynchronously.
- *
- * @param phoneNumber The phone number to lookup.
- * @param listener The listener to notify when the phone number lookup is complete.
- * @param imageListener The listener to notify when the image lookup is complete.
- */
- public void getPhoneNumberInfo(String phoneNumber, NumberLookupListener listener,
- ImageLookupListener imageListener, boolean isIncoming);
-
- public interface NumberLookupListener {
-
- /**
- * Callback when a phone number has been looked up.
- *
- * @param info The looked up information. Or (@literal null} if there are no results.
- */
- public void onPhoneNumberInfoComplete(PhoneNumberInfo info);
- }
-
- public interface ImageLookupListener {
-
- /**
- * Callback when a image has been fetched.
- *
- * @param bitmap The fetched image.
- */
- public void onImageFetchComplete(Bitmap bitmap);
- }
-
- public interface PhoneNumberInfo {
- public String getDisplayName();
- public String getNumber();
- public int getPhoneType();
- public String getPhoneLabel();
- public String getNormalizedNumber();
- public String getImageUrl();
- public String getLookupKey();
- public boolean isBusiness();
- public int getLookupSource();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/spam/SpamCallListListener.java b/InCallUI/src/com/android/incallui/spam/SpamCallListListener.java
deleted file mode 100644
index b97f4d099..000000000
--- a/InCallUI/src/com/android/incallui/spam/SpamCallListListener.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.spam;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import android.content.Context;
-import android.telecom.DisconnectCause;
-import android.text.TextUtils;
-
-import com.android.dialer.calllog.CallLogAsyncTaskUtil;
-import com.android.incallui.Call;
-import com.android.incallui.CallList;
-import com.android.incallui.Log;
-
-public class SpamCallListListener implements CallList.Listener {
- private static final String TAG = "SpamCallListListener";
-
- private final Context mContext;
-
- public SpamCallListListener(Context context) {
- mContext = context;
- }
-
- @Override
- public void onIncomingCall(final Call call) {
- String number = call.getNumber();
- if (TextUtils.isEmpty(number)) {
- return;
- }
- CallLogAsyncTaskUtil.getNumberInCallHistory(mContext, number,
- new CallLogAsyncTaskUtil.OnGetNumberInCallHistoryListener() {
- @Override
- public void onComplete(boolean inCallHistory) {
- call.setCallHistoryStatus(inCallHistory ?
- Call.CALL_HISTORY_STATUS_PRESENT
- : Call.CALL_HISTORY_STATUS_NOT_PRESENT);
- }
- });
- }
-
- @Override
- public void onUpgradeToVideo(Call call) {}
-
- @Override
- public void onCallListChange(CallList callList) {}
-
- @Override
- public void onDisconnect(Call call) {
- if (shouldShowAfterCallNotification(call)) {
- showNotification(call.getNumber());
- }
- }
-
- /**
- * Posts the intent for displaying the after call spam notification to the user.
- */
- @VisibleForTesting
- /* package */ void showNotification(String number) {
- //TODO(mhashmi): build and show notifications here
- }
-
- /**
- * Determines if the after call notification should be shown for the specified call.
- */
- private boolean shouldShowAfterCallNotification(Call call) {
- String number = call.getNumber();
- if (TextUtils.isEmpty(number)) {
- return false;
- }
-
- Call.LogState logState = call.getLogState();
- if (!logState.isIncoming) {
- return false;
- }
-
- if (logState.duration <= 0) {
- return false;
- }
-
- if (logState.contactLookupResult != Call.LogState.LOOKUP_NOT_FOUND
- && logState.contactLookupResult != Call.LogState.LOOKUP_UNKNOWN) {
- return false;
- }
-
- int callHistoryStatus = call.getCallHistoryStatus();
- if (callHistoryStatus == Call.CALL_HISTORY_STATUS_PRESENT) {
- return false;
- } else if (callHistoryStatus == Call.CALL_HISTORY_STATUS_UNKNOWN) {
- Log.i(TAG, "Call history status is unknown, returning false");
- return false;
- }
-
- // Check if call disconnected because of either user hanging up
- int disconnectCause = call.getDisconnectCause().getCode();
- if (disconnectCause != DisconnectCause.LOCAL && disconnectCause != DisconnectCause.REMOTE) {
- return false;
- }
-
- Log.i(TAG, "shouldShowAfterCallNotification, returning true");
- return true;
- }
-} \ No newline at end of file
diff --git a/InCallUI/src/com/android/incallui/util/AccessibilityUtil.java b/InCallUI/src/com/android/incallui/util/AccessibilityUtil.java
deleted file mode 100644
index 1fdd2bac6..000000000
--- a/InCallUI/src/com/android/incallui/util/AccessibilityUtil.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2013 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.util;
-
-import android.content.Context;
-import android.view.accessibility.AccessibilityManager;
-
-public class AccessibilityUtil {
- public static boolean isTalkBackEnabled(Context context) {
- AccessibilityManager accessibilityManager = (AccessibilityManager) context
- .getSystemService(Context.ACCESSIBILITY_SERVICE);
- return accessibilityManager != null
- && accessibilityManager.isEnabled()
- && accessibilityManager.isTouchExplorationEnabled();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/util/TelecomCallUtil.java b/InCallUI/src/com/android/incallui/util/TelecomCallUtil.java
deleted file mode 100644
index 53ecc29e9..000000000
--- a/InCallUI/src/com/android/incallui/util/TelecomCallUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 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.util;
-
-import android.net.Uri;
-import android.telecom.Call;
-import android.telephony.PhoneNumberUtils;
-
-/**
- * Class to provide a standard interface for obtaining information from the underlying
- * android.telecom.Call. Much of this should be obtained through the incall.Call, but
- * on occasion we need to interact with the telecom.Call directly (eg. call blocking,
- * before the incall.Call has been created).
- */
-public class TelecomCallUtil {
-
- // Whether the call handle is an emergency number.
- public static boolean isEmergencyCall(Call call) {
- Uri handle = call.getDetails().getHandle();
- return PhoneNumberUtils.isEmergencyNumber(
- handle == null ? "" : handle.getSchemeSpecificPart());
- }
-
- public static String getNumber(Call call) {
- if (call == null) {
- return null;
- }
- if (call.getDetails().getGatewayInfo() != null) {
- return call.getDetails().getGatewayInfo()
- .getOriginalAddress().getSchemeSpecificPart();
- }
- Uri handle = getHandle(call);
- return handle == null ? null : handle.getSchemeSpecificPart();
- }
-
- public static Uri getHandle(Call call) {
- return call == null ? null : call.getDetails().getHandle();
- }
-}
diff --git a/InCallUI/src/com/android/incallui/widget/multiwaveview/Ease.java b/InCallUI/src/com/android/incallui/widget/multiwaveview/Ease.java
deleted file mode 100644
index 5ef689771..000000000
--- a/InCallUI/src/com/android/incallui/widget/multiwaveview/Ease.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2011 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.widget.multiwaveview;
-
-import android.animation.TimeInterpolator;
-
-class Ease {
- private static final float DOMAIN = 1.0f;
- private static final float DURATION = 1.0f;
- private static final float START = 0.0f;
-
- static class Linear {
- public static final TimeInterpolator easeNone = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return input;
- }
- };
- }
-
- static class Cubic {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*((input=input/DURATION-1)*input*input + 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1.0f) ?
- (DOMAIN/2*input*input*input + START)
- : (DOMAIN/2*((input-=2)*input*input + 2) + START);
- }
- };
- }
-
- static class Quad {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation (float input) {
- return DOMAIN*(input/=DURATION)*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN *(input/=DURATION)*(input-2) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input + START)
- : (-DOMAIN/2 * ((--input)*(input-2) - 1) + START);
- }
- };
- }
-
- static class Quart {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN * ((input=input/DURATION-1)*input*input*input - 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input*input*input + START)
- : (-DOMAIN/2 * ((input-=2)*input*input*input - 2) + START);
- }
- };
- }
-
- static class Quint {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*((input=input/DURATION-1)*input*input*input*input + 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input*input*input*input + START)
- : (DOMAIN/2*((input-=2)*input*input*input*input + 2) + START);
- }
- };
- }
-
- static class Sine {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN * (float) Math.cos(input/DURATION * (Math.PI/2)) + DOMAIN + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN * (float) Math.sin(input/DURATION * (Math.PI/2)) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN/2 * ((float)Math.cos(Math.PI*input/DURATION) - 1.0f) + START;
- }
- };
- }
-
-}
diff --git a/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java b/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java
deleted file mode 100644
index efeb4b7e3..000000000
--- a/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java
+++ /dev/null
@@ -1,1473 +0,0 @@
-/*
- * Copyright (C) 2012 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.widget.multiwaveview;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Vibrator;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.widget.ExploreByTouchHelper;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.accessibility.AccessibilityNodeProvider;
-
-import com.android.dialer.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This is a copy of com.android.internal.widget.multiwaveview.GlowPadView with minor changes
- * to remove dependencies on private api's.
- *
- * Incoporated the scaling functionality.
- *
- * A re-usable widget containing a center, outer ring and wave animation.
- */
-public class GlowPadView extends View {
- private static final String TAG = "GlowPadView";
- private static final boolean DEBUG = false;
-
- // Wave state machine
- private static final int STATE_IDLE = 0;
- private static final int STATE_START = 1;
- private static final int STATE_FIRST_TOUCH = 2;
- private static final int STATE_TRACKING = 3;
- private static final int STATE_SNAP = 4;
- private static final int STATE_FINISH = 5;
-
- // Animation properties.
- private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
-
- public interface OnTriggerListener {
- int NO_HANDLE = 0;
- int CENTER_HANDLE = 1;
- public void onGrabbed(View v, int handle);
- public void onReleased(View v, int handle);
- public void onTrigger(View v, int target);
- public void onGrabbedStateChange(View v, int handle);
- public void onFinishFinalAnimation();
- }
-
- // Tuneable parameters for animation
- private static final int WAVE_ANIMATION_DURATION = 1350;
- private static final int RETURN_TO_HOME_DELAY = 1200;
- private static final int RETURN_TO_HOME_DURATION = 200;
- private static final int HIDE_ANIMATION_DELAY = 200;
- private static final int HIDE_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DELAY = 50;
- private static final int INITIAL_SHOW_HANDLE_DURATION = 200;
- private static final int REVEAL_GLOW_DELAY = 0;
- private static final int REVEAL_GLOW_DURATION = 0;
-
- private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
- private static final float TARGET_SCALE_EXPANDED = 1.0f;
- private static final float TARGET_SCALE_COLLAPSED = 0.8f;
- private static final float RING_SCALE_EXPANDED = 1.0f;
- private static final float RING_SCALE_COLLAPSED = 0.5f;
-
- private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
- private AnimationBundle mWaveAnimations = new AnimationBundle();
- private AnimationBundle mTargetAnimations = new AnimationBundle();
- private AnimationBundle mGlowAnimations = new AnimationBundle();
- private ArrayList<String> mTargetDescriptions;
- private ArrayList<String> mDirectionDescriptions;
- private OnTriggerListener mOnTriggerListener;
- private TargetDrawable mHandleDrawable;
- private TargetDrawable mOuterRing;
- private Vibrator mVibrator;
-
- private int mFeedbackCount = 3;
- private int mVibrationDuration = 0;
- private int mGrabbedState;
- private int mActiveTarget = -1;
- private float mGlowRadius;
- private float mWaveCenterX;
- private float mWaveCenterY;
- private int mMaxTargetHeight;
- private int mMaxTargetWidth;
- private float mRingScaleFactor = 1f;
- private boolean mAllowScaling;
-
- private float mOuterRadius = 0.0f;
- private float mSnapMargin = 0.0f;
- private boolean mDragging;
- private int mNewTargetResources;
-
- private AccessibilityNodeProvider mAccessibilityNodeProvider;
- private GlowpadExploreByTouchHelper mExploreByTouchHelper;
-
- private class AnimationBundle extends ArrayList<Tweener> {
- private static final long serialVersionUID = 0xA84D78726F127468L;
- private boolean mSuspended;
-
- public void start() {
- if (mSuspended) return; // ignore attempts to start animations
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.start();
- }
- }
-
- public void cancel() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.cancel();
- }
- clear();
- }
-
- public void stop() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.end();
- }
- clear();
- }
-
- public void setSuspended(boolean suspend) {
- mSuspended = suspend;
- }
- };
-
- private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- ping();
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidate();
- }
- };
-
- private boolean mAnimatingTargets;
- private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- if (mNewTargetResources != 0) {
- internalSetTargetResources(mNewTargetResources);
- mNewTargetResources = 0;
- hideTargets(false, false);
- }
- mAnimatingTargets = false;
- }
- };
- private int mTargetResourceId;
- private int mTargetDescriptionsResourceId;
- private int mDirectionDescriptionsResourceId;
- private boolean mAlwaysTrackFinger;
- private int mHorizontalInset;
- private int mVerticalInset;
- private int mGravity = Gravity.TOP;
- private boolean mInitialLayout = true;
- private Tweener mBackgroundAnimator;
- private PointCloud mPointCloud;
- private float mInnerRadius;
- private int mPointerId;
-
- public GlowPadView(Context context) {
- this(context, null);
- }
-
- public GlowPadView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Resources res = context.getResources();
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView);
- mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius);
- mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius);
- mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin);
- mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration,
- mVibrationDuration);
- mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount,
- mFeedbackCount);
- mAllowScaling = a.getBoolean(R.styleable.GlowPadView_allowScaling, false);
- TypedValue handle = a.peekValue(R.styleable.GlowPadView_handleDrawable);
- setHandleDrawable(handle != null ? handle.resourceId : R.drawable.ic_incall_audio_handle);
- mOuterRing = new TargetDrawable(res,
- getResourceId(a, R.styleable.GlowPadView_outerRingDrawable), 1);
-
- mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false);
-
- int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
- Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null;
- mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
-
- TypedValue outValue = new TypedValue();
-
- // Read array of target drawables
- if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) {
- internalSetTargetResources(outValue.resourceId);
- }
- if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
- throw new IllegalStateException("Must specify at least one target drawable");
- }
-
- // Read array of target descriptions
- if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify target descriptions");
- }
- setTargetDescriptionsResourceId(resourceId);
- }
-
- // Read array of direction descriptions
- if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify direction descriptions");
- }
- setDirectionDescriptionsResourceId(resourceId);
- }
-
- // Use gravity attribute from LinearLayout
- //a = context.obtainStyledAttributes(attrs, R.styleable.LinearLayout);
- mGravity = a.getInt(R.styleable.GlowPadView_android_gravity, Gravity.TOP);
- a.recycle();
-
-
- setVibrateEnabled(mVibrationDuration > 0);
-
- assignDefaultsIfNeeded();
-
- mPointCloud = new PointCloud(pointDrawable);
- mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
- mPointCloud.glowManager.setRadius(mGlowRadius);
-
- mExploreByTouchHelper = new GlowpadExploreByTouchHelper(this);
- ViewCompat.setAccessibilityDelegate(this, mExploreByTouchHelper);
- }
-
- private int getResourceId(TypedArray a, int id) {
- TypedValue tv = a.peekValue(id);
- return tv == null ? 0 : tv.resourceId;
- }
-
- private void dump() {
- Log.v(TAG, "Outer Radius = " + mOuterRadius);
- Log.v(TAG, "SnapMargin = " + mSnapMargin);
- Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
- Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
- Log.v(TAG, "GlowRadius = " + mGlowRadius);
- Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
- Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
- }
-
- public void suspendAnimations() {
- mWaveAnimations.setSuspended(true);
- mTargetAnimations.setSuspended(true);
- mGlowAnimations.setSuspended(true);
- }
-
- public void resumeAnimations() {
- mWaveAnimations.setSuspended(false);
- mTargetAnimations.setSuspended(false);
- mGlowAnimations.setSuspended(false);
- mWaveAnimations.start();
- mTargetAnimations.start();
- mGlowAnimations.start();
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- // View should be large enough to contain the background + handle and
- // target drawable on either edge.
- return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- // View should be large enough to contain the unlock ring + target and
- // target drawable on either edge
- return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
- }
-
- /**
- * This gets the suggested width accounting for the ring's scale factor.
- */
- protected int getScaledSuggestedMinimumWidth() {
- return (int) (mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius)
- + mMaxTargetWidth);
- }
-
- /**
- * This gets the suggested height accounting for the ring's scale factor.
- */
- protected int getScaledSuggestedMinimumHeight() {
- return (int) (mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius)
- + mMaxTargetHeight);
- }
-
- private int resolveMeasured(int measureSpec, int desired)
- {
- int result = 0;
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (MeasureSpec.getMode(measureSpec)) {
- case MeasureSpec.UNSPECIFIED:
- result = desired;
- break;
- case MeasureSpec.AT_MOST:
- result = Math.min(specSize, desired);
- break;
- case MeasureSpec.EXACTLY:
- default:
- result = specSize;
- }
- return result;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int minimumWidth = getSuggestedMinimumWidth();
- final int minimumHeight = getSuggestedMinimumHeight();
- int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
- int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
-
- mRingScaleFactor = computeScaleFactor(minimumWidth, minimumHeight,
- computedWidth, computedHeight);
-
- int scaledWidth = getScaledSuggestedMinimumWidth();
- int scaledHeight = getScaledSuggestedMinimumHeight();
-
- computeInsets(computedWidth - scaledWidth, computedHeight - scaledHeight);
- setMeasuredDimension(computedWidth, computedHeight);
- }
-
- private void switchToState(int state, float x, float y) {
- switch (state) {
- case STATE_IDLE:
- deactivateTargets();
- hideGlow(0, 0, 0.0f, null);
- startBackgroundAnimation(0, 0.0f);
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- mHandleDrawable.setAlpha(1.0f);
- break;
-
- case STATE_START:
- startBackgroundAnimation(0, 0.0f);
- break;
-
- case STATE_FIRST_TOUCH:
- mHandleDrawable.setAlpha(0.0f);
- deactivateTargets();
- showTargets(true);
- startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
-
- final AccessibilityManager accessibilityManager =
- (AccessibilityManager) getContext().getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- if (accessibilityManager.isEnabled()) {
- announceTargets();
- }
- break;
-
- case STATE_TRACKING:
- mHandleDrawable.setAlpha(0.0f);
- break;
-
- case STATE_SNAP:
- // TODO: Add transition states (see list_selector_background_transition.xml)
- mHandleDrawable.setAlpha(0.0f);
- showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null);
- break;
-
- case STATE_FINISH:
- doFinish();
- break;
- }
- }
-
- private void showGlow(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mGlowAnimations.cancel();
- mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
- "ease", Ease.Cubic.easeIn,
- "delay", delay,
- "alpha", finalAlpha,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mGlowAnimations.start();
- }
-
- private void hideGlow(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mGlowAnimations.cancel();
- mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
- "ease", Ease.Quart.easeOut,
- "delay", delay,
- "alpha", finalAlpha,
- "x", 0.0f,
- "y", 0.0f,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mGlowAnimations.start();
- }
-
- private void deactivateTargets() {
- final int count = mTargetDrawables.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- mActiveTarget = -1;
- }
-
- /**
- * Dispatches a trigger event to listener. Ignored if a listener is not set.
- * @param whichTarget the target that was triggered.
- */
- private void dispatchTriggerEvent(int whichTarget) {
- vibrate();
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onTrigger(this, whichTarget);
- }
- }
-
- private void dispatchOnFinishFinalAnimation() {
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onFinishFinalAnimation();
- }
- }
-
- private void doFinish() {
- final int activeTarget = mActiveTarget;
- final boolean targetHit = activeTarget != -1;
-
- if (targetHit) {
- if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
-
- highlightSelected(activeTarget);
-
- // Inform listener of any active targets. Typically only one will be active.
- hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
- dispatchTriggerEvent(activeTarget);
- if (!mAlwaysTrackFinger) {
- // Force ring and targets to finish animation to final expanded state
- mTargetAnimations.stop();
- }
- } else {
- // Animate handle back to the center based on current state.
- hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing);
- hideTargets(true, false);
- }
-
- setGrabbedState(OnTriggerListener.NO_HANDLE);
- }
-
- private void highlightSelected(int activeTarget) {
- // Highlight the given target and fade others
- mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
- hideUnselected(activeTarget);
- }
-
- private void hideUnselected(int active) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- if (i != active) {
- mTargetDrawables.get(i).setAlpha(0.0f);
- }
- }
- }
-
- private void hideTargets(boolean animate, boolean expanded) {
- mTargetAnimations.cancel();
- // Note: these animations should complete at the same time so that we can swap out
- // the target assets asynchronously from the setTargetResources() call.
- mAnimatingTargets = animate;
- final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
- final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
-
- final float targetScale = expanded ?
- TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
- final int length = mTargetDrawables.size();
- final TimeInterpolator interpolator = Ease.Cubic.easeOut;
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", interpolator,
- "alpha", 0.0f,
- "scaleX", targetScale,
- "scaleY", targetScale,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
-
- float ringScaleTarget = expanded ?
- RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
- ringScaleTarget *= mRingScaleFactor;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", interpolator,
- "alpha", 0.0f,
- "scaleX", ringScaleTarget,
- "scaleY", ringScaleTarget,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void showTargets(boolean animate) {
- mTargetAnimations.stop();
- mAnimatingTargets = animate;
- final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
- final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
- final int length = mTargetDrawables.size();
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", 1.0f,
- "scaleY", 1.0f,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
- float ringScale = mRingScaleFactor * RING_SCALE_EXPANDED;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", ringScale,
- "scaleY", ringScale,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void vibrate() {
- if (mVibrator != null) {
- mVibrator.vibrate(mVibrationDuration);
- }
- }
-
- private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) {
- Resources res = getContext().getResources();
- TypedArray array = res.obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
- for (int i = 0; i < count; i++) {
- TypedValue value = array.peekValue(i);
- TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0, 3);
- drawables.add(target);
- }
- array.recycle();
- return drawables;
- }
-
- private void internalSetTargetResources(int resourceId) {
- final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId);
- mTargetDrawables = targets;
- mTargetResourceId = resourceId;
-
- int maxWidth = mHandleDrawable.getWidth();
- int maxHeight = mHandleDrawable.getHeight();
- final int count = targets.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = targets.get(i);
- maxWidth = Math.max(maxWidth, target.getWidth());
- maxHeight = Math.max(maxHeight, target.getHeight());
- }
- if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
- mMaxTargetWidth = maxWidth;
- mMaxTargetHeight = maxHeight;
- requestLayout(); // required to resize layout and call updateTargetPositions()
- } else {
- updateTargetPositions(mWaveCenterX, mWaveCenterY);
- updatePointCloudPosition(mWaveCenterX, mWaveCenterY);
- }
- }
- /**
- * Loads an array of drawables from the given resourceId.
- *
- * @param resourceId
- */
- public void setTargetResources(int resourceId) {
- if (mAnimatingTargets) {
- // postpone this change until we return to the initial state
- mNewTargetResources = resourceId;
- } else {
- internalSetTargetResources(resourceId);
- }
- }
-
- public int getTargetResourceId() {
- return mTargetResourceId;
- }
-
- /**
- * Sets the handle drawable to the drawable specified by the resource ID.
- * @param resourceId
- */
- public void setHandleDrawable(int resourceId) {
- if (mHandleDrawable != null) {
- mHandleDrawable.setDrawable(getResources(), resourceId);
- } else {
- mHandleDrawable = new TargetDrawable(getResources(), resourceId, 1);
- }
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- }
-
- /**
- * Sets the resource id specifying the target descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setTargetDescriptionsResourceId(int resourceId) {
- mTargetDescriptionsResourceId = resourceId;
- if (mTargetDescriptions != null) {
- mTargetDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target descriptions for accessibility.
- *
- * @return The resource id.
- */
- public int getTargetDescriptionsResourceId() {
- return mTargetDescriptionsResourceId;
- }
-
- /**
- * Sets the resource id specifying the target direction descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setDirectionDescriptionsResourceId(int resourceId) {
- mDirectionDescriptionsResourceId = resourceId;
- if (mDirectionDescriptions != null) {
- mDirectionDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target direction descriptions.
- *
- * @return The resource id.
- */
- public int getDirectionDescriptionsResourceId() {
- return mDirectionDescriptionsResourceId;
- }
-
- /**
- * Enable or disable vibrate on touch.
- *
- * @param enabled
- */
- public void setVibrateEnabled(boolean enabled) {
- if (enabled && mVibrator == null) {
- mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
- } else {
- mVibrator = null;
- }
- }
-
- /**
- * Starts wave animation.
- *
- */
- public void ping() {
- if (mFeedbackCount > 0) {
- boolean doWaveAnimation = true;
- final AnimationBundle waveAnimations = mWaveAnimations;
-
- // Don't do a wave if there's already one in progress
- if (waveAnimations.size() > 0 && waveAnimations.get(0).animator.isRunning()) {
- long t = waveAnimations.get(0).animator.getCurrentPlayTime();
- if (t < WAVE_ANIMATION_DURATION/2) {
- doWaveAnimation = false;
- }
- }
-
- if (doWaveAnimation) {
- startWaveAnimation();
- }
- }
- }
-
- private void stopAndHideWaveAnimation() {
- mWaveAnimations.cancel();
- mPointCloud.waveManager.setAlpha(0.0f);
- }
-
- private void startWaveAnimation() {
- mWaveAnimations.cancel();
- mPointCloud.waveManager.setAlpha(1.0f);
- mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f);
- mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION,
- "ease", Ease.Quad.easeOut,
- "delay", 0,
- "radius", 2.0f * mOuterRadius,
- "onUpdate", mUpdateListener,
- "onComplete",
- new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- mPointCloud.waveManager.setRadius(0.0f);
- mPointCloud.waveManager.setAlpha(0.0f);
- }
- }));
- mWaveAnimations.start();
- }
-
- /**
- * Resets the widget to default state and cancels all animation. If animate is 'true', will
- * animate objects into place. Otherwise, objects will snap back to place.
- *
- * @param animate
- */
- public void reset(boolean animate) {
- mGlowAnimations.stop();
- mTargetAnimations.stop();
- startBackgroundAnimation(0, 0.0f);
- stopAndHideWaveAnimation();
- hideTargets(animate, false);
- hideGlow(0, 0, 0.0f, null);
- Tweener.reset();
- }
-
- private void startBackgroundAnimation(int duration, float alpha) {
- final Drawable background = getBackground();
- if (mAlwaysTrackFinger && background != null) {
- if (mBackgroundAnimator != null) {
- mBackgroundAnimator.animator.cancel();
- }
- mBackgroundAnimator = Tweener.to(background, duration,
- "ease", Ease.Cubic.easeIn,
- "alpha", (int)(255.0f * alpha),
- "delay", SHOW_ANIMATION_DELAY);
- mBackgroundAnimator.animator.start();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getActionMasked();
- boolean handled = false;
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_DOWN:
- if (DEBUG) Log.v(TAG, "*** DOWN ***");
- handleDown(event);
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG) Log.v(TAG, "*** MOVE ***");
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_UP:
- if (DEBUG) Log.v(TAG, "*** UP ***");
- handleMove(event);
- handleUp(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG) Log.v(TAG, "*** CANCEL ***");
- handleMove(event);
- handleCancel(event);
- handled = true;
- break;
- }
- invalidate();
- return handled ? true : super.onTouchEvent(event);
- }
-
- private void updateGlowPosition(float x, float y) {
- float dx = x - mOuterRing.getX();
- float dy = y - mOuterRing.getY();
- dx *= 1f / mRingScaleFactor;
- dy *= 1f / mRingScaleFactor;
- mPointCloud.glowManager.setX(mOuterRing.getX() + dx);
- mPointCloud.glowManager.setY(mOuterRing.getY() + dy);
- }
-
- private void handleDown(MotionEvent event) {
- int actionIndex = event.getActionIndex();
- float eventX = event.getX(actionIndex);
- float eventY = event.getY(actionIndex);
- switchToState(STATE_START, eventX, eventY);
- if (!trySwitchToFirstTouchState(eventX, eventY)) {
- mDragging = false;
- } else {
- mPointerId = event.getPointerId(actionIndex);
- updateGlowPosition(eventX, eventY);
- }
- }
-
- private void handleUp(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
- int actionIndex = event.getActionIndex();
- if (event.getPointerId(actionIndex) == mPointerId) {
- switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
- }
- }
-
- private void handleCancel(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
-
- // We should drop the active target here but it interferes with
- // moving off the screen in the direction of the navigation bar. At some point we may
- // want to revisit how we handle this. For now we'll allow a canceled event to
- // activate the current target.
-
- // mActiveTarget = -1; // Drop the active target if canceled.
-
- int actionIndex = event.findPointerIndex(mPointerId);
- actionIndex = actionIndex == -1 ? 0 : actionIndex;
- switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
- }
-
- private void handleMove(MotionEvent event) {
- int activeTarget = -1;
- final int historySize = event.getHistorySize();
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- int ntargets = targets.size();
- float x = 0.0f;
- float y = 0.0f;
- int actionIndex = event.findPointerIndex(mPointerId);
-
- if (actionIndex == -1) {
- return; // no data for this pointer
- }
-
- for (int k = 0; k < historySize + 1; k++) {
- float eventX = k < historySize ? event.getHistoricalX(actionIndex, k)
- : event.getX(actionIndex);
- float eventY = k < historySize ? event.getHistoricalY(actionIndex, k)
- :event.getY(actionIndex);
- // tx and ty are relative to wave center
- float tx = eventX - mWaveCenterX;
- float ty = eventY - mWaveCenterY;
- float touchRadius = (float) Math.hypot(tx, ty);
- final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
- float limitX = tx * scale;
- float limitY = ty * scale;
- double angleRad = Math.atan2(-ty, tx);
-
- if (!mDragging) {
- trySwitchToFirstTouchState(eventX, eventY);
- }
-
- if (mDragging) {
- // For multiple targets, snap to the one that matches
- final float snapRadius = mRingScaleFactor * mOuterRadius - mSnapMargin;
- final float snapDistance2 = snapRadius * snapRadius;
- // Find first target in range
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = targets.get(i);
-
- double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets;
- double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets;
- if (target.isEnabled()) {
- boolean angleMatches =
- (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
- (angleRad + 2 * Math.PI > targetMinRad &&
- angleRad + 2 * Math.PI <= targetMaxRad);
- if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
- activeTarget = i;
- }
- }
- }
- }
- x = limitX;
- y = limitY;
- }
-
- if (!mDragging) {
- return;
- }
-
- if (activeTarget != -1) {
- switchToState(STATE_SNAP, x,y);
- updateGlowPosition(x, y);
- } else {
- switchToState(STATE_TRACKING, x, y);
- updateGlowPosition(x, y);
- }
-
- if (mActiveTarget != activeTarget) {
- // Defocus the old target
- if (mActiveTarget != -1) {
- TargetDrawable target = targets.get(mActiveTarget);
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- // Focus the new target
- if (activeTarget != -1) {
- TargetDrawable target = targets.get(activeTarget);
- target.setState(TargetDrawable.STATE_FOCUSED);
- final AccessibilityManager accessibilityManager =
- (AccessibilityManager) getContext().getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- if (accessibilityManager.isEnabled()) {
- String targetContentDescription = getTargetDescription(activeTarget);
- announceForAccessibility(targetContentDescription);
- }
- }
- }
- mActiveTarget = activeTarget;
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- final AccessibilityManager accessibilityManager =
- (AccessibilityManager) getContext().getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- if (accessibilityManager.isTouchExplorationEnabled()) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
- break;
- }
- onTouchEvent(event);
- event.setAction(action);
- }
- super.onHoverEvent(event);
- return true;
- }
-
- /**
- * Sets the current grabbed state, and dispatches a grabbed state change
- * event to our listener.
- */
- private void setGrabbedState(int newState) {
- if (newState != mGrabbedState) {
- if (newState != OnTriggerListener.NO_HANDLE) {
- vibrate();
- }
- mGrabbedState = newState;
- if (mOnTriggerListener != null) {
- if (newState == OnTriggerListener.NO_HANDLE) {
- mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
- } else {
- mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
- }
- mOnTriggerListener.onGrabbedStateChange(this, newState);
- }
- }
- }
-
- private boolean trySwitchToFirstTouchState(float x, float y) {
- final float tx = x - mWaveCenterX;
- final float ty = y - mWaveCenterY;
- if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) {
- if (DEBUG) Log.v(TAG, "** Handle HIT");
- switchToState(STATE_FIRST_TOUCH, x, y);
- updateGlowPosition(tx, ty);
- mDragging = true;
- return true;
- }
- return false;
- }
-
- private void assignDefaultsIfNeeded() {
- if (mOuterRadius == 0.0f) {
- mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
- }
- if (mSnapMargin == 0.0f) {
- mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
- }
- if (mInnerRadius == 0.0f) {
- mInnerRadius = mHandleDrawable.getWidth() / 10.0f;
- }
- }
-
- private void computeInsets(int dx, int dy) {
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- mHorizontalInset = 0;
- break;
- case Gravity.RIGHT:
- mHorizontalInset = dx;
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- mHorizontalInset = dx / 2;
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- mVerticalInset = 0;
- break;
- case Gravity.BOTTOM:
- mVerticalInset = dy;
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- mVerticalInset = dy / 2;
- break;
- }
- }
-
- /**
- * Given the desired width and height of the ring and the allocated width and height, compute
- * how much we need to scale the ring.
- */
- private float computeScaleFactor(int desiredWidth, int desiredHeight,
- int actualWidth, int actualHeight) {
-
- // Return unity if scaling is not allowed.
- if (!mAllowScaling) return 1f;
-
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- float scaleX = 1f;
- float scaleY = 1f;
-
- // We use the gravity as a cue for whether we want to scale on a particular axis.
- // We only scale to fit horizontally if we're not pinned to the left or right. Likewise,
- // we only scale to fit vertically if we're not pinned to the top or bottom. In these
- // cases, we want the ring to hang off the side or top/bottom, respectively.
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- case Gravity.RIGHT:
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- if (desiredWidth > actualWidth) {
- scaleX = (1f * actualWidth - mMaxTargetWidth) /
- (desiredWidth - mMaxTargetWidth);
- }
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- case Gravity.BOTTOM:
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- if (desiredHeight > actualHeight) {
- scaleY = (1f * actualHeight - mMaxTargetHeight) /
- (desiredHeight - mMaxTargetHeight);
- }
- break;
- }
- return Math.min(scaleX, scaleY);
- }
-
- private float getRingWidth() {
- return mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
- }
-
- private float getRingHeight() {
- return mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- final int width = right - left;
- final int height = bottom - top;
-
- // Target placement width/height. This puts the targets on the greater of the ring
- // width or the specified outer radius.
- final float placementWidth = getRingWidth();
- final float placementHeight = getRingHeight();
- float newWaveCenterX = mHorizontalInset
- + (mMaxTargetWidth + placementWidth) / 2;
- float newWaveCenterY = mVerticalInset
- + (mMaxTargetHeight + placementHeight) / 2;
-
- if (mInitialLayout) {
- stopAndHideWaveAnimation();
- hideTargets(false, false);
- mInitialLayout = false;
- }
-
- mOuterRing.setPositionX(newWaveCenterX);
- mOuterRing.setPositionY(newWaveCenterY);
-
- mPointCloud.setScale(mRingScaleFactor);
-
- mHandleDrawable.setPositionX(newWaveCenterX);
- mHandleDrawable.setPositionY(newWaveCenterY);
-
- updateTargetPositions(newWaveCenterX, newWaveCenterY);
- updatePointCloudPosition(newWaveCenterX, newWaveCenterY);
- updateGlowPosition(newWaveCenterX, newWaveCenterY);
-
- mWaveCenterX = newWaveCenterX;
- mWaveCenterY = newWaveCenterY;
-
- if (DEBUG) dump();
- }
-
- private void updateTargetPositions(float centerX, float centerY) {
- // Reposition the target drawables if the view changed.
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- final int size = targets.size();
- final float alpha = (float) (-2.0f * Math.PI / size);
- for (int i = 0; i < size; i++) {
- final TargetDrawable targetIcon = targets.get(i);
- final float angle = alpha * i;
- targetIcon.setPositionX(centerX);
- targetIcon.setPositionY(centerY);
- targetIcon.setX(getRingWidth() / 2 * (float) Math.cos(angle));
- targetIcon.setY(getRingHeight() / 2 * (float) Math.sin(angle));
- }
- }
-
- private void updatePointCloudPosition(float centerX, float centerY) {
- mPointCloud.setCenter(centerX, centerY);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mPointCloud.draw(canvas);
- mOuterRing.draw(canvas);
- final int ntargets = mTargetDrawables.size();
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- if (target != null) {
- target.draw(canvas);
- }
- }
- mHandleDrawable.draw(canvas);
- }
-
- public void setOnTriggerListener(OnTriggerListener listener) {
- mOnTriggerListener = listener;
- }
-
- private float square(float d) {
- return d * d;
- }
-
- private float dist2(float dx, float dy) {
- return dx*dx + dy*dy;
- }
-
- private float getScaledGlowRadiusSquared() {
- final float scaledTapRadius;
- final AccessibilityManager accessibilityManager =
- (AccessibilityManager) getContext().getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- if (accessibilityManager.isEnabled()) {
- scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius;
- } else {
- scaledTapRadius = mGlowRadius;
- }
- return square(scaledTapRadius);
- }
-
- private void announceTargets() {
- StringBuilder utterance = new StringBuilder();
- final int targetCount = mTargetDrawables.size();
- for (int i = 0; i < targetCount; i++) {
- String targetDescription = getTargetDescription(i);
- String directionDescription = getDirectionDescription(i);
- if (!TextUtils.isEmpty(targetDescription)
- && !TextUtils.isEmpty(directionDescription)) {
- String text = String.format(directionDescription, targetDescription);
- utterance.append(text);
- }
- }
- if (utterance.length() > 0) {
- announceForAccessibility(utterance.toString());
- }
- }
-
- private String getTargetDescription(int index) {
- if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
- mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
- if (mTargetDrawables.size() != mTargetDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " equal to the number of target descriptions.");
- return null;
- }
- }
- return mTargetDescriptions.get(index);
- }
-
- private String getDirectionDescription(int index) {
- if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
- mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
- if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " equal to the number of direction descriptions.");
- return null;
- }
- }
- return mDirectionDescriptions.get(index);
- }
-
- private ArrayList<String> loadDescriptions(int resourceId) {
- TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
- for (int i = 0; i < count; i++) {
- String contentDescription = array.getString(i);
- targetContentDescriptions.add(contentDescription);
- }
- array.recycle();
- return targetContentDescriptions;
- }
-
- public int getResourceIdForTarget(int index) {
- final TargetDrawable drawable = mTargetDrawables.get(index);
- return drawable == null ? 0 : drawable.getResourceId();
- }
-
- public void setEnableTarget(int resourceId, boolean enabled) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- target.setEnabled(enabled);
- break; // should never be more than one match
- }
- }
- }
-
- /**
- * Gets the position of a target in the array that matches the given resource.
- * @param resourceId
- * @return the index or -1 if not found
- */
- public int getTargetPosition(int resourceId) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- return i; // should never be more than one match
- }
- }
- return -1;
- }
-
- private boolean replaceTargetDrawables(Resources res, int existingResourceId,
- int newResourceId) {
- if (existingResourceId == 0 || newResourceId == 0) {
- return false;
- }
-
- boolean result = false;
- final ArrayList<TargetDrawable> drawables = mTargetDrawables;
- final int size = drawables.size();
- for (int i = 0; i < size; i++) {
- final TargetDrawable target = drawables.get(i);
- if (target != null && target.getResourceId() == existingResourceId) {
- target.setDrawable(res, newResourceId);
- result = true;
- }
- }
-
- if (result) {
- requestLayout(); // in case any given drawable's size changes
- }
-
- return result;
- }
-
- /**
- * Searches the given package for a resource to use to replace the Drawable on the
- * target with the given resource id
- * @param component of the .apk that contains the resource
- * @param name of the metadata in the .apk
- * @param existingResId the resource id of the target to search for
- * @return true if found in the given package and replaced at least one target Drawables
- */
- public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name,
- int existingResId) {
- if (existingResId == 0) return false;
-
- boolean replaced = false;
- if (component != null) {
- try {
- PackageManager packageManager = getContext().getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForActivity(component);
- replaced = replaceTargetDrawables(res, existingResId, iconResId);
- }
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to swap drawable; "
- + component.flattenToShortString() + " not found", e);
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- if (!replaced) {
- // Restore the original drawable
- replaceTargetDrawables(getContext().getResources(), existingResId, existingResId);
- }
- return replaced;
- }
-
- public class GlowpadExploreByTouchHelper extends ExploreByTouchHelper {
-
- private Rect mBounds = new Rect();
-
- public GlowpadExploreByTouchHelper(View forView) {
- super(forView);
- }
-
- @Override
- protected int getVirtualViewAt(float x, float y) {
- if (mGrabbedState == OnTriggerListener.CENTER_HANDLE) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.isEnabled() && target.getBounds().contains((int) x, (int) y)) {
- return i;
- }
- }
- return INVALID_ID;
- } else {
- return HOST_ID;
- }
- }
-
- @Override
- protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
- if (mGrabbedState == OnTriggerListener.CENTER_HANDLE) {
- // Add virtual views backwards so that accessibility services like switch
- // access traverse them in the correct order
- for (int i = mTargetDrawables.size() - 1; i >= 0; i--) {
- if (mTargetDrawables.get(i).isEnabled()) {
- virtualViewIds.add(i);
- }
- }
- }
- }
-
- @Override
- protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
- if (virtualViewId >= 0 && virtualViewId < mTargetDescriptions.size()) {
- event.setContentDescription(mTargetDescriptions.get(virtualViewId));
- }
- }
-
- @Override
- public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
- if (host == GlowPadView.this && event.getEventType()
- == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
- event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
- }
- super.onInitializeAccessibilityEvent(host, event);
- }
-
- @Override
- public void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
- if (mGrabbedState == OnTriggerListener.NO_HANDLE) {
- node.setClickable(true);
- node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
- }
- mBounds.set(0, 0, GlowPadView.this.getWidth(), GlowPadView.this.getHeight());
- node.setBoundsInParent(mBounds);
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (mGrabbedState == OnTriggerListener.NO_HANDLE) {
- // Simulate handle being grabbed to expose targets.
- trySwitchToFirstTouchState(mWaveCenterX, mWaveCenterY);
- invalidateRoot();
- return true;
- }
- return super.performAccessibilityAction(host, action, args);
- }
-
- @Override
- protected void onPopulateNodeForVirtualView(int virtualViewId,
- AccessibilityNodeInfoCompat node) {
- if (virtualViewId < mTargetDrawables.size()) {
- final TargetDrawable target = mTargetDrawables.get(virtualViewId);
- node.setBoundsInParent(target.getBounds());
- node.setClickable(true);
- node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
- node.setContentDescription(getTargetDescription(virtualViewId));
- }
- }
-
- @Override
- protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
- Bundle arguments) {
- if (action == AccessibilityNodeInfo.ACTION_CLICK) {
- if (virtualViewId >= 0 && virtualViewId < mTargetDrawables.size()) {
- dispatchTriggerEvent(virtualViewId);
- return true;
- }
- }
- return false;
- }
-
- }
-}
diff --git a/InCallUI/src/com/android/incallui/widget/multiwaveview/PointCloud.java b/InCallUI/src/com/android/incallui/widget/multiwaveview/PointCloud.java
deleted file mode 100644
index 07a2cb964..000000000
--- a/InCallUI/src/com/android/incallui/widget/multiwaveview/PointCloud.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2012 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.widget.multiwaveview;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-public class PointCloud {
- private static final float MIN_POINT_SIZE = 2.0f;
- private static final float MAX_POINT_SIZE = 4.0f;
- private static final int INNER_POINTS = 8;
- private static final String TAG = "PointCloud";
- private ArrayList<Point> mPointCloud = new ArrayList<Point>();
- private Drawable mDrawable;
- private float mCenterX;
- private float mCenterY;
- private Paint mPaint;
- private float mScale = 1.0f;
- private static final float PI = (float) Math.PI;
-
- // These allow us to have multiple concurrent animations.
- WaveManager waveManager = new WaveManager();
- GlowManager glowManager = new GlowManager();
- private float mOuterRadius;
-
- public class WaveManager {
- private float radius = 50;
- private float width = 200.0f; // TODO: Make configurable
- private float alpha = 0.0f;
- public void setRadius(float r) {
- radius = r;
- }
-
- public float getRadius() {
- return radius;
- }
-
- public void setAlpha(float a) {
- alpha = a;
- }
-
- public float getAlpha() {
- return alpha;
- }
- };
-
- public class GlowManager {
- private float x;
- private float y;
- private float radius = 0.0f;
- private float alpha = 0.0f;
-
- public void setX(float x1) {
- x = x1;
- }
-
- public float getX() {
- return x;
- }
-
- public void setY(float y1) {
- y = y1;
- }
-
- public float getY() {
- return y;
- }
-
- public void setAlpha(float a) {
- alpha = a;
- }
-
- public float getAlpha() {
- return alpha;
- }
-
- public void setRadius(float r) {
- radius = r;
- }
-
- public float getRadius() {
- return radius;
- }
- }
-
- class Point {
- float x;
- float y;
- float radius;
-
- public Point(float x2, float y2, float r) {
- x = (float) x2;
- y = (float) y2;
- radius = r;
- }
- }
-
- public PointCloud(Drawable drawable) {
- mPaint = new Paint();
- mPaint.setFilterBitmap(true);
- mPaint.setColor(Color.rgb(255, 255, 255)); // TODO: make configurable
- mPaint.setAntiAlias(true);
- mPaint.setDither(true);
-
- mDrawable = drawable;
- if (mDrawable != null) {
- drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- }
- }
-
- public void setCenter(float x, float y) {
- mCenterX = x;
- mCenterY = y;
- }
-
- public void makePointCloud(float innerRadius, float outerRadius) {
- if (innerRadius == 0) {
- Log.w(TAG, "Must specify an inner radius");
- return;
- }
- mOuterRadius = outerRadius;
- mPointCloud.clear();
- final float pointAreaRadius = (outerRadius - innerRadius);
- final float ds = (2.0f * PI * innerRadius / INNER_POINTS);
- final int bands = (int) Math.round(pointAreaRadius / ds);
- final float dr = pointAreaRadius / bands;
- float r = innerRadius;
- for (int b = 0; b <= bands; b++, r += dr) {
- float circumference = 2.0f * PI * r;
- final int pointsInBand = (int) (circumference / ds);
- float eta = PI/2.0f;
- float dEta = 2.0f * PI / pointsInBand;
- for (int i = 0; i < pointsInBand; i++) {
- float x = r * (float) Math.cos(eta);
- float y = r * (float) Math.sin(eta);
- eta += dEta;
- mPointCloud.add(new Point(x, y, r));
- }
- }
- }
-
- public void setScale(float scale) {
- mScale = scale;
- }
-
- public float getScale() {
- return mScale;
- }
-
- private static float hypot(float x, float y) {
- return (float) Math.hypot(x, y);
- }
-
- private static float max(float a, float b) {
- return a > b ? a : b;
- }
-
- public int getAlphaForPoint(Point point) {
- // Contribution from positional glow
- float glowDistance = hypot(glowManager.x - point.x, glowManager.y - point.y);
- float glowAlpha = 0.0f;
-
- if (glowDistance < glowManager.radius) {
- double cos = Math.cos(Math.PI * 0.25d * glowDistance / glowManager.radius);
- glowAlpha = glowManager.alpha * max(0.0f, (float) Math.pow(cos, 10.0d));
- }
-
- // Compute contribution from Wave
- float radius = hypot(point.x, point.y);
- float distanceToWaveRing = (radius - waveManager.radius);
- float waveAlpha = 0.0f;
- if (distanceToWaveRing < waveManager.width * 0.5f && distanceToWaveRing < 0.0f) {
- double cos = Math.cos(Math.PI * 0.25d * distanceToWaveRing / waveManager.width);
- waveAlpha = waveManager.alpha * max(0.0f, (float) Math.pow(cos, 20.0d));
- }
-
- return (int) (max(glowAlpha, waveAlpha) * 255);
- }
-
- private float interp(float min, float max, float f) {
- return min + (max - min) * f;
- }
-
- public void draw(Canvas canvas) {
- ArrayList<Point> points = mPointCloud;
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(mScale, mScale, mCenterX, mCenterY);
- for (int i = 0; i < points.size(); i++) {
- Point point = points.get(i);
- final float pointSize = interp(MAX_POINT_SIZE, MIN_POINT_SIZE,
- point.radius / mOuterRadius);
- final float px = point.x + mCenterX;
- final float py = point.y + mCenterY;
- int alpha = getAlphaForPoint(point);
-
- if (alpha == 0) continue;
-
- if (mDrawable != null) {
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- final float cx = mDrawable.getIntrinsicWidth() * 0.5f;
- final float cy = mDrawable.getIntrinsicHeight() * 0.5f;
- final float s = pointSize / MAX_POINT_SIZE;
- canvas.scale(s, s, px, py);
- canvas.translate(px - cx, py - cy);
- mDrawable.setAlpha(alpha);
- mDrawable.draw(canvas);
- canvas.restore();
- } else {
- mPaint.setAlpha(alpha);
- canvas.drawCircle(px, py, pointSize, mPaint);
- }
- }
- canvas.restore();
- }
-
-}
diff --git a/InCallUI/src/com/android/incallui/widget/multiwaveview/TargetDrawable.java b/InCallUI/src/com/android/incallui/widget/multiwaveview/TargetDrawable.java
deleted file mode 100644
index adc5324eb..000000000
--- a/InCallUI/src/com/android/incallui/widget/multiwaveview/TargetDrawable.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2011 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.widget.multiwaveview;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.Log;
-
-public class TargetDrawable {
- private static final String TAG = "TargetDrawable";
- private static final boolean DEBUG = false;
-
- public static final int[] STATE_ACTIVE =
- { android.R.attr.state_enabled, android.R.attr.state_active };
- public static final int[] STATE_INACTIVE =
- { android.R.attr.state_enabled, -android.R.attr.state_active };
- public static final int[] STATE_FOCUSED =
- { android.R.attr.state_enabled, -android.R.attr.state_active,
- android.R.attr.state_focused };
-
- private float mTranslationX = 0.0f;
- private float mTranslationY = 0.0f;
- private float mPositionX = 0.0f;
- private float mPositionY = 0.0f;
- private float mScaleX = 1.0f;
- private float mScaleY = 1.0f;
- private float mAlpha = 1.0f;
- private Drawable mDrawable;
- private boolean mEnabled = true;
- private final int mResourceId;
- private int mNumDrawables = 1;
- private Rect mBounds;
-
- /**
- * This is changed from the framework version to pass in the number of drawables in the
- * container. The framework version relies on private api's to get the count from
- * StateListDrawable.
- *
- * @param res
- * @param resId
- * @param count The number of drawables in the resource.
- */
- public TargetDrawable(Resources res, int resId, int count) {
- mResourceId = resId;
- setDrawable(res, resId);
- mNumDrawables = count;
- }
-
- public void setDrawable(Resources res, int resId) {
- // Note we explicitly don't set mResourceId to resId since we allow the drawable to be
- // swapped at runtime and want to re-use the existing resource id for identification.
- Drawable drawable = resId == 0 ? null : res.getDrawable(resId);
- // Mutate the drawable so we can animate shared drawable properties.
- mDrawable = drawable != null ? drawable.mutate() : null;
- resizeDrawables();
- setState(STATE_INACTIVE);
- }
-
- public TargetDrawable(TargetDrawable other) {
- mResourceId = other.mResourceId;
- // Mutate the drawable so we can animate shared drawable properties.
- mDrawable = other.mDrawable != null ? other.mDrawable.mutate() : null;
- resizeDrawables();
- setState(STATE_INACTIVE);
- }
-
- public void setState(int [] state) {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- d.setState(state);
- }
- }
-
- /**
- * Returns true if the drawable is a StateListDrawable and is in the focused state.
- *
- * @return
- */
- public boolean isActive() {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- int[] states = d.getState();
- for (int i = 0; i < states.length; i++) {
- if (states[i] == android.R.attr.state_focused) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns true if this target is enabled. Typically an enabled target contains a valid
- * drawable in a valid state. Currently all targets with valid drawables are valid.
- *
- * @return
- */
- public boolean isEnabled() {
- return mDrawable != null && mEnabled;
- }
-
- /**
- * Makes drawables in a StateListDrawable all the same dimensions.
- * If not a StateListDrawable, then justs sets the bounds to the intrinsic size of the
- * drawable.
- */
- private void resizeDrawables() {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- int maxWidth = 0;
- int maxHeight = 0;
-
- for (int i = 0; i < mNumDrawables; i++) {
- d.selectDrawable(i);
- Drawable childDrawable = d.getCurrent();
- maxWidth = Math.max(maxWidth, childDrawable.getIntrinsicWidth());
- maxHeight = Math.max(maxHeight, childDrawable.getIntrinsicHeight());
- }
-
- if (DEBUG) Log.v(TAG, "union of childDrawable rects " + d + " to: "
- + maxWidth + "x" + maxHeight);
- d.setBounds(0, 0, maxWidth, maxHeight);
-
- for (int i = 0; i < mNumDrawables; i++) {
- d.selectDrawable(i);
- Drawable childDrawable = d.getCurrent();
- if (DEBUG) Log.v(TAG, "sizing drawable " + childDrawable + " to: "
- + maxWidth + "x" + maxHeight);
- childDrawable.setBounds(0, 0, maxWidth, maxHeight);
- }
- } else if (mDrawable != null) {
- mDrawable.setBounds(0, 0,
- mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
- }
- }
-
- public void setX(float x) {
- mTranslationX = x;
- }
-
- public void setY(float y) {
- mTranslationY = y;
- }
-
- public void setScaleX(float x) {
- mScaleX = x;
- }
-
- public void setScaleY(float y) {
- mScaleY = y;
- }
-
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- }
-
- public float getX() {
- return mTranslationX;
- }
-
- public float getY() {
- return mTranslationY;
- }
-
- public float getScaleX() {
- return mScaleX;
- }
-
- public float getScaleY() {
- return mScaleY;
- }
-
- public float getAlpha() {
- return mAlpha;
- }
-
- public void setPositionX(float x) {
- mPositionX = x;
- }
-
- public void setPositionY(float y) {
- mPositionY = y;
- }
-
- public float getPositionX() {
- return mPositionX;
- }
-
- public float getPositionY() {
- return mPositionY;
- }
-
- public int getWidth() {
- return mDrawable != null ? mDrawable.getIntrinsicWidth() : 0;
- }
-
- public int getHeight() {
- return mDrawable != null ? mDrawable.getIntrinsicHeight() : 0;
- }
-
- public Rect getBounds() {
- if (mBounds == null) {
- mBounds = new Rect();
- }
- mBounds.set((int) (mTranslationX + mPositionX - getWidth() * 0.5),
- (int) (mTranslationY + mPositionY - getHeight() * 0.5),
- (int) (mTranslationX + mPositionX + getWidth() * 0.5),
- (int) (mTranslationY + mPositionY + getHeight() * 0.5));
- return mBounds;
- }
-
- public void draw(Canvas canvas) {
- if (mDrawable == null || !mEnabled) {
- return;
- }
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(mScaleX, mScaleY, mPositionX, mPositionY);
- canvas.translate(mTranslationX + mPositionX, mTranslationY + mPositionY);
- canvas.translate(-0.5f * getWidth(), -0.5f * getHeight());
- mDrawable.setAlpha((int) Math.round(mAlpha * 255f));
- mDrawable.draw(canvas);
- canvas.restore();
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public int getResourceId() {
- return mResourceId;
- }
-}
diff --git a/InCallUI/src/com/android/incallui/widget/multiwaveview/Tweener.java b/InCallUI/src/com/android/incallui/widget/multiwaveview/Tweener.java
deleted file mode 100644
index 7222442fe..000000000
--- a/InCallUI/src/com/android/incallui/widget/multiwaveview/Tweener.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2011 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.widget.multiwaveview;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-class Tweener {
- private static final String TAG = "Tweener";
- private static final boolean DEBUG = false;
-
- ObjectAnimator animator;
- private static HashMap<Object, Tweener> sTweens = new HashMap<Object, Tweener>();
-
- public Tweener(ObjectAnimator anim) {
- animator = anim;
- }
-
- private static void remove(Animator animator) {
- Iterator<Entry<Object, Tweener>> iter = sTweens.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<Object, Tweener> entry = iter.next();
- if (entry.getValue().animator == animator) {
- if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey())
- + " sTweens.size() = " + sTweens.size());
- iter.remove();
- break; // an animator can only be attached to one object
- }
- }
- }
-
- public static Tweener to(Object object, long duration, Object... vars) {
- long delay = 0;
- AnimatorUpdateListener updateListener = null;
- AnimatorListener listener = null;
- TimeInterpolator interpolator = null;
-
- // Iterate through arguments and discover properties to animate
- ArrayList<PropertyValuesHolder> props = new ArrayList<PropertyValuesHolder>(vars.length/2);
- for (int i = 0; i < vars.length; i+=2) {
- if (!(vars[i] instanceof String)) {
- throw new IllegalArgumentException("Key must be a string: " + vars[i]);
- }
- String key = (String) vars[i];
- Object value = vars[i+1];
-
- if ("simultaneousTween".equals(key)) {
- // TODO
- } else if ("ease".equals(key)) {
- interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
- } else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) {
- updateListener = (AnimatorUpdateListener) value;
- } else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
- listener = (AnimatorListener) value;
- } else if ("delay".equals(key)) {
- delay = ((Number) value).longValue();
- } else if ("syncWith".equals(key)) {
- // TODO
- } else if (value instanceof float[]) {
- props.add(PropertyValuesHolder.ofFloat(key,
- ((float[])value)[0], ((float[])value)[1]));
- } else if (value instanceof int[]) {
- props.add(PropertyValuesHolder.ofInt(key,
- ((int[])value)[0], ((int[])value)[1]));
- } else if (value instanceof Number) {
- float floatValue = ((Number)value).floatValue();
- props.add(PropertyValuesHolder.ofFloat(key, floatValue));
- } else {
- throw new IllegalArgumentException(
- "Bad argument for key \"" + key + "\" with value " + value.getClass());
- }
- }
-
- // Re-use existing tween, if present
- Tweener tween = sTweens.get(object);
- ObjectAnimator anim = null;
- if (tween == null) {
- anim = ObjectAnimator.ofPropertyValuesHolder(object,
- props.toArray(new PropertyValuesHolder[props.size()]));
- tween = new Tweener(anim);
- sTweens.put(object, tween);
- if (DEBUG) Log.v(TAG, "Added new Tweener " + tween);
- } else {
- anim = sTweens.get(object).animator;
- replace(props, object); // Cancel all animators for given object
- }
-
- if (interpolator != null) {
- anim.setInterpolator(interpolator);
- }
-
- // Update animation with properties discovered in loop above
- anim.setStartDelay(delay);
- anim.setDuration(duration);
- if (updateListener != null) {
- anim.removeAllUpdateListeners(); // There should be only one
- anim.addUpdateListener(updateListener);
- }
- if (listener != null) {
- anim.removeAllListeners(); // There should be only one.
- anim.addListener(listener);
- }
- anim.addListener(mCleanupListener);
-
- return tween;
- }
-
- Tweener from(Object object, long duration, Object... vars) {
- // TODO: for v of vars
- // toVars[v] = object[v]
- // object[v] = vars[v]
- return Tweener.to(object, duration, vars);
- }
-
- // Listener to watch for completed animations and remove them.
- private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- remove(animation);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- remove(animation);
- }
- };
-
- public static void reset() {
- if (DEBUG) {
- Log.v(TAG, "Reset()");
- if (sTweens.size() > 0) {
- Log.v(TAG, "Cleaning up " + sTweens.size() + " animations");
- }
- }
- sTweens.clear();
- }
-
- private static void replace(ArrayList<PropertyValuesHolder> props, Object... args) {
- for (final Object killobject : args) {
- Tweener tween = sTweens.get(killobject);
- if (tween != null) {
- tween.animator.cancel();
- if (props != null) {
- tween.animator.setValues(
- props.toArray(new PropertyValuesHolder[props.size()]));
- } else {
- sTweens.remove(tween);
- }
- }
- }
- }
-}