/* * 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.phone; import android.content.Context; import android.util.Log; import android.view.ContextThemeWrapper; import com.android.internal.telephony.Call; import com.android.internal.telephony.Phone; /** * Helper class to manage the options menu for the InCallScreen. * * This class is the "Model" (in M-V-C nomenclature) for the in-call menu; * it knows about all possible menu items, and contains logic to determine * the current state and enabledness of each item based on the state of * the Phone. * * The corresponding View classes are InCallMenuView, which is used purely * to lay out and draw the menu, and InCallMenuItemView, which is the View * for a single item. */ class InCallMenu { private static final String LOG_TAG = "PHONE/InCallMenu"; private static final boolean DBG = false; /** * Reference to the InCallScreen activity that owns us. This will be * null if we haven't been initialized yet *or* after the InCallScreen * activity has been destroyed. */ private InCallScreen mInCallScreen; /** * Our corresponding View class. */ private InCallMenuView mInCallMenuView; /** * All possible menu items (see initMenu().) */ InCallMenuItemView mManageConference; InCallMenuItemView mShowDialpad; InCallMenuItemView mEndCall; InCallMenuItemView mAddCall; InCallMenuItemView mSwapCalls; InCallMenuItemView mMergeCalls; InCallMenuItemView mBluetooth; InCallMenuItemView mSpeaker; InCallMenuItemView mMute; InCallMenuItemView mHold; InCallMenuItemView mAnswerAndHold; InCallMenuItemView mAnswerAndEnd; InCallMenuItemView mAnswer; InCallMenuItemView mIgnore; InCallMenu(InCallScreen inCallScreen) { if (DBG) log("InCallMenu constructor..."); mInCallScreen = inCallScreen; } /** * Null out our reference to the InCallScreen activity. * This indicates that the InCallScreen activity has been destroyed. */ void clearInCallScreenReference() { mInCallScreen = null; if (mInCallMenuView != null) mInCallMenuView.clearInCallScreenReference(); } /* package */ InCallMenuView getView() { return mInCallMenuView; } /** * Initializes the in-call menu by creating a new InCallMenuView, * creating all possible menu items, and loading them into the * InCallMenuView. * * The only initialization of the individual items we do here is * one-time stuff, like setting the ID and click listener, or calling * setIndicatorVisible() for buttons that have a green LED, or calling * setText() for buttons whose text never changes. The actual * *current* state and enabledness of each item is set in * updateItems(). */ /* package */ void initMenu() { if (DBG) log("initMenu()..."); // Explicitly use the "icon menu" theme for the Views we create. Context wrappedContext = new ContextThemeWrapper( mInCallScreen, com.android.internal.R.style.Theme_IconMenu); mInCallMenuView = new InCallMenuView(wrappedContext, mInCallScreen); // // Create all possible InCallMenuView objects. // mManageConference = new InCallMenuItemView(wrappedContext); mManageConference.setId(R.id.menuManageConference); mManageConference.setOnClickListener(mInCallScreen); mManageConference.setText(R.string.menu_manageConference); mManageConference.setIconResource(com.android.internal.R.drawable.ic_menu_allfriends); mShowDialpad = new InCallMenuItemView(wrappedContext); mShowDialpad.setId(R.id.menuShowDialpad); mShowDialpad.setOnClickListener(mInCallScreen); mShowDialpad.setText(R.string.menu_showDialpad); // or "Hide dialpad" if it's open mShowDialpad.setIconResource(R.drawable.ic_menu_dial_pad); mEndCall = new InCallMenuItemView(wrappedContext); mEndCall.setId(R.id.menuEndCall); mEndCall.setOnClickListener(mInCallScreen); mEndCall.setText(R.string.menu_endCall); mEndCall.setIconResource(R.drawable.ic_menu_end_call); mAddCall = new InCallMenuItemView(wrappedContext); mAddCall.setId(R.id.menuAddCall); mAddCall.setOnClickListener(mInCallScreen); mAddCall.setText(R.string.menu_addCall); mAddCall.setIconResource(android.R.drawable.ic_menu_add); mSwapCalls = new InCallMenuItemView(wrappedContext); mSwapCalls.setId(R.id.menuSwapCalls); mSwapCalls.setOnClickListener(mInCallScreen); mSwapCalls.setText(R.string.menu_swapCalls); mSwapCalls.setIconResource(R.drawable.ic_menu_swap_calls); mMergeCalls = new InCallMenuItemView(wrappedContext); mMergeCalls.setId(R.id.menuMergeCalls); mMergeCalls.setOnClickListener(mInCallScreen); mMergeCalls.setText(R.string.menu_mergeCalls); mMergeCalls.setIconResource(R.drawable.ic_menu_merge_calls); // TODO: Icons for menu items we don't have yet: // R.drawable.ic_menu_answer_call // R.drawable.ic_menu_silence_ringer mBluetooth = new InCallMenuItemView(wrappedContext); mBluetooth.setId(R.id.menuBluetooth); mBluetooth.setOnClickListener(mInCallScreen); mBluetooth.setText(R.string.menu_bluetooth); mBluetooth.setIndicatorVisible(true); mSpeaker = new InCallMenuItemView(wrappedContext); mSpeaker.setId(R.id.menuSpeaker); mSpeaker.setOnClickListener(mInCallScreen); mSpeaker.setText(R.string.menu_speaker); mSpeaker.setIndicatorVisible(true); mMute = new InCallMenuItemView(wrappedContext); mMute.setId(R.id.menuMute); mMute.setOnClickListener(mInCallScreen); mMute.setText(R.string.menu_mute); mMute.setIndicatorVisible(true); mHold = new InCallMenuItemView(wrappedContext); mHold.setId(R.id.menuHold); mHold.setOnClickListener(mInCallScreen); mHold.setText(R.string.menu_hold); mHold.setIndicatorVisible(true); mAnswerAndHold = new InCallMenuItemView(wrappedContext); mAnswerAndHold.setId(R.id.menuAnswerAndHold); mAnswerAndHold.setOnClickListener(mInCallScreen); mAnswerAndHold.setText(R.string.menu_answerAndHold); mAnswerAndEnd = new InCallMenuItemView(wrappedContext); mAnswerAndEnd.setId(R.id.menuAnswerAndEnd); mAnswerAndEnd.setOnClickListener(mInCallScreen); mAnswerAndEnd.setText(R.string.menu_answerAndEnd); mAnswer = new InCallMenuItemView(wrappedContext); mAnswer.setId(R.id.menuAnswer); mAnswer.setOnClickListener(mInCallScreen); mAnswer.setText(R.string.menu_answer); mIgnore = new InCallMenuItemView(wrappedContext); mIgnore.setId(R.id.menuIgnore); mIgnore.setOnClickListener(mInCallScreen); mIgnore.setText(R.string.menu_ignore); // // Load all the items into the correct "slots" in the InCallMenuView. // // Row 0 is the topmost row onscreen, item 0 is the leftmost item in a row. // // Individual items may be disabled or hidden, but never move between // rows or change their order within a row. // // TODO: these items and their layout ought be specifiable // entirely in XML (just like we currently do with res/menu/*.xml // files.) // // Row 0: // This usually has "Show/Hide dialpad", but that gets replaced by // "Manage conference" if a conference call is active. PhoneApp app = PhoneApp.getInstance(); // As managing conference is only valid for GSM and not for CDMA int phoneType = app.phone.getPhoneType(); if (phoneType == Phone.PHONE_TYPE_GSM) { mInCallMenuView.addItemView(mManageConference, 0); } mInCallMenuView.addItemView(mShowDialpad, 0); // Row 1: mInCallMenuView.addItemView(mSwapCalls, 1); mInCallMenuView.addItemView(mMergeCalls, 1); mInCallMenuView.addItemView(mAddCall, 1); mInCallMenuView.addItemView(mEndCall, 1); // Row 2: // In this row we see *either* bluetooth/speaker/mute/hold // *or* answerAndHold/answerAndEnd, but never all 6 together. // For CDMA only Answer or Ignore option is valid for a Call Waiting scenario if (phoneType == Phone.PHONE_TYPE_CDMA) { mInCallMenuView.addItemView(mAnswer, 2); mInCallMenuView.addItemView(mIgnore, 2); } else if (phoneType == Phone.PHONE_TYPE_GSM) { mInCallMenuView.addItemView(mHold, 2); mInCallMenuView.addItemView(mAnswerAndHold, 2); mInCallMenuView.addItemView(mAnswerAndEnd, 2); } else { throw new IllegalStateException("Unexpected phone type: " + phoneType); } mInCallMenuView.addItemView(mMute, 2); mInCallMenuView.addItemView(mSpeaker, 2); mInCallMenuView.addItemView(mBluetooth, 2); mInCallMenuView.dumpState(); } /** * Updates the enabledness and visibility of all items in the * InCallMenuView based on the current state of the Phone. * * This is called every time we need to display the menu, right before * it becomes visible. * * @return true if we successfully updated the items and it's OK * to go ahead and show the menu, or false if * we shouldn't show the menu at all. */ /* package */ boolean updateItems(Phone phone) { if (DBG) log("updateItems()..."); // if (DBG) PhoneUtils.dumpCallState(phone); // If the phone is totally idle (like in the "call ended" state) // there's no menu at all. if (phone.getState() == Phone.State.IDLE) { if (DBG) log("- Phone is idle! Don't show the menu..."); return false; } final boolean hasRingingCall = !phone.getRingingCall().isIdle(); final boolean hasActiveCall = !phone.getForegroundCall().isIdle(); final Call.State fgCallState = phone.getForegroundCall().getState(); final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle(); // For OTA call, only show dialpad, endcall, speaker, and mute menu items if (hasActiveCall && (PhoneApp.getInstance().isOtaCallInActiveState())) { mAnswerAndHold.setVisible(false); mAnswerAndHold.setEnabled(false); mAnswerAndEnd.setVisible(false); mAnswerAndEnd.setEnabled(false); mManageConference.setVisible(false); mAddCall.setEnabled(false); mSwapCalls.setEnabled(false); mMergeCalls.setEnabled(false); mHold.setEnabled(false); mBluetooth.setEnabled(false); mMute.setEnabled(false); mAnswer.setVisible(false); mIgnore.setVisible(false); boolean inConferenceCall = PhoneUtils.isConferenceCall(phone.getForegroundCall()); boolean showShowDialpad = !inConferenceCall; boolean enableShowDialpad = showShowDialpad && mInCallScreen.okToShowDialpad(); mShowDialpad.setVisible(showShowDialpad); mShowDialpad.setEnabled(enableShowDialpad); boolean isDtmfDialerOpened = mInCallScreen.isDialerOpened(); mShowDialpad.setText(isDtmfDialerOpened ? R.string.menu_hideDialpad : R.string.menu_showDialpad); mEndCall.setVisible(true); mEndCall.setEnabled(true); mSpeaker.setVisible(true); mSpeaker.setEnabled(true); boolean speakerOn = PhoneUtils.isSpeakerOn(mInCallScreen.getApplicationContext()); mSpeaker.setIndicatorState(speakerOn); mInCallMenuView.updateVisibility(); return true; } // Special cases when an incoming call is ringing. if (hasRingingCall) { // In the "call waiting" state, show ONLY the "answer & end" // and "answer & hold" buttons, and nothing else. // TODO: be sure to test this for "only one line in use and it's // active" AND for "only one line in use and it's on hold". if (hasActiveCall && !hasHoldingCall) { int phoneType = phone.getPhoneType(); // For CDMA only make "Answer" and "Ignore" visible if (phoneType == Phone.PHONE_TYPE_CDMA) { mAnswer.setVisible(true); mAnswer.setEnabled(true); mIgnore.setVisible(true); mIgnore.setEnabled(true); // Explicitly remove GSM menu items mAnswerAndHold.setVisible(false); mAnswerAndEnd.setVisible(false); } else if (phoneType == Phone.PHONE_TYPE_GSM) { mAnswerAndHold.setVisible(true); mAnswerAndHold.setEnabled(true); mAnswerAndEnd.setVisible(true); mAnswerAndEnd.setEnabled(true); // Explicitly remove CDMA menu items mAnswer.setVisible(false); mIgnore.setVisible(false); mManageConference.setVisible(false); } else { throw new IllegalStateException("Unexpected phone type: " + phoneType); } mShowDialpad.setVisible(false); mEndCall.setVisible(false); mAddCall.setVisible(false); mSwapCalls.setVisible(false); mMergeCalls.setVisible(false); mBluetooth.setVisible(false); mSpeaker.setVisible(false); mMute.setVisible(false); mHold.setVisible(false); // Done updating the individual items. // The last step is to tell the InCallMenuView to update itself // based on any visibility changes that just happened. mInCallMenuView.updateVisibility(); return true; } else { // If there's an incoming ringing call but there aren't // any "special actions" to take, don't show a menu at all. return false; } } // TODO: double-check if any items here need to be disabled based on: // boolean keyguardRestricted = mInCallScreen.isPhoneStateRestricted(); // The InCallControlState object tells us the enabledness and/or // state of the various menu items: InCallControlState inCallControlState = mInCallScreen.getUpdatedInCallControlState(); // Manage conference: visible only if the foreground call is a // conference call. Enabled unless the "Manage conference" UI is // already up. mManageConference.setVisible(inCallControlState.manageConferenceVisible); mManageConference.setEnabled(inCallControlState.manageConferenceEnabled); // "Show/Hide dialpad": // - Visible: only in portrait mode, but NOT when "Manage // conference" is available (since that's shown instead.) // - Enabled: Only when it's OK to use the dialpad in the first // place (i.e. in the same states where the SlidingDrawer handle // is visible.) // - Text label: "Show" or "Hide", depending on the current state // of the sliding drawer. // (Note this logic is totally specific to the in-call menu, so // this state doesn't come from the inCallControlState object.) boolean showShowDialpad = !inCallControlState.manageConferenceVisible; boolean enableShowDialpad = showShowDialpad && mInCallScreen.okToShowDialpad(); mShowDialpad.setVisible(showShowDialpad); mShowDialpad.setEnabled(enableShowDialpad); mShowDialpad.setText(inCallControlState.dialpadVisible ? R.string.menu_hideDialpad : R.string.menu_showDialpad); // "End call": this button has no state and is always visible. // It's also always enabled. (Actually it *would* need to be // disabled if the phone was totally idle, but the entire in-call // menu is already disabled in that case (see above.)) mEndCall.setVisible(true); mEndCall.setEnabled(true); // "Add call" mAddCall.setVisible(true); mAddCall.setEnabled(inCallControlState.canAddCall); // Swap / merge calls mSwapCalls.setVisible(true); mSwapCalls.setEnabled(inCallControlState.canSwap); mMergeCalls.setVisible(true); mMergeCalls.setEnabled(inCallControlState.canMerge); // "Bluetooth": always visible, only enabled if BT is available. mBluetooth.setVisible(true); mBluetooth.setEnabled(inCallControlState.bluetoothEnabled); mBluetooth.setIndicatorState(inCallControlState.bluetoothIndicatorOn); // "Speaker": always visible. Disabled if a wired headset is // plugged in, otherwise enabled (and indicates the current // speaker state.) mSpeaker.setVisible(true); mSpeaker.setEnabled(inCallControlState.speakerEnabled); mSpeaker.setIndicatorState(inCallControlState.speakerOn); // "Mute": only enabled when the foreground call is ACTIVE. // (It's meaningless while on hold, or while DIALING/ALERTING.) // Also disabled (on CDMA devices) during emergency calls. mMute.setVisible(true); mMute.setEnabled(inCallControlState.canMute); mMute.setIndicatorState(inCallControlState.muteIndicatorOn); // "Hold" mHold.setVisible(inCallControlState.supportsHold); mHold.setIndicatorState(inCallControlState.onHold); mHold.setEnabled(inCallControlState.canHold); // "Answer" and "Ignore" are used only when there's an incoming // ringing call (see above). (And for now they're only used in // CDMA, for the call waiting case.) mAnswer.setVisible(false); mAnswer.setEnabled(false); mIgnore.setVisible(false); mIgnore.setEnabled(false); // "Answer & end" and "Answer & hold" are only useful // when there's an incoming ringing call (see above.) mAnswerAndHold.setVisible(false); mAnswerAndHold.setEnabled(false); mAnswerAndEnd.setVisible(false); mAnswerAndEnd.setEnabled(false); // Done updating the individual items. // The last step is to tell the InCallMenuView to update itself // based on any visibility changes that just happened. mInCallMenuView.updateVisibility(); return true; } private void log(String msg) { Log.d(LOG_TAG, msg); } }