• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
InCallMenu(InCallScreen inCallScreen)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      */
clearInCallScreenReference()82     void clearInCallScreenReference() {
83         mInCallScreen = null;
84         if (mInCallMenuView != null) mInCallMenuView.clearInCallScreenReference();
85     }
86 
getView()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      */
initMenu()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      */
updateItems(CallManager cm)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 
log(String msg)475     private void log(String msg) {
476         Log.d(LOG_TAG, msg);
477     }
478 }
479