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