Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.phone;
     18 
     19 import android.content.Context;
     20 import android.net.sip.SipManager;
     21 import android.util.Log;
     22 import android.view.ContextThemeWrapper;
     23 import com.android.internal.telephony.Call;
     24 import com.android.internal.telephony.Phone;
     25 import com.android.internal.telephony.CallManager;
     26 
     27 /**
     28  * Helper class to manage the options menu for the InCallScreen.
     29  *
     30  * This class is the "Model" (in M-V-C nomenclature) for the in-call menu;
     31  * it knows about all possible menu items, and contains logic to determine
     32  * the current state and enabledness of each item based on the state of
     33  * the Phone.
     34  *
     35  * The corresponding View classes are InCallMenuView, which is used purely
     36  * to lay out and draw the menu, and InCallMenuItemView, which is the View
     37  * for a single item.
     38  */
     39 class InCallMenu {
     40     private static final String LOG_TAG = "PHONE/InCallMenu";
     41     private static final boolean DBG = false;
     42 
     43     /**
     44      * Reference to the InCallScreen activity that owns us.  This will be
     45      * null if we haven't been initialized yet *or* after the InCallScreen
     46      * activity has been destroyed.
     47      */
     48     private InCallScreen mInCallScreen;
     49 
     50     /**
     51      * Our corresponding View class.
     52      */
     53     private InCallMenuView mInCallMenuView;
     54 
     55     /**
     56      * All possible menu items (see initMenu().)
     57      */
     58     InCallMenuItemView mManageConference;
     59     InCallMenuItemView mShowDialpad;
     60     InCallMenuItemView mEndCall;
     61     InCallMenuItemView mAddCall;
     62     InCallMenuItemView mSwapCalls;
     63     InCallMenuItemView mMergeCalls;
     64     InCallMenuItemView mBluetooth;
     65     InCallMenuItemView mSpeaker;
     66     InCallMenuItemView mMute;
     67     InCallMenuItemView mHold;
     68     InCallMenuItemView mAnswerAndHold;
     69     InCallMenuItemView mAnswerAndEnd;
     70     InCallMenuItemView mAnswer;
     71     InCallMenuItemView mIgnore;
     72 
     73     InCallMenu(InCallScreen inCallScreen) {
     74         if (DBG) log("InCallMenu constructor...");
     75         mInCallScreen = inCallScreen;
     76     }
     77 
     78     /**
     79      * Null out our reference to the InCallScreen activity.
     80      * This indicates that the InCallScreen activity has been destroyed.
     81      */
     82     void clearInCallScreenReference() {
     83         mInCallScreen = null;
     84         if (mInCallMenuView != null) mInCallMenuView.clearInCallScreenReference();
     85     }
     86 
     87     /* package */ InCallMenuView getView() {
     88         return mInCallMenuView;
     89     }
     90 
     91     /**
     92      * Initializes the in-call menu by creating a new InCallMenuView,
     93      * creating all possible menu items, and loading them into the
     94      * InCallMenuView.
     95      *
     96      * The only initialization of the individual items we do here is
     97      * one-time stuff, like setting the ID and click listener, or calling
     98      * setIndicatorVisible() for buttons that have a green LED, or calling
     99      * setText() for buttons whose text never changes.  The actual
    100      * *current* state and enabledness of each item is set in
    101      * updateItems().
    102      */
    103     /* package */ void initMenu() {
    104         if (DBG) log("initMenu()...");
    105 
    106         // Explicitly use the "icon menu" theme for the Views we create.
    107         Context wrappedContext = new ContextThemeWrapper(
    108                 mInCallScreen,
    109                 com.android.internal.R.style.Theme_IconMenu);
    110 
    111         mInCallMenuView = new InCallMenuView(wrappedContext, mInCallScreen);
    112 
    113         //
    114         // Create all possible InCallMenuView objects.
    115         //
    116 
    117         mManageConference = new InCallMenuItemView(wrappedContext);
    118         mManageConference.setId(R.id.menuManageConference);
    119         mManageConference.setOnClickListener(mInCallScreen);
    120         mManageConference.setText(R.string.menu_manageConference);
    121         mManageConference.setIconResource(com.android.internal.R.drawable.ic_menu_allfriends);
    122 
    123         mShowDialpad = new InCallMenuItemView(wrappedContext);
    124         mShowDialpad.setId(R.id.menuShowDialpad);
    125         mShowDialpad.setOnClickListener(mInCallScreen);
    126         mShowDialpad.setText(R.string.menu_showDialpad); // or "Hide dialpad" if it's open
    127         mShowDialpad.setIconResource(R.drawable.ic_menu_dial_pad);
    128 
    129         mEndCall = new InCallMenuItemView(wrappedContext);
    130         mEndCall.setId(R.id.menuEndCall);
    131         mEndCall.setOnClickListener(mInCallScreen);
    132         mEndCall.setText(R.string.menu_endCall);
    133         mEndCall.setIconResource(R.drawable.ic_menu_end_call);
    134 
    135         mAddCall = new InCallMenuItemView(wrappedContext);
    136         mAddCall.setId(R.id.menuAddCall);
    137         mAddCall.setOnClickListener(mInCallScreen);
    138         mAddCall.setText(R.string.menu_addCall);
    139         mAddCall.setIconResource(android.R.drawable.ic_menu_add);
    140 
    141         mSwapCalls = new InCallMenuItemView(wrappedContext);
    142         mSwapCalls.setId(R.id.menuSwapCalls);
    143         mSwapCalls.setOnClickListener(mInCallScreen);
    144         mSwapCalls.setText(R.string.menu_swapCalls);
    145         mSwapCalls.setIconResource(R.drawable.ic_menu_swap_calls);
    146 
    147         mMergeCalls = new InCallMenuItemView(wrappedContext);
    148         mMergeCalls.setId(R.id.menuMergeCalls);
    149         mMergeCalls.setOnClickListener(mInCallScreen);
    150         mMergeCalls.setText(R.string.menu_mergeCalls);
    151         mMergeCalls.setIconResource(R.drawable.ic_menu_merge_calls);
    152 
    153         // TODO: Icons for menu items we don't have yet:
    154         //   R.drawable.ic_menu_answer_call
    155         //   R.drawable.ic_menu_silence_ringer
    156 
    157         mBluetooth = new InCallMenuItemView(wrappedContext);
    158         mBluetooth.setId(R.id.menuBluetooth);
    159         mBluetooth.setOnClickListener(mInCallScreen);
    160         mBluetooth.setText(R.string.menu_bluetooth);
    161         mBluetooth.setIndicatorVisible(true);
    162 
    163         mSpeaker = new InCallMenuItemView(wrappedContext);
    164         mSpeaker.setId(R.id.menuSpeaker);
    165         mSpeaker.setOnClickListener(mInCallScreen);
    166         mSpeaker.setText(R.string.menu_speaker);
    167         mSpeaker.setIndicatorVisible(true);
    168 
    169         mMute = new InCallMenuItemView(wrappedContext);
    170         mMute.setId(R.id.menuMute);
    171         mMute.setOnClickListener(mInCallScreen);
    172         mMute.setText(R.string.menu_mute);
    173         mMute.setIndicatorVisible(true);
    174 
    175         mHold = new InCallMenuItemView(wrappedContext);
    176         mHold.setId(R.id.menuHold);
    177         mHold.setOnClickListener(mInCallScreen);
    178         mHold.setText(R.string.menu_hold);
    179         mHold.setIndicatorVisible(true);
    180 
    181         mAnswerAndHold = new InCallMenuItemView(wrappedContext);
    182         mAnswerAndHold.setId(R.id.menuAnswerAndHold);
    183         mAnswerAndHold.setOnClickListener(mInCallScreen);
    184         mAnswerAndHold.setText(R.string.menu_answerAndHold);
    185 
    186         mAnswerAndEnd = new InCallMenuItemView(wrappedContext);
    187         mAnswerAndEnd.setId(R.id.menuAnswerAndEnd);
    188         mAnswerAndEnd.setOnClickListener(mInCallScreen);
    189         mAnswerAndEnd.setText(R.string.menu_answerAndEnd);
    190 
    191         mAnswer = new InCallMenuItemView(wrappedContext);
    192         mAnswer.setId(R.id.menuAnswer);
    193         mAnswer.setOnClickListener(mInCallScreen);
    194         mAnswer.setText(R.string.menu_answer);
    195 
    196         mIgnore = new InCallMenuItemView(wrappedContext);
    197         mIgnore.setId(R.id.menuIgnore);
    198         mIgnore.setOnClickListener(mInCallScreen);
    199         mIgnore.setText(R.string.menu_ignore);
    200 
    201         //
    202         // Load all the items into the correct "slots" in the InCallMenuView.
    203         //
    204         // Row 0 is the topmost row onscreen, item 0 is the leftmost item in a row.
    205         //
    206         // Individual items may be disabled or hidden, but never move between
    207         // rows or change their order within a row.
    208         //
    209         // TODO: these items and their layout ought be specifiable
    210         // entirely in XML (just like we currently do with res/menu/*.xml
    211         // files.)
    212         //
    213 
    214         // Row 0:
    215         // This usually has "Show/Hide dialpad", but that gets replaced by
    216         // "Manage conference" if a conference call is active.
    217         PhoneApp app = PhoneApp.getInstance();
    218         // As managing conference is valid for SIP, we always include it
    219         // when SIP VOIP feature is present.
    220         int phoneType = app.phone.getPhoneType();
    221         if ((phoneType == Phone.PHONE_TYPE_GSM)
    222                 || SipManager.isVoipSupported(app)) {
    223             mInCallMenuView.addItemView(mManageConference, 0);
    224         }
    225         mInCallMenuView.addItemView(mShowDialpad, 0);
    226 
    227         // Row 1:
    228         mInCallMenuView.addItemView(mSwapCalls, 1);
    229         mInCallMenuView.addItemView(mMergeCalls, 1);
    230         mInCallMenuView.addItemView(mAddCall, 1);
    231         mInCallMenuView.addItemView(mEndCall, 1);
    232 
    233         // Row 2:
    234         // In this row we see *either*  bluetooth/speaker/mute/hold
    235         // *or* answerAndHold/answerAndEnd, but never all 6 together.
    236         // For CDMA only Answer or Ignore option is valid for a Call Waiting scenario
    237         if (phoneType == Phone.PHONE_TYPE_CDMA) {
    238             mInCallMenuView.addItemView(mAnswer, 2);
    239             mInCallMenuView.addItemView(mIgnore, 2);
    240         }
    241         if ((phoneType == Phone.PHONE_TYPE_GSM)
    242                 || SipManager.isVoipSupported(app)) {
    243             mInCallMenuView.addItemView(mHold, 2);
    244             mInCallMenuView.addItemView(mAnswerAndHold, 2);
    245             mInCallMenuView.addItemView(mAnswerAndEnd, 2);
    246         }
    247         mInCallMenuView.addItemView(mMute, 2);
    248         mInCallMenuView.addItemView(mSpeaker, 2);
    249         mInCallMenuView.addItemView(mBluetooth, 2);
    250 
    251         mInCallMenuView.dumpState();
    252     }
    253 
    254     /**
    255      * Updates the enabledness and visibility of all items in the
    256      * InCallMenuView based on the current state of the Phone.
    257      *
    258      * This is called every time we need to display the menu, right before
    259      * it becomes visible.
    260      *
    261      * @return true if we successfully updated the items and it's OK
    262      *         to go ahead and show the menu, or false if
    263      *         we shouldn't show the menu at all.
    264      */
    265     /* package */ boolean updateItems(CallManager cm) {
    266         if (DBG) log("updateItems()...");
    267         // if (DBG) PhoneUtils.dumpCallState();
    268 
    269         // If the phone is totally idle (like in the "call ended" state)
    270         // there's no menu at all.
    271         if (cm.getState() == Phone.State.IDLE) {
    272             if (DBG) log("- Phone is idle!  Don't show the menu...");
    273             return false;
    274         }
    275 
    276         final boolean hasRingingCall = cm.hasActiveRingingCall();
    277         final boolean hasActiveCall = cm.hasActiveFgCall();
    278         final Call.State fgCallState = cm.getActiveFgCallState();
    279         final boolean hasHoldingCall = cm.hasActiveBgCall();
    280 
    281         // For OTA call, only show dialpad, endcall, speaker, and mute menu items
    282         if (hasActiveCall && TelephonyCapabilities.supportsOtasp(cm.getFgPhone()) &&
    283                 (PhoneApp.getInstance().isOtaCallInActiveState())) {
    284             mAnswerAndHold.setVisible(false);
    285             mAnswerAndHold.setEnabled(false);
    286             mAnswerAndEnd.setVisible(false);
    287             mAnswerAndEnd.setEnabled(false);
    288 
    289             mManageConference.setVisible(false);
    290             mAddCall.setEnabled(false);
    291             mSwapCalls.setEnabled(false);
    292             mMergeCalls.setEnabled(false);
    293             mHold.setEnabled(false);
    294             mBluetooth.setEnabled(false);
    295             mMute.setEnabled(false);
    296             mAnswer.setVisible(false);
    297             mIgnore.setVisible(false);
    298 
    299             boolean inConferenceCall =
    300                     PhoneUtils.isConferenceCall(cm.getActiveFgCall());
    301             boolean showShowDialpad = !inConferenceCall;
    302             boolean enableShowDialpad = showShowDialpad && mInCallScreen.okToShowDialpad();
    303             mShowDialpad.setVisible(showShowDialpad);
    304             mShowDialpad.setEnabled(enableShowDialpad);
    305             boolean isDtmfDialerOpened = mInCallScreen.isDialerOpened();
    306             mShowDialpad.setText(isDtmfDialerOpened
    307                                  ? R.string.menu_hideDialpad
    308                                  : R.string.menu_showDialpad);
    309 
    310             mEndCall.setVisible(true);
    311             mEndCall.setEnabled(true);
    312 
    313             mSpeaker.setVisible(true);
    314             mSpeaker.setEnabled(true);
    315             boolean speakerOn = PhoneUtils.isSpeakerOn(mInCallScreen.getApplicationContext());
    316             mSpeaker.setIndicatorState(speakerOn);
    317 
    318             mInCallMenuView.updateVisibility();
    319             return true;
    320         }
    321 
    322         // Special cases when an incoming call is ringing.
    323         if (hasRingingCall) {
    324             // In the "call waiting" state, show ONLY the "answer & end"
    325             // and "answer & hold" buttons, and nothing else.
    326             // TODO: be sure to test this for "only one line in use and it's
    327             // active" AND for "only one line in use and it's on hold".
    328             if (hasActiveCall && !hasHoldingCall) {
    329                 int phoneType = cm.getRingingPhone().getPhoneType();
    330                 // For CDMA only make "Answer" and "Ignore" visible
    331                 if (phoneType == Phone.PHONE_TYPE_CDMA) {
    332                     mAnswer.setVisible(true);
    333                     mAnswer.setEnabled(true);
    334                     mIgnore.setVisible(true);
    335                     mIgnore.setEnabled(true);
    336 
    337                     // Explicitly remove GSM menu items
    338                     mAnswerAndHold.setVisible(false);
    339                     mAnswerAndEnd.setVisible(false);
    340                 } else if ((phoneType == Phone.PHONE_TYPE_GSM)
    341                         || (phoneType == Phone.PHONE_TYPE_SIP)) {
    342                     mAnswerAndHold.setVisible(true);
    343                     mAnswerAndHold.setEnabled(true);
    344                     mAnswerAndEnd.setVisible(true);
    345                     mAnswerAndEnd.setEnabled(true);
    346 
    347                     // Explicitly remove CDMA menu items
    348                     mAnswer.setVisible(false);
    349                     mIgnore.setVisible(false);
    350 
    351                     mManageConference.setVisible(false);
    352                 } else {
    353                     throw new IllegalStateException("Unexpected phone type: " + phoneType);
    354                 }
    355 
    356                 mShowDialpad.setVisible(false);
    357                 mEndCall.setVisible(false);
    358                 mAddCall.setVisible(false);
    359                 mSwapCalls.setVisible(false);
    360                 mMergeCalls.setVisible(false);
    361                 mBluetooth.setVisible(false);
    362                 mSpeaker.setVisible(false);
    363                 mMute.setVisible(false);
    364                 mHold.setVisible(false);
    365 
    366                 // Done updating the individual items.
    367                 // The last step is to tell the InCallMenuView to update itself
    368                 // based on any visibility changes that just happened.
    369                 mInCallMenuView.updateVisibility();
    370 
    371                 return true;
    372             } else {
    373                 // If there's an incoming ringing call but there aren't
    374                 // any "special actions" to take, don't show a menu at all.
    375                 return false;
    376             }
    377         }
    378 
    379         // TODO: double-check if any items here need to be disabled based on:
    380         //   boolean keyguardRestricted = mInCallScreen.isPhoneStateRestricted();
    381 
    382         // The InCallControlState object tells us the enabledness and/or
    383         // state of the various menu items:
    384         InCallControlState inCallControlState = mInCallScreen.getUpdatedInCallControlState();
    385 
    386         // Manage conference: visible only if the foreground call is a
    387         // conference call.  Enabled unless the "Manage conference" UI is
    388         // already up.
    389         mManageConference.setVisible(inCallControlState.manageConferenceVisible);
    390         mManageConference.setEnabled(inCallControlState.manageConferenceEnabled);
    391 
    392         // "Show/Hide dialpad":
    393         // - Visible: only in portrait mode, but NOT when "Manage
    394         //   conference" is available (since that's shown instead.)
    395         // - Enabled: Only when it's OK to use the dialpad in the first
    396         //   place (i.e. in the same states where the SlidingDrawer handle
    397         //   is visible.)
    398         // - Text label: "Show" or "Hide", depending on the current state
    399         //   of the sliding drawer.
    400         // (Note this logic is totally specific to the in-call menu, so
    401         // this state doesn't come from the inCallControlState object.)
    402         boolean showShowDialpad = !inCallControlState.manageConferenceVisible;
    403         boolean enableShowDialpad = showShowDialpad && mInCallScreen.okToShowDialpad();
    404         mShowDialpad.setVisible(showShowDialpad);
    405         mShowDialpad.setEnabled(enableShowDialpad);
    406         mShowDialpad.setText(inCallControlState.dialpadVisible
    407                              ? R.string.menu_hideDialpad
    408                              : R.string.menu_showDialpad);
    409 
    410         // "End call": this button has no state and is always visible.
    411         // It's also always enabled.  (Actually it *would* need to be
    412         // disabled if the phone was totally idle, but the entire in-call
    413         // menu is already disabled in that case (see above.))
    414         mEndCall.setVisible(true);
    415         mEndCall.setEnabled(true);
    416 
    417         // "Add call"
    418         mAddCall.setVisible(true);
    419         mAddCall.setEnabled(inCallControlState.canAddCall);
    420 
    421         // Swap / merge calls
    422         mSwapCalls.setVisible(true);
    423         mSwapCalls.setEnabled(inCallControlState.canSwap);
    424         mMergeCalls.setVisible(true);
    425         mMergeCalls.setEnabled(inCallControlState.canMerge);
    426 
    427         // "Bluetooth": always visible, only enabled if BT is available.
    428         mBluetooth.setVisible(true);
    429         mBluetooth.setEnabled(inCallControlState.bluetoothEnabled);
    430         mBluetooth.setIndicatorState(inCallControlState.bluetoothIndicatorOn);
    431 
    432         // "Speaker": always visible.  Disabled if a wired headset is
    433         // plugged in, otherwise enabled (and indicates the current
    434         // speaker state.)
    435         mSpeaker.setVisible(true);
    436         mSpeaker.setEnabled(inCallControlState.speakerEnabled);
    437         mSpeaker.setIndicatorState(inCallControlState.speakerOn);
    438 
    439         // "Mute": only enabled when the foreground call is ACTIVE.
    440         // (It's meaningless while on hold, or while DIALING/ALERTING.)
    441         // Also disabled (on CDMA devices) during emergency calls.
    442         mMute.setVisible(true);
    443         mMute.setEnabled(inCallControlState.canMute);
    444         mMute.setIndicatorState(inCallControlState.muteIndicatorOn);
    445 
    446         // "Hold"
    447         mHold.setVisible(inCallControlState.supportsHold);
    448         mHold.setIndicatorState(inCallControlState.onHold);
    449         mHold.setEnabled(inCallControlState.canHold);
    450 
    451         // "Answer" and "Ignore" are used only when there's an incoming
    452         // ringing call (see above).  (And for now they're only used in
    453         // CDMA, for the call waiting case.)
    454         mAnswer.setVisible(false);
    455         mAnswer.setEnabled(false);
    456         mIgnore.setVisible(false);
    457         mIgnore.setEnabled(false);
    458 
    459         // "Answer & end" and "Answer & hold" are only useful
    460         // when there's an incoming ringing call (see above.)
    461         mAnswerAndHold.setVisible(false);
    462         mAnswerAndHold.setEnabled(false);
    463         mAnswerAndEnd.setVisible(false);
    464         mAnswerAndEnd.setEnabled(false);
    465 
    466         // Done updating the individual items.
    467         // The last step is to tell the InCallMenuView to update itself
    468         // based on any visibility changes that just happened.
    469         mInCallMenuView.updateVisibility();
    470 
    471         return true;
    472     }
    473 
    474 
    475     private void log(String msg) {
    476         Log.d(LOG_TAG, msg);
    477     }
    478 }
    479