Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2006 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 com.android.services.telephony.common.Call;
     20 import com.android.services.telephony.common.Call.State;
     21 
     22 import android.app.Activity;
     23 import android.app.AlertDialog;
     24 import android.app.FragmentTransaction;
     25 import android.content.DialogInterface;
     26 import android.content.DialogInterface.OnClickListener;
     27 import android.content.DialogInterface.OnCancelListener;
     28 import android.content.Intent;
     29 import android.content.res.Configuration;
     30 import android.os.Bundle;
     31 import android.view.KeyEvent;
     32 import android.view.View;
     33 import android.view.Window;
     34 import android.view.WindowManager;
     35 import android.view.accessibility.AccessibilityEvent;
     36 import android.widget.Toast;
     37 
     38 /**
     39  * Phone app "in call" screen.
     40  */
     41 public class InCallActivity extends Activity {
     42 
     43     public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
     44 
     45     private static final int INVALID_RES_ID = -1;
     46 
     47     private CallButtonFragment mCallButtonFragment;
     48     private CallCardFragment mCallCardFragment;
     49     private AnswerFragment mAnswerFragment;
     50     private DialpadFragment mDialpadFragment;
     51     private ConferenceManagerFragment mConferenceManagerFragment;
     52     private boolean mIsForegroundActivity;
     53     private AlertDialog mDialog;
     54 
     55     /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */
     56     private boolean mShowDialpadRequested;
     57 
     58     @Override
     59     protected void onCreate(Bundle icicle) {
     60         Log.d(this, "onCreate()...  this = " + this);
     61 
     62         super.onCreate(icicle);
     63 
     64         // set this flag so this activity will stay in front of the keyguard
     65         // Have the WindowManager filter out touch events that are "too fat".
     66         int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
     67                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
     68                 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
     69 
     70         getWindow().addFlags(flags);
     71 
     72         requestWindowFeature(Window.FEATURE_NO_TITLE);
     73 
     74         // TODO(klp): Do we need to add this back when prox sensor is not available?
     75         // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
     76 
     77         // Inflate everything in incall_screen.xml and add it to the screen.
     78         setContentView(R.layout.incall_screen);
     79 
     80         initializeInCall();
     81         Log.d(this, "onCreate(): exit");
     82     }
     83 
     84     @Override
     85     protected void onStart() {
     86         Log.d(this, "onStart()...");
     87         super.onStart();
     88 
     89         // setting activity should be last thing in setup process
     90         InCallPresenter.getInstance().setActivity(this);
     91     }
     92 
     93     @Override
     94     protected void onResume() {
     95         Log.i(this, "onResume()...");
     96         super.onResume();
     97 
     98         mIsForegroundActivity = true;
     99         InCallPresenter.getInstance().onUiShowing(true);
    100 
    101         if (mShowDialpadRequested) {
    102             mCallButtonFragment.displayDialpad(true);
    103             mShowDialpadRequested = false;
    104         }
    105     }
    106 
    107     // onPause is guaranteed to be called when the InCallActivity goes
    108     // in the background.
    109     @Override
    110     protected void onPause() {
    111         Log.d(this, "onPause()...");
    112         super.onPause();
    113 
    114         mIsForegroundActivity = false;
    115 
    116         mDialpadFragment.onDialerKeyUp(null);
    117 
    118         InCallPresenter.getInstance().onUiShowing(false);
    119     }
    120 
    121     @Override
    122     protected void onStop() {
    123         Log.d(this, "onStop()...");
    124         super.onStop();
    125     }
    126 
    127     @Override
    128     protected void onDestroy() {
    129         Log.d(this, "onDestroy()...  this = " + this);
    130 
    131         InCallPresenter.getInstance().setActivity(null);
    132 
    133         super.onDestroy();
    134     }
    135 
    136     /**
    137      * Returns true when theActivity is in foreground (between onResume and onPause).
    138      */
    139     /* package */ boolean isForegroundActivity() {
    140         return mIsForegroundActivity;
    141     }
    142 
    143     private boolean hasPendingErrorDialog() {
    144         return mDialog != null;
    145     }
    146     /**
    147      * Dismisses the in-call screen.
    148      *
    149      * We never *really* finish() the InCallActivity, since we don't want to get destroyed and then
    150      * have to be re-created from scratch for the next call.  Instead, we just move ourselves to the
    151      * back of the activity stack.
    152      *
    153      * This also means that we'll no longer be reachable via the BACK button (since moveTaskToBack()
    154      * puts us behind the Home app, but the home app doesn't allow the BACK key to move you any
    155      * farther down in the history stack.)
    156      *
    157      * (Since the Phone app itself is never killed, this basically means that we'll keep a single
    158      * InCallActivity instance around for the entire uptime of the device.  This noticeably improves
    159      * the UI responsiveness for incoming calls.)
    160      */
    161     @Override
    162     public void finish() {
    163         Log.i(this, "finish().  Dialog showing: " + (mDialog != null));
    164 
    165         // skip finish if we are still showing a dialog.
    166         if (!hasPendingErrorDialog() && !mAnswerFragment.hasPendingDialogs()) {
    167             super.finish();
    168         }
    169     }
    170 
    171     @Override
    172     protected void onNewIntent(Intent intent) {
    173         Log.d(this, "onNewIntent: intent = " + intent);
    174 
    175         // We're being re-launched with a new Intent.  Since it's possible for a
    176         // single InCallActivity instance to persist indefinitely (even if we
    177         // finish() ourselves), this sequence can potentially happen any time
    178         // the InCallActivity needs to be displayed.
    179 
    180         // Stash away the new intent so that we can get it in the future
    181         // by calling getIntent().  (Otherwise getIntent() will return the
    182         // original Intent from when we first got created!)
    183         setIntent(intent);
    184 
    185         // Activities are always paused before receiving a new intent, so
    186         // we can count on our onResume() method being called next.
    187 
    188         // Just like in onCreate(), handle the intent.
    189         internalResolveIntent(intent);
    190     }
    191 
    192     @Override
    193     public void onBackPressed() {
    194         Log.d(this, "onBackPressed()...");
    195 
    196         // BACK is also used to exit out of any "special modes" of the
    197         // in-call UI:
    198 
    199         if (mDialpadFragment.isVisible()) {
    200             mCallButtonFragment.displayDialpad(false);  // do the "closing" animation
    201             return;
    202         } else if (mConferenceManagerFragment.isVisible()) {
    203             mConferenceManagerFragment.setVisible(false);
    204             return;
    205         }
    206 
    207         // Always disable the Back key while an incoming call is ringing
    208         final Call call = CallList.getInstance().getIncomingCall();
    209         if (call != null) {
    210             Log.d(this, "Consume Back press for an inconing call");
    211             return;
    212         }
    213 
    214         // Nothing special to do.  Fall back to the default behavior.
    215         super.onBackPressed();
    216     }
    217 
    218     @Override
    219     public boolean onKeyUp(int keyCode, KeyEvent event) {
    220         // push input to the dialer.
    221         if ((mDialpadFragment.isVisible()) && (mDialpadFragment.onDialerKeyUp(event))){
    222             return true;
    223         } else if (keyCode == KeyEvent.KEYCODE_CALL) {
    224             // Always consume CALL to be sure the PhoneWindow won't do anything with it
    225             return true;
    226         }
    227         return super.onKeyUp(keyCode, event);
    228     }
    229 
    230     @Override
    231     public boolean onKeyDown(int keyCode, KeyEvent event) {
    232         switch (keyCode) {
    233             case KeyEvent.KEYCODE_CALL:
    234                 boolean handled = InCallPresenter.getInstance().handleCallKey();
    235                 if (!handled) {
    236                     Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown");
    237                 }
    238                 // Always consume CALL to be sure the PhoneWindow won't do anything with it
    239                 return true;
    240 
    241             // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
    242             // The standard system-wide handling of the ENDCALL key
    243             // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
    244             // already implements exactly what the UI spec wants,
    245             // namely (1) "hang up" if there's a current active call,
    246             // or (2) "don't answer" if there's a current ringing call.
    247 
    248             case KeyEvent.KEYCODE_CAMERA:
    249                 // Disable the CAMERA button while in-call since it's too
    250                 // easy to press accidentally.
    251                 return true;
    252 
    253             case KeyEvent.KEYCODE_VOLUME_UP:
    254             case KeyEvent.KEYCODE_VOLUME_DOWN:
    255             case KeyEvent.KEYCODE_VOLUME_MUTE:
    256                 // Ringer silencing handled by PhoneWindowManager.
    257                 break;
    258 
    259             case KeyEvent.KEYCODE_MUTE:
    260                 // toggle mute
    261                 CallCommandClient.getInstance().mute(!AudioModeProvider.getInstance().getMute());
    262                 return true;
    263 
    264             // Various testing/debugging features, enabled ONLY when VERBOSE == true.
    265             case KeyEvent.KEYCODE_SLASH:
    266                 if (Log.VERBOSE) {
    267                     Log.v(this, "----------- InCallActivity View dump --------------");
    268                     // Dump starting from the top-level view of the entire activity:
    269                     Window w = this.getWindow();
    270                     View decorView = w.getDecorView();
    271                     decorView.debug();
    272                     return true;
    273                 }
    274                 break;
    275             case KeyEvent.KEYCODE_EQUALS:
    276                 // TODO: Dump phone state?
    277                 break;
    278         }
    279 
    280         if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
    281             return true;
    282         }
    283 
    284         return super.onKeyDown(keyCode, event);
    285     }
    286 
    287     private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
    288         Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
    289 
    290         // As soon as the user starts typing valid dialable keys on the
    291         // keyboard (presumably to type DTMF tones) we start passing the
    292         // key events to the DTMFDialer's onDialerKeyDown.
    293         if (mDialpadFragment.isVisible()) {
    294             return mDialpadFragment.onDialerKeyDown(event);
    295 
    296             // TODO: If the dialpad isn't currently visible, maybe
    297             // consider automatically bringing it up right now?
    298             // (Just to make sure the user sees the digits widget...)
    299             // But this probably isn't too critical since it's awkward to
    300             // use the hard keyboard while in-call in the first place,
    301             // especially now that the in-call UI is portrait-only...
    302         }
    303 
    304         return false;
    305     }
    306 
    307     @Override
    308     public void onConfigurationChanged(Configuration config) {
    309         InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config);
    310     }
    311 
    312     public CallButtonFragment getCallButtonFragment() {
    313         return mCallButtonFragment;
    314     }
    315 
    316     private void internalResolveIntent(Intent intent) {
    317         final String action = intent.getAction();
    318 
    319         if (action.equals(intent.ACTION_MAIN)) {
    320             // This action is the normal way to bring up the in-call UI.
    321             //
    322             // But we do check here for one extra that can come along with the
    323             // ACTION_MAIN intent:
    324 
    325             if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
    326                 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
    327                 // dialpad should be initially visible.  If the extra isn't
    328                 // present at all, we just leave the dialpad in its previous state.
    329 
    330                 final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
    331                 Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
    332 
    333                 relaunchedFromDialer(showDialpad);
    334             }
    335 
    336             return;
    337         }
    338     }
    339 
    340     private void relaunchedFromDialer(boolean showDialpad) {
    341         mShowDialpadRequested = showDialpad;
    342 
    343         if (mShowDialpadRequested) {
    344             // If there's only one line in use, AND it's on hold, then we're sure the user
    345             // wants to use the dialpad toward the exact line, so un-hold the holding line.
    346             final Call call = CallList.getInstance().getActiveOrBackgroundCall();
    347             if (call != null && call.getState() == State.ONHOLD) {
    348                 CallCommandClient.getInstance().hold(call.getCallId(), false);
    349             }
    350         }
    351     }
    352 
    353     private void initializeInCall() {
    354         if (mCallButtonFragment == null) {
    355             mCallButtonFragment = (CallButtonFragment) getFragmentManager()
    356                     .findFragmentById(R.id.callButtonFragment);
    357             mCallButtonFragment.getView().setVisibility(View.INVISIBLE);
    358         }
    359 
    360         if (mCallCardFragment == null) {
    361             mCallCardFragment = (CallCardFragment) getFragmentManager()
    362                     .findFragmentById(R.id.callCardFragment);
    363         }
    364 
    365         if (mAnswerFragment == null) {
    366             mAnswerFragment = (AnswerFragment) getFragmentManager()
    367                     .findFragmentById(R.id.answerFragment);
    368         }
    369 
    370         if (mDialpadFragment == null) {
    371             mDialpadFragment = (DialpadFragment) getFragmentManager()
    372                     .findFragmentById(R.id.dialpadFragment);
    373             getFragmentManager().beginTransaction().hide(mDialpadFragment).commit();
    374         }
    375 
    376         if (mConferenceManagerFragment == null) {
    377             mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager()
    378                     .findFragmentById(R.id.conferenceManagerFragment);
    379             mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE);
    380         }
    381     }
    382 
    383     private void toast(String text) {
    384         final Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
    385 
    386         toast.show();
    387     }
    388 
    389     /**
    390      * Simulates a user click to hide the dialpad. This will update the UI to show the call card,
    391      * update the checked state of the dialpad button, and update the proximity sensor state.
    392      */
    393     public void hideDialpadForDisconnect() {
    394         mCallButtonFragment.displayDialpad(false);
    395     }
    396 
    397     public void dismissKeyguard(boolean dismiss) {
    398         if (dismiss) {
    399             getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    400         } else {
    401             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    402         }
    403     }
    404 
    405     public void displayDialpad(boolean showDialpad) {
    406         final FragmentTransaction ft = getFragmentManager().beginTransaction();
    407         if (showDialpad) {
    408             ft.setCustomAnimations(R.anim.incall_dialpad_slide_in, 0);
    409             ft.show(mDialpadFragment);
    410         } else {
    411             ft.setCustomAnimations(0, R.anim.incall_dialpad_slide_out);
    412             ft.hide(mDialpadFragment);
    413         }
    414         ft.commitAllowingStateLoss();
    415 
    416         InCallPresenter.getInstance().getProximitySensor().onDialpadVisible(showDialpad);
    417     }
    418 
    419     public boolean isDialpadVisible() {
    420         return mDialpadFragment.isVisible();
    421     }
    422 
    423     public void displayManageConferencePanel(boolean showPanel) {
    424         if (showPanel) {
    425             mConferenceManagerFragment.setVisible(true);
    426         }
    427     }
    428 
    429     public void showPostCharWaitDialog(int callId, String chars) {
    430         final PostCharDialogFragment fragment = new PostCharDialogFragment(callId,  chars);
    431         fragment.show(getFragmentManager(), "postCharWait");
    432     }
    433 
    434     @Override
    435     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    436         if (mCallCardFragment != null) {
    437             mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
    438         }
    439         return super.dispatchPopulateAccessibilityEvent(event);
    440     }
    441 
    442     public void maybeShowErrorDialogOnDisconnect(Call.DisconnectCause cause) {
    443         Log.d(this, "maybeShowErrorDialogOnDisconnect");
    444 
    445         if (!isFinishing()) {
    446             final int resId = getResIdForDisconnectCause(cause);
    447             if (resId != INVALID_RES_ID) {
    448                 showErrorDialog(resId);
    449             }
    450         }
    451     }
    452 
    453     public void dismissPendingDialogs() {
    454         if (mDialog != null) {
    455             mDialog.dismiss();
    456             mDialog = null;
    457         }
    458         mAnswerFragment.dismissPendingDialogues();
    459     }
    460 
    461     /**
    462      * Utility function to bring up a generic "error" dialog.
    463      */
    464     private void showErrorDialog(int resId) {
    465         final CharSequence msg = getResources().getText(resId);
    466         Log.i(this, "Show Dialog: " + msg);
    467 
    468         dismissPendingDialogs();
    469 
    470         mDialog = new AlertDialog.Builder(this)
    471             .setMessage(msg)
    472             .setPositiveButton(R.string.ok, new OnClickListener() {
    473                 @Override
    474                 public void onClick(DialogInterface dialog, int which) {
    475                     onDialogDismissed();
    476                 }})
    477             .setOnCancelListener(new OnCancelListener() {
    478                 @Override
    479                 public void onCancel(DialogInterface dialog) {
    480                     onDialogDismissed();
    481                 }})
    482             .create();
    483 
    484         mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    485         mDialog.show();
    486     }
    487 
    488     private int getResIdForDisconnectCause(Call.DisconnectCause cause) {
    489         int resId = INVALID_RES_ID;
    490 
    491         if (cause == Call.DisconnectCause.CALL_BARRED) {
    492             resId = R.string.callFailed_cb_enabled;
    493         } else if (cause == Call.DisconnectCause.FDN_BLOCKED) {
    494             resId = R.string.callFailed_fdn_only;
    495         } else if (cause == Call.DisconnectCause.CS_RESTRICTED) {
    496             resId = R.string.callFailed_dsac_restricted;
    497         } else if (cause == Call.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
    498             resId = R.string.callFailed_dsac_restricted_emergency;
    499         } else if (cause == Call.DisconnectCause.CS_RESTRICTED_NORMAL) {
    500             resId = R.string.callFailed_dsac_restricted_normal;
    501         }
    502 
    503         return resId;
    504     }
    505 
    506     private void onDialogDismissed() {
    507         mDialog = null;
    508         InCallPresenter.getInstance().onDismissDialog();
    509     }
    510 }
    511