Home | History | Annotate | Download | only in incallui
      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 
     17 package com.android.incallui;
     18 
     19 import android.animation.LayoutTransition;
     20 import android.content.Context;
     21 import android.graphics.Bitmap;
     22 import android.graphics.drawable.BitmapDrawable;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Bundle;
     25 import android.text.TextUtils;
     26 import android.view.Gravity;
     27 import android.view.LayoutInflater;
     28 import android.view.View;
     29 import android.view.View.OnClickListener;
     30 import android.view.ViewGroup;
     31 import android.view.ViewStub;
     32 import android.view.accessibility.AccessibilityEvent;
     33 import android.widget.ImageView;
     34 import android.widget.TextView;
     35 
     36 import com.android.services.telephony.common.Call;
     37 
     38 import java.util.List;
     39 
     40 /**
     41  * Fragment for call card.
     42  */
     43 public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
     44         implements CallCardPresenter.CallCardUi {
     45 
     46     // Primary caller info
     47     private TextView mPhoneNumber;
     48     private TextView mNumberLabel;
     49     private TextView mPrimaryName;
     50     private TextView mCallStateLabel;
     51     private TextView mCallTypeLabel;
     52     private ImageView mPhoto;
     53     private TextView mElapsedTime;
     54     private View mProviderInfo;
     55     private TextView mProviderLabel;
     56     private TextView mProviderNumber;
     57     private ViewGroup mSupplementaryInfoContainer;
     58 
     59     // Secondary caller info
     60     private ViewStub mSecondaryCallInfo;
     61     private TextView mSecondaryCallName;
     62     private ImageView mSecondaryPhoto;
     63     private View mSecondaryPhotoOverlay;
     64 
     65     // Cached DisplayMetrics density.
     66     private float mDensity;
     67 
     68     @Override
     69     CallCardPresenter.CallCardUi getUi() {
     70         return this;
     71     }
     72 
     73     @Override
     74     CallCardPresenter createPresenter() {
     75         return new CallCardPresenter();
     76     }
     77 
     78     @Override
     79     public void onCreate(Bundle savedInstanceState) {
     80         super.onCreate(savedInstanceState);
     81     }
     82 
     83 
     84     @Override
     85     public void onActivityCreated(Bundle savedInstanceState) {
     86         super.onActivityCreated(savedInstanceState);
     87 
     88         final CallList calls = CallList.getInstance();
     89         final Call call = calls.getFirstCall();
     90         getPresenter().init(getActivity(), call);
     91     }
     92 
     93     @Override
     94     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     95             Bundle savedInstanceState) {
     96         super.onCreateView(inflater, container, savedInstanceState);
     97 
     98         mDensity = getResources().getDisplayMetrics().density;
     99 
    100         return inflater.inflate(R.layout.call_card, container, false);
    101     }
    102 
    103     @Override
    104     public void onViewCreated(View view, Bundle savedInstanceState) {
    105         super.onViewCreated(view, savedInstanceState);
    106 
    107         mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
    108         mPrimaryName = (TextView) view.findViewById(R.id.name);
    109         mNumberLabel = (TextView) view.findViewById(R.id.label);
    110         mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info);
    111         mPhoto = (ImageView) view.findViewById(R.id.photo);
    112         mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
    113         mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
    114         mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
    115         mProviderInfo = view.findViewById(R.id.providerInfo);
    116         mProviderLabel = (TextView) view.findViewById(R.id.providerLabel);
    117         mProviderNumber = (TextView) view.findViewById(R.id.providerAddress);
    118         mSupplementaryInfoContainer =
    119             (ViewGroup) view.findViewById(R.id.supplementary_info_container);
    120     }
    121 
    122     @Override
    123     public void setVisible(boolean on) {
    124         if (on) {
    125             getView().setVisibility(View.VISIBLE);
    126         } else {
    127             getView().setVisibility(View.INVISIBLE);
    128         }
    129     }
    130 
    131     @Override
    132     public void setPrimaryName(String name, boolean nameIsNumber) {
    133         if (TextUtils.isEmpty(name)) {
    134             mPrimaryName.setText("");
    135         } else {
    136             mPrimaryName.setText(name);
    137 
    138             // Set direction of the name field
    139             int nameDirection = View.TEXT_DIRECTION_INHERIT;
    140             if (nameIsNumber) {
    141                 nameDirection = View.TEXT_DIRECTION_LTR;
    142             }
    143             mPrimaryName.setTextDirection(nameDirection);
    144         }
    145     }
    146 
    147     @Override
    148     public void setPrimaryImage(Drawable image) {
    149         if (image != null) {
    150             setDrawableToImageView(mPhoto, image);
    151         }
    152     }
    153 
    154     @Override
    155     public void setPrimaryPhoneNumber(String number) {
    156         // Set the number
    157         if (TextUtils.isEmpty(number)) {
    158             mPhoneNumber.setText("");
    159             mPhoneNumber.setVisibility(View.GONE);
    160         } else {
    161             mPhoneNumber.setText(number);
    162             mPhoneNumber.setVisibility(View.VISIBLE);
    163             mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
    164         }
    165     }
    166 
    167     @Override
    168     public void setPrimaryLabel(String label) {
    169         if (!TextUtils.isEmpty(label)) {
    170             mNumberLabel.setText(label);
    171             mNumberLabel.setVisibility(View.VISIBLE);
    172         } else {
    173             mNumberLabel.setVisibility(View.GONE);
    174         }
    175 
    176     }
    177 
    178     @Override
    179     public void setPrimary(String number, String name, boolean nameIsNumber, String label,
    180             Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall) {
    181         Log.d(this, "Setting primary call");
    182 
    183         if (isConference) {
    184             name = getConferenceString(isGeneric);
    185             photo = getConferencePhoto(isGeneric);
    186             nameIsNumber = false;
    187         }
    188 
    189         setPrimaryPhoneNumber(number);
    190 
    191         // set the name field.
    192         setPrimaryName(name, nameIsNumber);
    193 
    194         // Set the label (Mobile, Work, etc)
    195         setPrimaryLabel(label);
    196 
    197         showInternetCallLabel(isSipCall);
    198 
    199         setDrawableToImageView(mPhoto, photo);
    200     }
    201 
    202     @Override
    203     public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
    204             Drawable photo, boolean isConference, boolean isGeneric) {
    205 
    206         if (show) {
    207             if (isConference) {
    208                 name = getConferenceString(isGeneric);
    209                 photo = getConferencePhoto(isGeneric);
    210                 nameIsNumber = false;
    211             }
    212 
    213             showAndInitializeSecondaryCallInfo();
    214             mSecondaryCallName.setText(name);
    215 
    216             int nameDirection = View.TEXT_DIRECTION_INHERIT;
    217             if (nameIsNumber) {
    218                 nameDirection = View.TEXT_DIRECTION_LTR;
    219             }
    220             mSecondaryCallName.setTextDirection(nameDirection);
    221 
    222             setDrawableToImageView(mSecondaryPhoto, photo);
    223         } else {
    224             mSecondaryCallInfo.setVisibility(View.GONE);
    225         }
    226     }
    227 
    228     @Override
    229     public void setSecondaryImage(Drawable image) {
    230         if (image != null) {
    231             setDrawableToImageView(mSecondaryPhoto, image);
    232         }
    233     }
    234 
    235     @Override
    236     public void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn,
    237             String gatewayLabel, String gatewayNumber) {
    238         String callStateLabel = null;
    239 
    240         // States other than disconnected not yet supported
    241         callStateLabel = getCallStateLabelFromState(state, cause);
    242 
    243         Log.v(this, "setCallState " + callStateLabel);
    244         Log.v(this, "DisconnectCause " + cause);
    245         Log.v(this, "bluetooth on " + bluetoothOn);
    246         Log.v(this, "gateway " + gatewayLabel + gatewayNumber);
    247 
    248         // There are cases where we totally skip the animation, in which case remove the transition
    249         // animation here and restore it afterwards.
    250         final boolean skipAnimation = (Call.State.isDialing(state)
    251                 || state == Call.State.DISCONNECTED || state == Call.State.DISCONNECTING);
    252         LayoutTransition transition = null;
    253         if (skipAnimation) {
    254             transition = mSupplementaryInfoContainer.getLayoutTransition();
    255             mSupplementaryInfoContainer.setLayoutTransition(null);
    256         }
    257 
    258         // Update the call state label.
    259         if (!TextUtils.isEmpty(callStateLabel)) {
    260             mCallStateLabel.setVisibility(View.VISIBLE);
    261             mCallStateLabel.setText(callStateLabel);
    262 
    263             if (Call.State.INCOMING == state) {
    264                 setBluetoothOn(bluetoothOn);
    265             }
    266         } else {
    267             mCallStateLabel.setVisibility(View.GONE);
    268             // Gravity is aligned left when receiving an incoming call in landscape.
    269             // In that rare case, the gravity needs to be reset to the right.
    270             // Also, setText("") is used since there is a delay in making the view GONE,
    271             // so the user will otherwise see the text jump to the right side before disappearing.
    272             if(mCallStateLabel.getGravity() != Gravity.END) {
    273                 mCallStateLabel.setText("");
    274                 mCallStateLabel.setGravity(Gravity.END);
    275             }
    276         }
    277 
    278         // Provider info: (e.g. "Calling via <gatewayLabel>")
    279         if (!TextUtils.isEmpty(gatewayLabel) && !TextUtils.isEmpty(gatewayNumber)) {
    280             mProviderLabel.setText(gatewayLabel);
    281             mProviderNumber.setText(gatewayNumber);
    282             mProviderInfo.setVisibility(View.VISIBLE);
    283         } else {
    284             mProviderInfo.setVisibility(View.GONE);
    285         }
    286 
    287         // Restore the animation.
    288         if (skipAnimation) {
    289             mSupplementaryInfoContainer.setLayoutTransition(transition);
    290         }
    291     }
    292 
    293     private void showInternetCallLabel(boolean show) {
    294         if (show) {
    295             final String label = getView().getContext().getString(
    296                     R.string.incall_call_type_label_sip);
    297             mCallTypeLabel.setVisibility(View.VISIBLE);
    298             mCallTypeLabel.setText(label);
    299         } else {
    300             mCallTypeLabel.setVisibility(View.GONE);
    301         }
    302     }
    303 
    304     @Override
    305     public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) {
    306         if (show) {
    307             if (mElapsedTime.getVisibility() != View.VISIBLE) {
    308                 AnimationUtils.Fade.show(mElapsedTime);
    309             }
    310             mElapsedTime.setText(callTimeElapsed);
    311         } else {
    312             // hide() animation has no effect if it is already hidden.
    313             AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE);
    314         }
    315     }
    316 
    317     private void setDrawableToImageView(ImageView view, Drawable photo) {
    318         if (photo == null) {
    319             photo = view.getResources().getDrawable(R.drawable.picture_unknown);
    320         }
    321 
    322         final Drawable current = view.getDrawable();
    323         if (current == null) {
    324             view.setImageDrawable(photo);
    325             AnimationUtils.Fade.show(view);
    326         } else {
    327             AnimationUtils.startCrossFade(view, current, photo);
    328             view.setVisibility(View.VISIBLE);
    329         }
    330     }
    331 
    332     private String getConferenceString(boolean isGeneric) {
    333         Log.v(this, "isGenericString: " + isGeneric);
    334         final int resId = isGeneric ? R.string.card_title_in_call : R.string.card_title_conf_call;
    335         return getView().getResources().getString(resId);
    336     }
    337 
    338     private Drawable getConferencePhoto(boolean isGeneric) {
    339         Log.v(this, "isGenericPhoto: " + isGeneric);
    340         final int resId = isGeneric ? R.drawable.picture_dialing : R.drawable.picture_conference;
    341         return getView().getResources().getDrawable(resId);
    342     }
    343 
    344     private void setBluetoothOn(boolean onOff) {
    345         // Also, display a special icon (alongside the "Incoming call"
    346         // label) if there's an incoming call and audio will be routed
    347         // to bluetooth when you answer it.
    348         final int bluetoothIconId = R.drawable.ic_in_call_bt_dk;
    349 
    350         if (onOff) {
    351             mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
    352             mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5));
    353         } else {
    354             // Clear out any icons
    355             mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
    356         }
    357     }
    358 
    359     /**
    360      * Gets the call state label based on the state of the call and
    361      * cause of disconnect
    362      */
    363     private String getCallStateLabelFromState(int state, Call.DisconnectCause cause) {
    364         final Context context = getView().getContext();
    365         String callStateLabel = null;  // Label to display as part of the call banner
    366 
    367         if (Call.State.IDLE == state) {
    368             // "Call state" is meaningless in this state.
    369 
    370         } else if (Call.State.ACTIVE == state) {
    371             // We normally don't show a "call state label" at all in
    372             // this state (but see below for some special cases).
    373 
    374         } else if (Call.State.ONHOLD == state) {
    375             callStateLabel = context.getString(R.string.card_title_on_hold);
    376         } else if (Call.State.DIALING == state) {
    377             callStateLabel = context.getString(R.string.card_title_dialing);
    378         } else if (Call.State.REDIALING == state) {
    379             callStateLabel = context.getString(R.string.card_title_redialing);
    380         } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) {
    381             callStateLabel = context.getString(R.string.card_title_incoming_call);
    382 
    383         } else if (Call.State.DISCONNECTING == state) {
    384             // While in the DISCONNECTING state we display a "Hanging up"
    385             // message in order to make the UI feel more responsive.  (In
    386             // GSM it's normal to see a delay of a couple of seconds while
    387             // negotiating the disconnect with the network, so the "Hanging
    388             // up" state at least lets the user know that we're doing
    389             // something.  This state is currently not used with CDMA.)
    390             callStateLabel = context.getString(R.string.card_title_hanging_up);
    391 
    392         } else if (Call.State.DISCONNECTED == state) {
    393             callStateLabel = getCallFailedString(cause);
    394 
    395         } else {
    396             Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
    397         }
    398 
    399         return callStateLabel;
    400     }
    401 
    402     /**
    403      * Maps the disconnect cause to a resource string.
    404      */
    405     private String getCallFailedString(Call.DisconnectCause cause) {
    406         int resID = R.string.card_title_call_ended;
    407 
    408         // TODO: The card *title* should probably be "Call ended" in all
    409         // cases, but if the DisconnectCause was an error condition we should
    410         // probably also display the specific failure reason somewhere...
    411 
    412         switch (cause) {
    413             case BUSY:
    414                 resID = R.string.callFailed_userBusy;
    415                 break;
    416 
    417             case CONGESTION:
    418                 resID = R.string.callFailed_congestion;
    419                 break;
    420 
    421             case TIMED_OUT:
    422                 resID = R.string.callFailed_timedOut;
    423                 break;
    424 
    425             case SERVER_UNREACHABLE:
    426                 resID = R.string.callFailed_server_unreachable;
    427                 break;
    428 
    429             case NUMBER_UNREACHABLE:
    430                 resID = R.string.callFailed_number_unreachable;
    431                 break;
    432 
    433             case INVALID_CREDENTIALS:
    434                 resID = R.string.callFailed_invalid_credentials;
    435                 break;
    436 
    437             case SERVER_ERROR:
    438                 resID = R.string.callFailed_server_error;
    439                 break;
    440 
    441             case OUT_OF_NETWORK:
    442                 resID = R.string.callFailed_out_of_network;
    443                 break;
    444 
    445             case LOST_SIGNAL:
    446             case CDMA_DROP:
    447                 resID = R.string.callFailed_noSignal;
    448                 break;
    449 
    450             case LIMIT_EXCEEDED:
    451                 resID = R.string.callFailed_limitExceeded;
    452                 break;
    453 
    454             case POWER_OFF:
    455                 resID = R.string.callFailed_powerOff;
    456                 break;
    457 
    458             case ICC_ERROR:
    459                 resID = R.string.callFailed_simError;
    460                 break;
    461 
    462             case OUT_OF_SERVICE:
    463                 resID = R.string.callFailed_outOfService;
    464                 break;
    465 
    466             case INVALID_NUMBER:
    467             case UNOBTAINABLE_NUMBER:
    468                 resID = R.string.callFailed_unobtainable_number;
    469                 break;
    470 
    471             default:
    472                 resID = R.string.card_title_call_ended;
    473                 break;
    474         }
    475         return this.getView().getContext().getString(resID);
    476     }
    477 
    478     private void showAndInitializeSecondaryCallInfo() {
    479         mSecondaryCallInfo.setVisibility(View.VISIBLE);
    480 
    481         // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible
    482         // until mSecondaryCallInfo is inflated in the call above.
    483         if (mSecondaryCallName == null) {
    484             mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
    485         }
    486         if (mSecondaryPhoto == null) {
    487             mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto);
    488         }
    489 
    490         if (mSecondaryPhotoOverlay == null) {
    491             mSecondaryPhotoOverlay = getView().findViewById(R.id.dim_effect_for_secondary_photo);
    492             mSecondaryPhotoOverlay.setOnClickListener(new OnClickListener() {
    493                 @Override
    494                 public void onClick(View v) {
    495                     getPresenter().secondaryPhotoClicked();
    496                 }
    497             });
    498             mSecondaryPhotoOverlay.setOnTouchListener(new SmallerHitTargetTouchListener());
    499         }
    500     }
    501 
    502     public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    503         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
    504             dispatchPopulateAccessibilityEvent(event, mPrimaryName);
    505             dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
    506             return;
    507         }
    508         dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
    509         dispatchPopulateAccessibilityEvent(event, mPrimaryName);
    510         dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
    511         dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
    512         dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
    513 
    514         return;
    515     }
    516 
    517     private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
    518         if (view == null) return;
    519         final List<CharSequence> eventText = event.getText();
    520         int size = eventText.size();
    521         view.dispatchPopulateAccessibilityEvent(event);
    522         // if no text added write null to keep relative position
    523         if (size == eventText.size()) {
    524             eventText.add(null);
    525         }
    526     }
    527 }
    528