summaryrefslogtreecommitdiff
path: root/InCallUI/src/com/android/incallui
diff options
context:
space:
mode:
authorEric Erfanian <erfanian@google.com>2017-02-22 16:32:36 -0800
committerEric Erfanian <erfanian@google.com>2017-03-01 09:56:52 -0800
commitccca31529c07970e89419fb85a9e8153a5396838 (patch)
treea7034c0a01672b97728c13282a2672771cd28baa /InCallUI/src/com/android/incallui
parente7ae4624ba6f25cb8e648db74e0d64c0113a16ba (diff)
Update dialer sources.
Test: Built package and system image. This change clobbers the old source, and is an export from an internal Google repository. The internal repository was forked form Android in March, and this change includes modifications since then, to near the v8 release. Since the fork, we've moved code from monolithic to independent modules. In addition, we've switched to Blaze/Bazel as the build sysetm. This export, however, still uses make. New dependencies have been added: - Dagger - Auto-Value - Glide - Libshortcutbadger Going forward, development will still be in Google3, and the Gerrit release will become an automated export, with the next drop happening in ~ two weeks. Android.mk includes local modifications from ToT. Abridged changelog: Bug fixes ● Not able to mute, add a call when using Phone app in multiwindow mode ● Double tap on keypad triggering multiple key and tones ● Reported spam numbers not showing as spam in the call log ● Crash when user tries to block number while Phone app is not set as default ● Crash when user picks a number from search auto-complete list Visual Voicemail (VVM) improvements ● Share Voicemail audio via standard exporting mechanisms that support file attachment (email, MMS, etc.) ● Make phone number, email and web sites in VVM transcript clickable ● Set PIN before declining VVM Terms of Service {Carrier} ● Set client type for outbound visual voicemail SMS {Carrier} New incoming call and incall UI on older devices (Android M) ● Updated Phone app icon ● New incall UI (large buttons, button labels) ● New and animated Answer/Reject gestures Accessibility ● Add custom answer/decline call buttons on answer screen for touch exploration accessibility services ● Increase size of touch target ● Add verbal feedback when a Voicemail fails to load ● Fix pressing of Phone buttons while in a phone call using Switch Access ● Fix selecting and opening contacts in talkback mode ● Split focus for ‘Learn More’ link in caller id & spam to help distinguish similar text Other ● Backup & Restore for App Preferences ● Prompt user to enable Wi-Fi calling if the call ends due to out of service and Wi-Fi is connected ● Rename “Dialpad” to “Keypad” ● Show "Private number" for restricted calls ● Delete unused items (vcard, add contact, call history) from Phone menu Change-Id: I2a7e53532a24c21bf308bf0a6d178d7ddbca4958
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);
- }
- }
- }
- }
-}