CallCardFragment.java revision 1df52df7a0248814fbd4575103059a8b427f5d9a
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.app.Activity; 20import android.content.Context; 21import android.graphics.Bitmap; 22import android.graphics.drawable.BitmapDrawable; 23import android.graphics.drawable.Drawable; 24import android.os.Bundle; 25import android.text.TextUtils; 26import android.view.Gravity; 27import android.view.LayoutInflater; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.ViewStub; 31import android.widget.ImageView; 32import android.widget.TextView; 33 34import com.android.services.telephony.common.Call; 35 36/** 37 * Fragment for call card. 38 */ 39public class CallCardFragment extends BaseFragment<CallCardPresenter> 40 implements CallCardPresenter.CallCardUi { 41 42 // Primary caller info 43 private TextView mPhoneNumber; 44 private TextView mNumberLabel; 45 private TextView mName; 46 private TextView mCallStateLabel; 47 private ImageView mPhoto; 48 private TextView mElapsedTime; 49 50 // Secondary caller info 51 private ViewStub mSecondaryCallInfo; 52 private TextView mSecondaryCallName; 53 private ImageView mSecondaryPhoto; 54 55 // Cached DisplayMetrics density. 56 private float mDensity; 57 58 @Override 59 CallCardPresenter createPresenter() { 60 return new CallCardPresenter(); 61 } 62 63 @Override 64 public View onCreateView(LayoutInflater inflater, ViewGroup container, 65 Bundle savedInstanceState) { 66 mDensity = getResources().getDisplayMetrics().density; 67 68 return inflater.inflate(R.layout.call_card, container, false); 69 } 70 71 72 @Override 73 public void onViewCreated(View view, Bundle savedInstanceState) { 74 mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber); 75 mName = (TextView) view.findViewById(R.id.name); 76 mNumberLabel = (TextView) view.findViewById(R.id.label); 77 mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info); 78 mPhoto = (ImageView) view.findViewById(R.id.photo); 79 mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel); 80 mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime); 81 82 // This method call will begin the callbacks on CallCardUi. We need to ensure 83 // everything needed for the callbacks is set up before this is called. 84 getPresenter().onUiReady(this); 85 } 86 87 @Override 88 public void onDestroyView() { 89 super.onDestroyView(); 90 getPresenter().onUiUnready(this); 91 } 92 93 @Override 94 public void setVisible(boolean on) { 95 if (on) { 96 getView().setVisibility(View.VISIBLE); 97 } else { 98 getView().setVisibility(View.INVISIBLE); 99 } 100 } 101 102 @Override 103 public void setPrimary(String number, String name, boolean nameIsNumber, String label, 104 Drawable photo, boolean isConference) { 105 106 if (isConference) { 107 name = getView().getResources().getString(R.string.card_title_conf_call); 108 photo = getView().getResources().getDrawable(R.drawable.picture_conference); 109 } 110 111 // Set the number 112 if (TextUtils.isEmpty(number)) { 113 mPhoneNumber.setText(""); 114 mPhoneNumber.setVisibility(View.GONE); 115 } else { 116 mPhoneNumber.setText(number); 117 mPhoneNumber.setVisibility(View.VISIBLE); 118 mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR); 119 } 120 121 // Set direction of the name field 122 123 // set the name field. 124 if (TextUtils.isEmpty(name)) { 125 mName.setText(""); 126 } else { 127 mName.setText(name); 128 129 int nameDirection = View.TEXT_DIRECTION_INHERIT; 130 if (nameIsNumber) { 131 nameDirection = View.TEXT_DIRECTION_LTR; 132 } 133 mName.setTextDirection(nameDirection); 134 } 135 136 // Set the label (Mobile, Work, etc) 137 if (!TextUtils.isEmpty(label)) { 138 mNumberLabel.setText(label); 139 mNumberLabel.setVisibility(View.VISIBLE); 140 } else { 141 mNumberLabel.setVisibility(View.GONE); 142 } 143 144 setDrawableToImageView(mPhoto, photo); 145 } 146 147 @Override 148 public void setSecondary(boolean show, String name, String label, Drawable photo) { 149 150 if (show) { 151 showAndInitializeSecondaryCallInfo(); 152 153 mSecondaryCallName.setText(name); 154 setDrawableToImageView(mSecondaryPhoto, photo); 155 } else { 156 mSecondaryCallInfo.setVisibility(View.GONE); 157 } 158 } 159 160 @Override 161 public void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn) { 162 String callStateLabel = null; 163 164 // States other than disconnected not yet supported 165 callStateLabel = getCallStateLabelFromState(state, cause); 166 167 Logger.v(this, "setCallState ", callStateLabel); 168 Logger.v(this, "DisconnectCause ", cause); 169 Logger.v(this, "bluetooth on ", bluetoothOn); 170 171 if (!TextUtils.isEmpty(callStateLabel)) { 172 mCallStateLabel.setVisibility(View.VISIBLE); 173 mCallStateLabel.setText(callStateLabel); 174 175 if (Call.State.INCOMING == state) { 176 setBluetoothOn(bluetoothOn); 177 } 178 } else { 179 mCallStateLabel.setVisibility(View.GONE); 180 // Gravity is aligned left when receiving an incoming call in landscape. 181 // In that rare case, the gravity needs to be reset to the right. 182 // Also, setText("") is used since there is a delay in making the view GONE, 183 // so the user will otherwise see the text jump to the right side before disappearing. 184 if(mCallStateLabel.getGravity() != Gravity.END) { 185 mCallStateLabel.setText(""); 186 mCallStateLabel.setGravity(Gravity.END); 187 } 188 } 189 } 190 191 @Override 192 public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { 193 if (show) { 194 if (mElapsedTime.getVisibility() != View.VISIBLE) { 195 AnimationUtils.Fade.show(mElapsedTime); 196 } 197 mElapsedTime.setText(callTimeElapsed); 198 } else { 199 // hide() animation has no effect if it is already hidden. 200 AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE); 201 } 202 } 203 204 private void setDrawableToImageView(ImageView view, Drawable photo) { 205 if (photo == null) { 206 photo = view.getResources().getDrawable(R.drawable.picture_unknown); 207 } 208 209 final Drawable current = view.getDrawable(); 210 if (current == null) { 211 view.setImageDrawable(photo); 212 AnimationUtils.Fade.show(view); 213 } else { 214 AnimationUtils.startCrossFade(view, current, photo); 215 mPhoto.setVisibility(View.VISIBLE); 216 } 217 } 218 219 private void setBluetoothOn(boolean onOff) { 220 // Also, display a special icon (alongside the "Incoming call" 221 // label) if there's an incoming call and audio will be routed 222 // to bluetooth when you answer it. 223 final int bluetoothIconId = R.drawable.ic_incoming_call_bluetooth; 224 225 if (onOff) { 226 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0); 227 mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5)); 228 } else { 229 // Clear out any icons 230 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 231 } 232 } 233 234 /** 235 * Gets the call state label based on the state of the call and 236 * cause of disconnect 237 */ 238 private String getCallStateLabelFromState(int state, Call.DisconnectCause cause) { 239 final Context context = getView().getContext(); 240 String callStateLabel = null; // Label to display as part of the call banner 241 242 if (Call.State.IDLE == state) { 243 // "Call state" is meaningless in this state. 244 245 } else if (Call.State.ACTIVE == state) { 246 // We normally don't show a "call state label" at all in 247 // this state (but see below for some special cases). 248 249 } else if (Call.State.ONHOLD == state) { 250 callStateLabel = context.getString(R.string.card_title_on_hold); 251 252 } else if (Call.State.DIALING == state) { 253 callStateLabel = context.getString(R.string.card_title_dialing); 254 255 } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) { 256 callStateLabel = context.getString(R.string.card_title_incoming_call); 257 258 // TODO(klp): Add a disconnecting state 259 //} else if (Call.State.DISCONNECTING) { 260 // While in the DISCONNECTING state we display a "Hanging up" 261 // message in order to make the UI feel more responsive. (In 262 // GSM it's normal to see a delay of a couple of seconds while 263 // negotiating the disconnect with the network, so the "Hanging 264 // up" state at least lets the user know that we're doing 265 // something. This state is currently not used with CDMA.) 266 //callStateLabel = context.getString(R.string.card_title_hanging_up); 267 268 } else if (Call.State.DISCONNECTED == state) { 269 callStateLabel = getCallFailedString(cause); 270 271 } else { 272 Logger.wtf(this, "updateCallStateWidgets: unexpected call: " + state); 273 } 274 275 return callStateLabel; 276 } 277 278 /** 279 * Maps the disconnect cause to a resource string. 280 */ 281 private String getCallFailedString(Call.DisconnectCause cause) { 282 int resID = R.string.card_title_call_ended; 283 284 // TODO: The card *title* should probably be "Call ended" in all 285 // cases, but if the DisconnectCause was an error condition we should 286 // probably also display the specific failure reason somewhere... 287 288 switch (cause) { 289 case BUSY: 290 resID = R.string.callFailed_userBusy; 291 break; 292 293 case CONGESTION: 294 resID = R.string.callFailed_congestion; 295 break; 296 297 case TIMED_OUT: 298 resID = R.string.callFailed_timedOut; 299 break; 300 301 case SERVER_UNREACHABLE: 302 resID = R.string.callFailed_server_unreachable; 303 break; 304 305 case NUMBER_UNREACHABLE: 306 resID = R.string.callFailed_number_unreachable; 307 break; 308 309 case INVALID_CREDENTIALS: 310 resID = R.string.callFailed_invalid_credentials; 311 break; 312 313 case SERVER_ERROR: 314 resID = R.string.callFailed_server_error; 315 break; 316 317 case OUT_OF_NETWORK: 318 resID = R.string.callFailed_out_of_network; 319 break; 320 321 case LOST_SIGNAL: 322 case CDMA_DROP: 323 resID = R.string.callFailed_noSignal; 324 break; 325 326 case LIMIT_EXCEEDED: 327 resID = R.string.callFailed_limitExceeded; 328 break; 329 330 case POWER_OFF: 331 resID = R.string.callFailed_powerOff; 332 break; 333 334 case ICC_ERROR: 335 resID = R.string.callFailed_simError; 336 break; 337 338 case OUT_OF_SERVICE: 339 resID = R.string.callFailed_outOfService; 340 break; 341 342 case INVALID_NUMBER: 343 case UNOBTAINABLE_NUMBER: 344 resID = R.string.callFailed_unobtainable_number; 345 break; 346 347 default: 348 resID = R.string.card_title_call_ended; 349 break; 350 } 351 return this.getView().getContext().getString(resID); 352 } 353 354 private void showAndInitializeSecondaryCallInfo() { 355 mSecondaryCallInfo.setVisibility(View.VISIBLE); 356 357 // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible 358 // until mSecondaryCallInfo is inflated in the call above. 359 if (mSecondaryCallName == null) { 360 mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); 361 } 362 if (mSecondaryPhoto == null) { 363 mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto); 364 } 365 } 366} 367