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