CallButtonFragment.java revision fa6d1c3cc9cc049a062d9308b4f4042df2ecfbab
1/* 2 * Copyright (C) 2013 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 17package com.android.incallui; 18 19import android.content.Context; 20import android.graphics.drawable.LayerDrawable; 21import android.media.AudioManager; 22import android.os.Bundle; 23import android.view.LayoutInflater; 24import android.view.Menu; 25import android.view.MenuItem; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.CompoundButton; 29import android.widget.PopupMenu; 30import android.widget.PopupMenu.OnDismissListener; 31import android.widget.PopupMenu.OnMenuItemClickListener; 32import android.widget.ToggleButton; 33 34import com.android.services.telephony.common.AudioMode; 35 36/** 37 * Fragment for call control buttons 38 */ 39public class CallButtonFragment extends BaseFragment<CallButtonPresenter> 40 implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, 41 OnDismissListener { 42 43 private ToggleButton mMuteButton; 44 private ToggleButton mAudioButton; 45 private ToggleButton mHoldButton; 46 private ToggleButton mShowDialpadButton; 47 48 private PopupMenu mAudioModePopup; 49 private boolean mAudioModePopupVisible; 50 private View mEndCallButton; 51 52 @Override 53 CallButtonPresenter createPresenter() { 54 // TODO: find a cleaner way to include audio mode provider than 55 // having a singleton instance. 56 return new CallButtonPresenter(AudioModeProvider.getInstance()); 57 } 58 59 @Override 60 public void onCreate(Bundle savedInstanceState) { 61 super.onCreate(savedInstanceState); 62 } 63 64 @Override 65 public View onCreateView(LayoutInflater inflater, ViewGroup container, 66 Bundle savedInstanceState) { 67 final View parent = inflater.inflate(R.layout.call_button_fragment, container, false); 68 69 mEndCallButton = parent.findViewById(R.id.endButton); 70 mEndCallButton.setOnClickListener(new View.OnClickListener() { 71 @Override 72 public void onClick(View v) { 73 getPresenter().endCallClicked(); 74 } 75 }); 76 77 mMuteButton = (ToggleButton) parent.findViewById(R.id.muteButton); 78 mMuteButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 79 @Override 80 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 81 getPresenter().muteClicked(isChecked); 82 } 83 }); 84 85 mAudioButton = (ToggleButton) parent.findViewById(R.id.audioButton); 86 mAudioButton.setOnClickListener(new View.OnClickListener() { 87 @Override 88 public void onClick(View view) { 89 onAudioButtonClicked(); 90 } 91 }); 92 93 mHoldButton = (ToggleButton) parent.findViewById(R.id.holdButton); 94 mHoldButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 95 @Override 96 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 97 getPresenter().holdClicked(isChecked); 98 } 99 }); 100 101 mShowDialpadButton = (ToggleButton) parent.findViewById(R.id.dialpadButton); 102 mShowDialpadButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 103 @Override 104 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 105 getPresenter().showDialpadClicked(isChecked); 106 } 107 }); 108 109 return parent; 110 } 111 112 @Override 113 public void onViewCreated(View view, Bundle savedInstanceState) { 114 getPresenter().onUiReady(this); 115 116 // set the buttons 117 updateAudioButtons(getPresenter().getSupportedAudio()); 118 } 119 120 @Override 121 public void onDestroyView() { 122 super.onDestroyView(); 123 getPresenter().onUiUnready(this); 124 } 125 126 @Override 127 public void setVisible(boolean on) { 128 if (on) { 129 getView().setVisibility(View.VISIBLE); 130 } else { 131 getView().setVisibility(View.INVISIBLE); 132 } 133 } 134 135 @Override 136 public void setMute(boolean value) { 137 mMuteButton.setChecked(value); 138 } 139 140 @Override 141 public void setHold(boolean value) { 142 mHoldButton.setChecked(value); 143 } 144 145 @Override 146 public void showHold(boolean show) { 147 mHoldButton.setVisibility(show ? View.VISIBLE : View.GONE); 148 } 149 150 @Override 151 public void setAudio(int mode) { 152 } 153 154 @Override 155 public void setSupportedAudio(int modeMask) { 156 updateAudioButtons(modeMask); 157 refreshAudioModePopup(); 158 } 159 160 @Override 161 public boolean onMenuItemClick(MenuItem item) { 162 Logger.d(this, "- onMenuItemClick: " + item); 163 Logger.d(this, " id: " + item.getItemId()); 164 Logger.d(this, " title: '" + item.getTitle() + "'"); 165 166 int mode = AudioMode.WIRED_OR_EARPIECE; 167 168 switch (item.getItemId()) { 169 case R.id.audio_mode_speaker: 170 mode = AudioMode.SPEAKER; 171 break; 172 case R.id.audio_mode_earpiece: 173 case R.id.audio_mode_wired_headset: 174 // InCallAudioMode.EARPIECE means either the handset earpiece, 175 // or the wired headset (if connected.) 176 mode = AudioMode.WIRED_OR_EARPIECE; 177 break; 178 case R.id.audio_mode_bluetooth: 179 mode = AudioMode.BLUETOOTH; 180 break; 181 default: 182 Logger.e(this, "onMenuItemClick: unexpected View ID " + item.getItemId() 183 + " (MenuItem = '" + item + "')"); 184 break; 185 } 186 187 getPresenter().setAudioMode(mode); 188 189 return true; 190 } 191 192 // PopupMenu.OnDismissListener implementation; see showAudioModePopup(). 193 // This gets called when the PopupMenu gets dismissed for *any* reason, like 194 // the user tapping outside its bounds, or pressing Back, or selecting one 195 // of the menu items. 196 @Override 197 public void onDismiss(PopupMenu menu) { 198 Logger.d(this, "- onDismiss: " + menu); 199 mAudioModePopupVisible = false; 200 } 201 202 /** 203 * Checks for supporting modes. If bluetooth is supported, it uses the audio 204 * pop up menu. Otherwise, it toggles the speakerphone. 205 */ 206 private void onAudioButtonClicked() { 207 Logger.d(this, "onAudioButtonClicked: " + 208 AudioMode.toString(getPresenter().getSupportedAudio())); 209 210 if (isSupported(AudioMode.BLUETOOTH)) { 211 showAudioModePopup(); 212 } else { 213 getPresenter().toggleSpeakerphone(); 214 } 215 } 216 217 /** 218 * Refreshes the "Audio mode" popup if it's visible. This is useful 219 * (for example) when a wired headset is plugged or unplugged, 220 * since we need to switch back and forth between the "earpiece" 221 * and "wired headset" items. 222 * 223 * This is safe to call even if the popup is already dismissed, or even if 224 * you never called showAudioModePopup() in the first place. 225 */ 226 public void refreshAudioModePopup() { 227 if (mAudioModePopup != null && mAudioModePopupVisible) { 228 // Dismiss the previous one 229 mAudioModePopup.dismiss(); // safe even if already dismissed 230 // And bring up a fresh PopupMenu 231 showAudioModePopup(); 232 } 233 } 234 235 /** 236 * Updates the audio button so that the appriopriate visual layers 237 * are visible based on the supported audio formats. 238 */ 239 private void updateAudioButtons(int supportedModes) { 240 final boolean bluetoothSupported = isSupported(AudioMode.BLUETOOTH); 241 final boolean speakerSupported = isSupported(AudioMode.SPEAKER); 242 243 boolean audioButtonEnabled = false; 244 boolean audioButtonChecked = false; 245 boolean showMoreIndicator = false; 246 247 boolean showBluetoothIcon = false; 248 boolean showSpeakerphoneOnIcon = false; 249 boolean showSpeakerphoneOffIcon = false; 250 boolean showHandsetIcon = false; 251 252 boolean showToggleIndicator = false; 253 254 if (bluetoothSupported) { 255 Logger.d(this, "updateAudioButtons - popup menu mode"); 256 257 audioButtonEnabled = true; 258 showMoreIndicator = true; 259 // The audio button is NOT a toggle in this state. (And its 260 // setChecked() state is irrelevant since we completely hide the 261 // btn_compound_background layer anyway.) 262 263 // Update desired layers: 264 if (isAudio(AudioMode.BLUETOOTH)) { 265 showBluetoothIcon = true; 266 } else if (isAudio(AudioMode.SPEAKER)) { 267 showSpeakerphoneOnIcon = true; 268 } else { 269 showHandsetIcon = true; 270 // TODO: if a wired headset is plugged in, that takes precedence 271 // over the handset earpiece. If so, maybe we should show some 272 // sort of "wired headset" icon here instead of the "handset 273 // earpiece" icon. (Still need an asset for that, though.) 274 } 275 } else if (speakerSupported) { 276 Logger.d(this, "updateAudioButtons - speaker toggle mode"); 277 278 audioButtonEnabled = true; 279 280 // The audio button *is* a toggle in this state, and indicated the 281 // current state of the speakerphone. 282 audioButtonChecked = isAudio(AudioMode.SPEAKER); 283 284 // update desired layers: 285 showToggleIndicator = true; 286 287 showSpeakerphoneOnIcon = isAudio(AudioMode.SPEAKER); 288 showSpeakerphoneOffIcon = !showSpeakerphoneOnIcon; 289 } else { 290 Logger.d(this, "updateAudioButtons - disabled..."); 291 292 // The audio button is a toggle in this state, but that's mostly 293 // irrelevant since it's always disabled and unchecked. 294 audioButtonEnabled = false; 295 audioButtonChecked = false; 296 297 // update desired layers: 298 showToggleIndicator = true; 299 showSpeakerphoneOffIcon = true; 300 } 301 302 // Finally, update it all! 303 304 Logger.v(this, "audioButtonEnabled: " + audioButtonEnabled); 305 Logger.v(this, "audioButtonChecked: " + audioButtonChecked); 306 Logger.v(this, "showMoreIndicator: " + showMoreIndicator); 307 Logger.v(this, "showBluetoothIcon: " + showBluetoothIcon); 308 Logger.v(this, "showSpeakerphoneOnIcon: " + showSpeakerphoneOnIcon); 309 Logger.v(this, "showSpeakerphoneOffIcon: " + showSpeakerphoneOffIcon); 310 Logger.v(this, "showHandsetIcon: " + showHandsetIcon); 311 312 // Constants for Drawable.setAlpha() 313 final int HIDDEN = 0; 314 final int VISIBLE = 255; 315 316 mAudioButton.setEnabled(audioButtonEnabled); 317 mAudioButton.setChecked(audioButtonChecked); 318 319 final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground(); 320 Logger.d(this, "'layers' drawable: " + layers); 321 322 layers.findDrawableByLayerId(R.id.compoundBackgroundItem) 323 .setAlpha(showToggleIndicator ? VISIBLE : HIDDEN); 324 325 layers.findDrawableByLayerId(R.id.moreIndicatorItem) 326 .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN); 327 328 layers.findDrawableByLayerId(R.id.bluetoothItem) 329 .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN); 330 331 layers.findDrawableByLayerId(R.id.handsetItem) 332 .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN); 333 334 layers.findDrawableByLayerId(R.id.speakerphoneOnItem) 335 .setAlpha(showSpeakerphoneOnIcon ? VISIBLE : HIDDEN); 336 337 layers.findDrawableByLayerId(R.id.speakerphoneOffItem) 338 .setAlpha(showSpeakerphoneOffIcon ? VISIBLE : HIDDEN); 339 } 340 341 private void showAudioModePopup() { 342 Logger.d(this, "showAudioPopup()..."); 343 344 mAudioModePopup = new PopupMenu(getView().getContext(), mAudioButton /* anchorView */); 345 mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu, 346 mAudioModePopup.getMenu()); 347 mAudioModePopup.setOnMenuItemClickListener(this); 348 mAudioModePopup.setOnDismissListener(this); 349 350 final Menu menu = mAudioModePopup.getMenu(); 351 352 // TODO: Still need to have the "currently active" audio mode come 353 // up pre-selected (or focused?) with a blue highlight. Still 354 // need exact visual design, and possibly framework support for this. 355 // See comments below for the exact logic. 356 357 final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker); 358 speakerItem.setEnabled(isSupported(AudioMode.SPEAKER)); 359 // TODO: Show speakerItem as initially "selected" if 360 // speaker is on. 361 362 // We display *either* "earpiece" or "wired headset", never both, 363 // depending on whether a wired headset is physically plugged in. 364 final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece); 365 final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset); 366 367 final boolean usingHeadset = isSupported(AudioMode.WIRED_HEADSET); 368 earpieceItem.setVisible(!usingHeadset); 369 earpieceItem.setEnabled(!usingHeadset); 370 wiredHeadsetItem.setVisible(usingHeadset); 371 wiredHeadsetItem.setEnabled(usingHeadset); 372 // TODO: Show the above item (either earpieceItem or wiredHeadsetItem) 373 // as initially "selected" if speakerOn and 374 // bluetoothIndicatorOn are both false. 375 376 final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth); 377 bluetoothItem.setEnabled(isSupported(AudioMode.BLUETOOTH)); 378 // TODO: Show bluetoothItem as initially "selected" if 379 // bluetoothIndicatorOn is true. 380 381 mAudioModePopup.show(); 382 383 // Unfortunately we need to manually keep track of the popup menu's 384 // visiblity, since PopupMenu doesn't have an isShowing() method like 385 // Dialogs do. 386 mAudioModePopupVisible = true; 387 } 388 389 private boolean isSupported(int mode) { 390 return (mode == (getPresenter().getSupportedAudio() & mode)); 391 } 392 393 private boolean isAudio(int mode) { 394 return (mode == getPresenter().getAudioMode()); 395 } 396 397 @Override 398 public void displayDialpad(boolean value) { 399 mShowDialpadButton.setChecked(value); 400 if (getActivity() != null && getActivity() instanceof InCallActivity) { 401 ((InCallActivity) getActivity()).displayDialpad(value); 402 } 403 } 404} 405