diff options
author | Stephen Bird <sbird@cyngn.com> | 2016-05-02 09:39:57 -0700 |
---|---|---|
committer | Richard MacGregor <rmacgregor@cyngn.com> | 2016-05-09 08:58:38 -0700 |
commit | 1513c75c14068c3a43f66ffa2431572896c76c96 (patch) | |
tree | 7329a892e8e88e98188ed6bed4a4dda2939f3dae /src | |
parent | f023f97370cfbe775a1fab04da3699e0f82c066b (diff) | |
download | packages_apps_InCallUI-1513c75c14068c3a43f66ffa2431572896c76c96.tar.gz packages_apps_InCallUI-1513c75c14068c3a43f66ffa2431572896c76c96.tar.bz2 packages_apps_InCallUI-1513c75c14068c3a43f66ffa2431572896c76c96.zip |
Move Mod plugins out of CallButtonFragment to ModButtonFragment
Things were getting crowded.
Add a new button bar explicitly for plugins.
Tickets CD-592 & NOTES-101
Change-Id: I1bdf074d8e3417161eb29cf05140016b084c5006
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/incallui/CallCardFragment.java | 6 | ||||
-rw-r--r-- | src/com/android/incallui/InCallActivity.java | 3 | ||||
-rw-r--r-- | src/com/android/incallui/ModButtonFragment.java | 450 | ||||
-rw-r--r-- | src/com/android/incallui/ModButtonPresenter.java | 428 |
4 files changed, 887 insertions, 0 deletions
diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 498d13aa..c64f200b 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -132,6 +132,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr // Container view that houses the primary call information private ViewGroup mPrimaryCallInfo; private View mCallButtonsContainer; + private View mModButtonsContainer; private TextView mRecordingTimeLabel; private TextView mRecordingIcon; @@ -284,6 +285,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container); mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner); mCallButtonsContainer = view.findViewById(R.id.callButtonFragment); + mModButtonsContainer = view.findViewById(R.id.modButtonFragment); mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage); mProgressSpinner = view.findViewById(R.id.progressSpinner); @@ -1225,6 +1227,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor); } mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor); + mModButtonsContainer.setBackgroundColor(themeColors.mSecondaryColor); mCallSubject.setTextColor(themeColors.mPrimaryColor); mCurrentThemeColors = themeColors; @@ -1272,6 +1275,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mFloatingActionButtonController.setScreenWidth(parent.getWidth()); mCallButtonsContainer.setAlpha(0); + mModButtonsContainer.setAlpha(0); mCallStateLabel.setAlpha(0); mPrimaryName.setAlpha(0); mCallTypeLabel.setAlpha(0); @@ -1283,6 +1287,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr assignTranslateAnimation(mCallNumberAndLabel, 3); assignTranslateAnimation(mCallTypeLabel, 4); assignTranslateAnimation(mCallButtonsContainer, 5); + assignTranslateAnimation(mModButtonsContainer, 6); final Animator animator = getShrinkAnimator(parent.getHeight(), originalHeight); @@ -1480,6 +1485,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private void setViewStatePostAnimation(OnLayoutChangeListener layoutChangeListener) { setViewStatePostAnimation(mCallButtonsContainer); + setViewStatePostAnimation(mModButtonsContainer); setViewStatePostAnimation(mCallStateLabel); setViewStatePostAnimation(mPrimaryName); setViewStatePostAnimation(mCallTypeLabel); diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index d5c5db8f..4d1cee5b 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -93,6 +93,7 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { private static final int SNACKBAR_TIMEOUT = 10000; // 10 seconds auto dismiss private CallButtonFragment mCallButtonFragment; + private ModButtonFragment mModButtonFragment; private CallCardFragment mCallCardFragment; private AnswerFragment mAnswerFragment; private DialpadFragment mDialpadFragment; @@ -371,6 +372,8 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { mConferenceManagerFragment = (ConferenceManagerFragment) fragment; } else if (fragment instanceof CallButtonFragment) { mCallButtonFragment = (CallButtonFragment) fragment; + } else if (fragment instanceof ModButtonFragment) { + mModButtonFragment = (ModButtonFragment) fragment; } } diff --git a/src/com/android/incallui/ModButtonFragment.java b/src/com/android/incallui/ModButtonFragment.java new file mode 100644 index 00000000..e3cda71c --- /dev/null +++ b/src/com/android/incallui/ModButtonFragment.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.contacts.common.util.MaterialColorMapUtils.MaterialPalette; +import com.android.incallui.incallapi.InCallPluginInfo; + +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +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.text.TextUtils; +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.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import static com.android.incallui.ModButtonFragment.Buttons.BUTTON_COUNT; +import static com.android.incallui.ModButtonFragment.Buttons.BUTTON_INCALL; +import static com.android.incallui.ModButtonFragment.Buttons.BUTTON_TAKE_NOTE; + +/** + * Fragment for mod control buttons + */ +public class ModButtonFragment + extends BaseFragment<ModButtonPresenter, ModButtonPresenter.ModButtonUi> + implements ModButtonPresenter.ModButtonUi, + View.OnClickListener { + + private static final String TAG = ModButtonFragment.class.getSimpleName(); + private static final boolean DEBUG = false; + + 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; + + private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT); + + private ImageButton mInCallProvider; + private ImageButton mTakeNoteButton; + private ImageButton mOverflowButton; + + private PopupMenu mOverflowPopup; + + private MaterialPalette mCurrentThemeColors; + + public interface Buttons { + int BUTTON_INCALL = 0; + int BUTTON_TAKE_NOTE = 1; + int BUTTON_COUNT = 2; + } + + @Override + public ModButtonPresenter createPresenter() { + return new ModButtonPresenter(); + } + + @Override + public ModButtonPresenter.ModButtonUi 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.mod_button_fragment, container, false); + + mInCallProvider = (ImageButton) parent.findViewById(R.id.inCallProviders); + mInCallProvider.setOnClickListener(this); + mTakeNoteButton = (ImageButton) parent.findViewById(R.id.takeNoteButton); + mTakeNoteButton.setOnClickListener(this); + mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton); + mOverflowButton.setOnClickListener(this); + + return parent; + } + + @Override + public void onResume() { + super.onResume(); + + updateColors(); + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (DEBUG) Log.d(this, "onClick(View " + view + ", id " + id + ")..."); + + switch(id) { + case R.id.inCallProviders: + getPresenter().switchToVideoCall(); + break; + case R.id.takeNoteButton: + getPresenter().takeNote(); + break; + case R.id.overflowButton: + if (mOverflowPopup != null) { + mOverflowPopup.show(); + } + break; + default: + 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; + } + + mCurrentThemeColors = themeColors; + + ImageButton[] normalButtons = { + mInCallProvider, + mTakeNoteButton, + mOverflowButton + }; + + for (ImageButton button : normalButtons) { + final LayerDrawable layers = (LayerDrawable) button.getBackground(); + final RippleDrawable btnDrawable = backgroundDrawable(themeColors); + layers.setDrawableByLayerId(R.id.backgroundItem, btnDrawable); + } + + updateButtonStates(); + } + + /** + * 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_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); + } + + // default + private void addUnselected(Resources res, StateListDrawable drawable, MaterialPalette palette) { + LayerDrawable unselectedDrawable = + (LayerDrawable) res.getDrawable(R.drawable.btn_mod_unselected); + ((GradientDrawable) unselectedDrawable.getDrawable(0)).setColor(palette.mSecondaryColor); + drawable.addState(new int[0], unselectedDrawable); + } + + + @Override + public void setEnabled(boolean isEnabled) { + mInCallProvider.setEnabled(isEnabled); + mTakeNoteButton.setEnabled(isEnabled); + mOverflowButton.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) { + switch (id) { + case BUTTON_INCALL: + return mInCallProvider; + case BUTTON_TAKE_NOTE: + return mTakeNoteButton; + default: + Log.w(this, "Invalid button id"); + return null; + } + } + + 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 ModButtonPresenter. 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 (button == null) { + continue; + } + 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 setDeepLinkNoteIcon(Drawable d) { + if (d == null) { + mTakeNoteButton.setVisibility(View.GONE); + } else { + createLayers(mTakeNoteButton, d); + } + } + + @Override + public void modifyChangeToVideoButton() { + List<InCallPluginInfo> contactInCallPlugins + = getPresenter().getContactInCallPluginInfoList(); + int listSize = (contactInCallPlugins != null) ? contactInCallPlugins.size() : 0; + if (listSize == 1) { + InCallPluginInfo info = contactInCallPlugins.get(0); + if (info != null && info.getPluginVideoIcon() != null) { + createLayers(mInCallProvider, info.getPluginVideoIcon()); + } + } else { + createLayers(mInCallProvider, getResources().getDrawable(R.drawable.ic_video)); + } + } + + /**The function is called when Video Call button gets pressed. The function creates and + * displays video call options. + */ + @Override + public void displayVideoCallProviderOptions() { + ModButtonPresenter.ModButtonUi ui = getUi(); + if (ui == null) { + Log.e(this, "Cannot display VideoCallOptions as ui is null"); + return; + } + + Context context = getContext(); + + final ArrayList<Drawable> icons = new ArrayList<Drawable>(); + final ArrayList<String> items = new ArrayList<String>(); + final ArrayList<Integer> itemToCallType = new ArrayList<Integer>(); + + // Prepare the string array and mapping. + List<InCallPluginInfo> contactInCallPlugins = + getPresenter().getContactInCallPluginInfoList(); + if (contactInCallPlugins != null && !contactInCallPlugins.isEmpty()) { + int i = 0; + for (InCallPluginInfo info : contactInCallPlugins) { + items.add(info.getPluginTitle()); + icons.add(info.getPluginBrandIcon()); + itemToCallType.add(i); + i++; + } + } + + ListAdapter adapter = new ListItemWithImageArrayAdapter(context.getApplicationContext(), + R.layout.videocall_handoff_item, items, icons); + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + final int selCallType = itemToCallType.get(item); + // InCall Plugin selected + getPresenter().handoverCallToVoIPPlugin(selCallType); + dialog.dismiss(); + } + }; + AlertDialog.Builder builder = new AlertDialog.Builder(getUi().getContext()); + builder.setTitle(R.string.video_call_option_title); + builder.setAdapter(adapter, listener); + final AlertDialog alert; + alert = builder.create(); + alert.show(); + } + + @Override + public Context getContext() { + return getActivity(); + } + + @Override + public void showInviteSnackbar(final PendingIntent inviteIntent, String inviteText) { + if (TextUtils.isEmpty(inviteText)) { + return; + } + final InCallActivity activity = (InCallActivity) getActivity(); + if (activity != null) { + activity.showInviteSnackbar(inviteIntent, inviteText); + } + } + + /** + * Adapter used to Array adapter with an icon and custom item layout + */ + private class ListItemWithImageArrayAdapter extends ArrayAdapter<String> { + private int mLayout; + private List<Drawable> mIcons; + + public ListItemWithImageArrayAdapter(Context context, int layout, List<String> titles, + List<Drawable> icons) { + super(context, 0, titles); + mLayout = layout; + mIcons = icons; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + String title = getItem(position); + Drawable icon = mIcons.get(position); + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(mLayout, parent, false); + } + + TextView textView = (TextView) convertView.findViewById(R.id.title); + textView.setText(title); + + ImageView msgIcon = (ImageView) convertView.findViewById(R.id.icon); + msgIcon.setImageDrawable(icon); + + return convertView; + } + } + + private void createLayers(ImageButton button, Drawable icon) { + Drawable newIcon = icon.getConstantState().newDrawable().mutate(); + + final LayerDrawable layerDrawable = + (LayerDrawable) getResources().getDrawable(R.drawable.btn_mod_drawable).mutate(); + + newIcon.setTintList(getResources().getColorStateList(R.color.mod_icon_tint)); + newIcon.setAutoMirrored(false); + + layerDrawable.setDrawableByLayerId(R.id.foregroundItem, newIcon); + button.setBackgroundDrawable(layerDrawable); + } +} diff --git a/src/com/android/incallui/ModButtonPresenter.java b/src/com/android/incallui/ModButtonPresenter.java new file mode 100644 index 00000000..df350f54 --- /dev/null +++ b/src/com/android/incallui/ModButtonPresenter.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.deeplink.DeepLinkIntegrationManager; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; +import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; +import com.android.incallui.InCallPresenter.CanAddCallListener; +import com.android.incallui.InCallPresenter.InCallDetailsListener; +import com.android.incallui.InCallPresenter.InCallPluginUpdateListener; +import com.android.incallui.InCallPresenter.InCallState; +import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.incallui.InCallPresenter.IncomingCallListener; +import com.android.incallui.incallapi.InCallPluginInfo; +import com.android.phone.common.ambient.AmbientConnection; +import com.android.phone.common.incall.StartInCallCallReceiver; +import com.cyanogen.ambient.common.api.AmbientApiClient; +import com.cyanogen.ambient.common.api.ResultCallback; +import com.cyanogen.ambient.deeplink.DeepLink; +import com.cyanogen.ambient.deeplink.applicationtype.DeepLinkApplicationType; +import com.cyanogen.ambient.deeplink.linkcontent.CallDeepLinkContent; +import com.cyanogen.ambient.deeplink.linkcontent.DeepLinkContentType; +import com.cyanogen.ambient.incall.InCallServices; +import com.cyanogen.ambient.incall.extension.OriginCodes; +import com.cyanogen.ambient.incall.extension.StartCallRequest; +import com.cyanogen.ambient.incall.extension.StatusCodes; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; + +import java.util.List; + +import cyanogenmod.providers.CMSettings; + +import static com.android.incallui.ModButtonFragment.Buttons.BUTTON_INCALL; +import static com.android.incallui.ModButtonFragment.Buttons.BUTTON_TAKE_NOTE; + +/** + * Logic for mod buttons. + */ +public class ModButtonPresenter extends Presenter<ModButtonPresenter.ModButtonUi> + implements InCallStateListener, IncomingCallListener, + InCallDetailsListener, CanAddCallListener, CallList.ActiveSubChangeListener, + StartInCallCallReceiver.Receiver, ContactInfoCacheCallback, InCallPluginUpdateListener { + + private static final String TAG = ModButtonPresenter.class.getSimpleName(); + private static final boolean DEBUG = false; + + private Call mCall; + private DeepLink mNoteDeepLink; + private ContactInfoCache.ContactCacheEntry mPrimaryContactInfo; + private StartInCallCallReceiver mCallback; + + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + if (DEBUG) Log.i(TAG, "Got InCallPlugin result callback code = " + resultCode); + + switch (resultCode) { + case StatusCodes.StartCall.HANDOVER_CONNECTED: + if (mCall == null) { + return; + } + + if (DEBUG) Log.i(TAG, "Disconnecting call: " + mCall); + TelecomAdapter.getInstance().disconnectCall(mCall.getId()); + break; + default: + Log.i(TAG, "Nothing to do for this InCallPlugin resultcode = " + resultCode); + } + } + + public ModButtonPresenter() {} + + @Override + public void onUiReady(ModButtonUi ui) { + super.onUiReady(ui); + + // register for call state changes last + final InCallPresenter inCallPresenter = InCallPresenter.getInstance(); + inCallPresenter.addListener(this); + inCallPresenter.addIncomingCallListener(this); + inCallPresenter.addDetailsListener(this); + inCallPresenter.addCanAddCallListener(this); + inCallPresenter.addInCallPluginUpdateListener(this); + CallList.getInstance().addActiveSubChangeListener(this); + + // Update the buttons state immediately for the current call + onStateChange(InCallState.NO_CALLS, inCallPresenter.getInCallState(), + CallList.getInstance()); + } + + @Override + public void onUiUnready(ModButtonUi ui) { + super.onUiUnready(ui); + + InCallPresenter.getInstance().removeListener(this); + InCallPresenter.getInstance().removeIncomingCallListener(this); + InCallPresenter.getInstance().removeDetailsListener(this); + InCallPresenter.getInstance().removeCanAddCallListener(this); + InCallPresenter.getInstance().removeInCallPluginUpdateListener(this); + CallList.getInstance().removeActiveSubChangeListener(this); + } + + @Override + public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { + + if (newState == InCallState.OUTGOING) { + mCall = callList.getOutgoingCall(); + } else if (newState == InCallState.INCALL) { + mCall = callList.getActiveOrBackgroundCall(); + } else if (newState == InCallState.INCOMING) { + mCall = callList.getIncomingCall(); + } else { + mCall = null; + } + + if (mCall != null && mPrimaryContactInfo == null) { + startContactInfoSearch(mCall, newState == InCallState.INCOMING); + getPreferredLinks(); + } + + updateUi(newState, mCall); + } + + /** + * Starts a query for more contact data for the save primary and secondary calls. + */ + private void startContactInfoSearch(final Call call, boolean isIncoming) { + final ContactInfoCache cache = ContactInfoCache.getInstance(getUi().getContext()); + + cache.findInfo(call, isIncoming, this); + } + + /** + * 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); + } + } + + public List<InCallPluginInfo> getContactInCallPluginInfoList() { + List<InCallPluginInfo> inCallPluginInfoList = null; + if (mCall != null) { + final ContactInfoCache cache = ContactInfoCache.getInstance(getUi().getContext()); + if (cache != null) { + ContactCacheEntry contactInfo = cache.getInfo(mCall.getId()); + if (contactInfo != null) { + inCallPluginInfoList = contactInfo.inCallPluginInfoList; + } + if (inCallPluginInfoList == null) { + cache.refreshPluginInfo(mCall, this); + } + } + } + return inCallPluginInfoList; + } + + public void switchToVideoCall() { + List<InCallPluginInfo> contactInCallPlugins = getContactInCallPluginInfoList(); + int listSize = (contactInCallPlugins != null) ? contactInCallPlugins.size() : 0; + if (listSize == 1) { + // If only one InCall Plugin available + handoverCallToVoIPPlugin(); + } else if (listSize > 0){ + // If multiple sources available + getUi().displayVideoCallProviderOptions(); + } + } + + public void handoverCallToVoIPPlugin() { + handoverCallToVoIPPlugin(0); + } + + public void handoverCallToVoIPPlugin(int contactPluginIndex) { + if (getUi() == null || getUi().getContext() == null) { + Log.e(TAG, "getUi returned null or can't find context."); + return; + } + + final Context ctx = getUi().getContext(); + List<InCallPluginInfo> inCallPluginInfoList = getContactInCallPluginInfoList(); + if (inCallPluginInfoList != null && inCallPluginInfoList.size() > contactPluginIndex) { + InCallPluginInfo info = inCallPluginInfoList.get(contactPluginIndex); + final ComponentName component = info.getPluginComponent(); + final String userId = info.getUserId(); + final String mimeType = info.getMimeType(); + if (component != null && !TextUtils.isEmpty(component.flattenToString()) && + !TextUtils.isEmpty(mimeType)) { + // Attempt call handover + final PendingIntent inviteIntent = info.getPluginInviteIntent(); + if (!TextUtils.isEmpty(userId)) { + AmbientApiClient client = + AmbientConnection.CLIENT.get(ctx.getApplicationContext()); + + mCallback = new StartInCallCallReceiver(new Handler(Looper.myLooper())); + mCallback.setReceiver(ModButtonPresenter.this); + StartCallRequest request = new StartCallRequest(userId, + OriginCodes.CALL_HANDOVER, + StartCallRequest.FLAG_CALL_TRANSFER, + mCallback); + + if (DEBUG) Log.i(TAG, "Starting InCallPlugin call for = " + userId); + InCallServices.getInstance().startVideoCall(client, component, request); + } else { + // Attempt invite + if (DEBUG) { + final ContactInfoCache cache = ContactInfoCache.getInstance(ctx); + ContactCacheEntry entry = cache.getInfo(mCall.getId()); + Uri lookupUri = entry.lookupUri; + Log.i(TAG, "Attempting invite for " + lookupUri.toString()); + } + + String inviteText; + if (inviteIntent != null) { + // Attempt contact invite + inviteText = ctx.getApplicationContext() + .getString(R.string.snackbar_incall_plugin_contact_invite, + info.getPluginTitle()); + } else { + // Inform user to add contact manually, no invite intent found + inviteText = ctx.getApplicationContext() + .getString(R.string.snackbar_incall_plugin_no_invite_found, + info.getPluginTitle()); + } + getUi().showInviteSnackbar(inviteIntent, inviteText); + } + } + } + } + + private void updateUi(InCallState state, Call call) { + if (DEBUG) Log.d(this, "Updating call UI for call: ", call); + + final ModButtonUi ui = getUi(); + if (ui == null) { + return; + } + + final boolean isProvisioned = isDeviceProvisionedInSettingsDb(ui.getContext()); + final boolean isEnabled = isProvisioned && + state.isConnectingOrConnected() && + 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 ModButtonUi ui = getUi(); + final boolean isProvisioned = isDeviceProvisionedInSettingsDb(ui.getContext()); + + List<InCallPluginInfo> contactInCallPlugins = getContactInCallPluginInfoList(); + final boolean shouldShowInCall = isProvisioned && + contactInCallPlugins != null && !contactInCallPlugins.isEmpty(); + final boolean showNote = isProvisioned && + DeepLinkIntegrationManager.getInstance().ambientIsAvailable(getUi().getContext()) && + mNoteDeepLink != null; + + ui.showButton(BUTTON_INCALL, shouldShowInCall); + if (shouldShowInCall) { + ui.modifyChangeToVideoButton(); + } + ui.showButton(BUTTON_TAKE_NOTE, showNote); + + ui.updateButtonStates(); + } + + private void contactUpdated() { + if (DEBUG) Log.i(this, "contactUpdated"); + if (getUi() != null && mCall != null) { + updateButtonsState(mCall); + } + } + + @Override + public void onInCallPluginUpdated() { + if (DEBUG) Log.i(this, "onInCallPluginUpdated"); + contactUpdated(); + } + + @Override + public void onContactInfoComplete(String callId, ContactCacheEntry entry) { + if (DEBUG) Log.i(this, "onContactInfoComplete"); + mPrimaryContactInfo = entry; + contactUpdated(); + } + + @Override + public void onImageLoadComplete(String callId, ContactCacheEntry entry) { + // Stub + } + + public interface ModButtonUi extends Ui { + void showButton(int buttonId, boolean show); + void enableButton(int buttonId, boolean enable); + void setEnabled(boolean on); + void modifyChangeToVideoButton(); + void displayVideoCallProviderOptions(); + void showInviteSnackbar(PendingIntent inviteIntent, String inviteText); + void setDeepLinkNoteIcon(Drawable d); + + /** + * 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(); + } + + public void onActiveSubChanged(int subId) { + InCallState state = InCallPresenter.getInstance() + .getPotentialStateFromCallList(CallList.getInstance()); + + onStateChange(null, state, CallList.getInstance()); + } + + public void takeNote() { + if (mCall != null && mNoteDeepLink != null) { + Context ctx = getUi().getContext(); + + android.telecom.Call.Details details = mCall.getTelecommCall().getDetails(); + CallDeepLinkContent content = new CallDeepLinkContent(mNoteDeepLink); + content.setName(TextUtils.isEmpty(mPrimaryContactInfo.name) ? + ctx.getString(R.string.deeplink_unknown_caller) : mPrimaryContactInfo.name); + content.setNumber(mCall.getNumber()); + content.setUri(DeepLinkIntegrationManager.generateCallUri(mCall.getNumber(), + details.getCreateTimeMillis())); + DeepLinkIntegrationManager.getInstance().sendContentSentEvent(ctx, mNoteDeepLink, + new ComponentName(ctx, CallButtonPresenter.class)); + ctx.startActivity(content.build()); + + } + } + + public void getPreferredLinks() { + if (mCall != null) { + Uri callUri = DeepLinkIntegrationManager.generateCallUri(mCall.getNumber(), + mCall.getCreateTimeMillis()); + DeepLinkIntegrationManager.getInstance().getPreferredLinksFor(mNoteDeepLinkCallback, + DeepLinkContentType.CALL, callUri); + } + } + + private ResultCallback<DeepLink.DeepLinkResultList> mNoteDeepLinkCallback = + new ResultCallback<DeepLink.DeepLinkResultList>() { + @Override + public void onResult(DeepLink.DeepLinkResultList deepLinkResult) { + if (getUi() == null) { + return; + } + + Drawable toDraw = null; + if (deepLinkResult != null && deepLinkResult.getResults() != null && + getUi() != null) { + List<DeepLink> links = deepLinkResult.getResults(); + for (DeepLink result : links) { + if (result.getApplicationType() == DeepLinkApplicationType.NOTE) { + mNoteDeepLink = result; + toDraw = result.getDrawableIcon(getUi().getContext()).mutate(); + break; + } + } + } + getUi().setDeepLinkNoteIcon(toDraw); + } + }; + + private boolean isDeviceProvisionedInSettingsDb(Context context) { + return (CMSettings.Secure.getInt(context.getContentResolver(), + CMSettings.Secure.CM_SETUP_WIZARD_COMPLETED, 0) != 0) && + (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0); + } +} |