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