diff options
Diffstat (limited to 'InCallUI/src/com/android/incallui')
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); - } - } - } - } -} |