当来电或去电时,通过记录里的信息是怎么保存下来的呢?
在CallNotifier.java代码里有这样一个方法:
private void onDisconnect(AsyncResult r) {
if (VDBG) log("onDisconnect()... CallManager state: " + mCM.getState());
Connection c = (Connection) r.result;
mDisconnectNumber = c.getAddress();
if (DBG) log("mDisconnectNumber:" + mDisconnectNumber);
if (DBG && c != null) {
log("- onDisconnect: cause = " + c.getDisconnectCause()
+ ", incoming = " + c.isIncoming()
+ ", date = " + c.getCreateTime());
}
mCdmaVoicePrivacyState = false;
int autoretrySetting = 0;
if ((c != null) && (c.getCall().getPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA)) {
autoretrySetting = android.provider.Settings.System.getInt(mApplication.
getContentResolver(),android.provider.Settings.System.CALL_AUTO_RETRY, 0);
}
if ((c != null) && (c.getCall().getPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA)) {
// Stop any signalInfo tone being played when a call gets ended
stopSignalInfoTone();
// Resetting the CdmaPhoneCallState members
mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
// Remove Call waiting timers
removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
}
// Stop the ringer if it was ringing (for an incoming call that
// either disconnected by itself, or was rejected by the user.)
//
// TODO: We technically *shouldn't* stop the ringer if the
// foreground or background call disconnects while an incoming call
// is still ringing, but that's a really rare corner case.
// It's safest to just unconditionally stop the ringer here.
// CDMA: For Call collision cases i.e. when the user makes an out going call
// and at the same time receives an Incoming Call, the Incoming Call is given
// higher preference. At this time framework sends a disconnect for the Out going
// call connection hence we should *not* be stopping the ringer being played for
// the Incoming Call
Call ringingCall = mCM.getFirstActiveRingingCall();
if (ringingCall.getPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if (PhoneUtils.isRealIncomingCall(ringingCall.getState())) {
// Also we need to take off the "In Call" icon from the Notification
// area as the Out going Call never got connected
if (DBG) log("cancelCallInProgressNotification()... (onDisconnect)");
NotificationMgr.getDefault().cancelCallInProgressNotification();
} else {
if (DBG) log("stopRing()... (onDisconnect)");
mRinger.stopRing();
}
} else { // GSM
if (DBG) log("stopRing()... (onDisconnect)");
mRinger.stopRing();
}
// stop call waiting tone if needed when disconnecting
if (mCallWaitingTonePlayer != null) {
mCallWaitingTonePlayer.stopTone();
mCallWaitingTonePlayer = null;
}
// Check for the various tones we might need to play (thru the
// earpiece) after a call disconnects.
int toneToPlay = InCallTonePlayer.TONE_NONE;
// The "Busy" or "Congestion" tone is the highest priority:
if (c != null) {
Connection.DisconnectCause cause = c.getDisconnectCause();
if (cause == Connection.DisconnectCause.BUSY) {
if (DBG) log("- need to play BUSY tone!");
toneToPlay = InCallTonePlayer.TONE_BUSY;
} else if (cause == Connection.DisconnectCause.CONGESTION) {
if (DBG) log("- need to play CONGESTION tone!");
toneToPlay = InCallTonePlayer.TONE_CONGESTION;
} else if (((cause == Connection.DisconnectCause.NORMAL)
|| (cause == Connection.DisconnectCause.LOCAL))
&& (mApplication.isOtaCallInActiveState())) {
if (DBG) log("- need to play OTA_CALL_END tone!");
toneToPlay = InCallTonePlayer.TONE_OTA_CALL_END;
} else if (cause == Connection.DisconnectCause.CDMA_REORDER) {
if (DBG) log("- need to play CDMA_REORDER tone!");
toneToPlay = InCallTonePlayer.TONE_REORDER;
} else if (cause == Connection.DisconnectCause.CDMA_INTERCEPT) {
if (DBG) log("- need to play CDMA_INTERCEPT tone!");
toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
} else if (cause == Connection.DisconnectCause.CDMA_DROP) {
if (DBG) log("- need to play CDMA_DROP tone!");
toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
} else if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
if (DBG) log("- need to play OUT OF SERVICE tone!");
toneToPlay = InCallTonePlayer.TONE_OUT_OF_SERVICE;
} else if (cause == Connection.DisconnectCause.UNOBTAINABLE_NUMBER) {
if (DBG) log("- need to play TONE_UNOBTAINABLE_NUMBER tone!");
toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
} else if (cause == Connection.DisconnectCause.ERROR_UNSPECIFIED) {
if (DBG) log("- DisconnectCause is ERROR_UNSPECIFIED: play TONE_CALL_ENDED!");
toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
}
}
// If we don't need to play BUSY or CONGESTION, then play the
// "call ended" tone if this was a "regular disconnect" (i.e. a
// normal call where one end or the other hung up) *and* this
// disconnect event caused the phone to become idle. (In other
// words, we *don't* play the sound if one call hangs up but
// there's still an active call on the other line.)
// TODO: We may eventually want to disable this via a preference.
if ((toneToPlay == InCallTonePlayer.TONE_NONE)
&& (mCM.getState() == Phone.State.IDLE)
&& (c != null)) {
Connection.DisconnectCause cause = c.getDisconnectCause();
if ((cause == Connection.DisconnectCause.NORMAL) // remote hangup
|| (cause == Connection.DisconnectCause.LOCAL)) { // local hangup
if (VDBG) log("- need to play CALL_ENDED tone!");
toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
mIsCdmaRedialCall = false;
}
}
if (mCM.getState() == Phone.State.IDLE) {
// Don't reset the audio mode or bluetooth/speakerphone state
// if we still need to let the user hear a tone through the earpiece.
if (toneToPlay == InCallTonePlayer.TONE_NONE) {
int currentMode = mAudioManager.getMode();
if(AudioManager.MODE_IN_VT_CALL == currentMode){
if(!isVTConnected){
resetAudioStateAfterDisconnect();
}else{
isNeedResetAudio = true;
sendEmptyMessageDelayed(DELAY_RESET_AUDIO_MODE, 5000);
}
}else{
resetAudioStateAfterDisconnect();
}
}
NotificationMgr.getDefault().cancelCallInProgressNotification();
// If the InCallScreen is *not* in the foreground, forcibly
// dismiss it to make sure it won't still be in the activity
// history. (But if it *is* in the foreground, don't mess
// with it; it needs to be visible, displaying the "Call
// ended" state.)
if (!mApplication.isShowingCallScreen()) {
if (VDBG) log("onDisconnect: force InCallScreen to finish()");
mApplication.dismissCallScreen();
} else {
if (VDBG) log("onDisconnect: In call screen. Set short timeout.");
mApplication.clearUserActivityTimeout();
}
}
if (c != null) {
final String number = c.getAddress();
final long date = c.getCreateTime();
final long duration = c.getDurationMillis();
final Connection.DisconnectCause cause = c.getDisconnectCause();
final Phone phone = c.getCall().getPhone();
final int dialType;
if (PhoneApp.getInstance().isVTCall())
dialType = CallLog.Calls.VIDEO_DIAL;
else
dialType = CallLog.Calls.VOICE_DIAL;
// Set the "type" to be displayed in the call log (see constants in CallLog.Calls)
final int callLogType;
if (c.isIncoming()) {
callLogType = (cause == Connection.DisconnectCause.INCOMING_MISSED ?
Calls.MISSED_TYPE : Calls.INCOMING_TYPE);
} else {
callLogType = Calls.OUTGOING_TYPE;
}
if (VDBG) log("- callLogType: " + callLogType + ", UserData: " + c.getUserData());
{
final CallerInfo ci = getCallerInfoFromConnection(c); // May be null.
final String logNumber = getLogNumber(c, ci);
if (DBG) log("- onDisconnect(): logNumber set to: " + /*logNumber*/ "xxxxxxx");
// TODO: In getLogNumber we use the presentation from
// the connection for the CNAP. Should we use the one
// below instead? (comes from caller info)
// For international calls, 011 needs to be logged as +
final int presentation = getPresentation(c, ci);
if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if ((PhoneNumberUtils.isEmergencyNumber(number))
&& (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF)) {
if (mEmergencyTonePlayerVibrator != null) {
mEmergencyTonePlayerVibrator.stop();
}
}
}
// To prevent accidental redial of emergency numbers
// (carrier requirement) the quickest solution is to
// not log the emergency number. We gate on CDMA
// (ugly) when we actually mean carrier X.
// TODO: Clean this up and come up with a unified strategy.
final boolean shouldNotlogEmergencyNumber =
(phone.getPhoneType() == Phone.PHONE_TYPE_CDMA);
// Don't call isOtaSpNumber on GSM phones.
final boolean isOtaNumber = (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA)
&& phone.isOtaSpNumber(number);
final boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(number);
// Don't put OTA or CDMA Emergency calls into call log
if (!(isOtaNumber || isEmergencyNumber && shouldNotlogEmergencyNumber ||
mIsPhoneNumberInBlackList)) {
CallLogAsync.AddCallArgs args = new CallLogAsync.AddCallArgs( mApplication, ci, logNumber, presentation, callLogType, date, duration, dialType);
mCallLog.addCall(args);
mIsPhoneNumberInBlackList = false;
}
}
if (callLogType == Calls.MISSED_TYPE) {
// Show the "Missed call" notification.
// (Note we *don't* do this if this was an incoming call that
// the user deliberately rejected.)
showMissedCallNotification(c, date);
}
// Possibly play a "post-disconnect tone" thru the earpiece.
// We do this here, rather than from the InCallScreen
// activity, since we need to do this even if you're not in
// the Phone UI at the moment the connection ends.
if (toneToPlay != InCallTonePlayer.TONE_NONE) {
if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")...");
new InCallTonePlayer(toneToPlay).start();
// TODO: alternatively, we could start an InCallTonePlayer
// here with an "unlimited" tone length,
// and manually stop it later when this connection truly goes
// away. (The real connection over the network was closed as soon
// as we got the BUSY message. But our telephony layer keeps the
// connection open for a few extra seconds so we can show the
// "busy" indication to the user. We could stop the busy tone
// when *that* connection's "disconnect" event comes in.)
}
if (mCM.getState() == Phone.State.IDLE) {
// Release screen wake locks if the in-call screen is not
// showing. Otherwise, let the in-call screen handle this because
// it needs to show the call ended screen for a couple of
// seconds.
if (!mApplication.isShowingCallScreen()) {
if (VDBG) log("- NOT showing in-call screen; releasing wake locks!");
mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
mApplication.requestWakeState(PhoneApp.WakeState.SLEEP);
} else {
if (VDBG) log("- still showing in-call screen; not releasing wake locks.");
}
} else {
if (VDBG) log("- phone still in use; not releasing wake locks.");
}
if (((mPreviousCdmaCallState == Call.State.DIALING)
|| (mPreviousCdmaCallState == Call.State.ALERTING))
&& (!PhoneNumberUtils.isEmergencyNumber(number))
&& (cause != Connection.DisconnectCause.INCOMING_MISSED )
&& (cause != Connection.DisconnectCause.NORMAL)
&& (cause != Connection.DisconnectCause.LOCAL)
&& (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
if (!mIsCdmaRedialCall) {
if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) {
// TODO: (Moto): The contact reference data may need to be stored and use
// here when redialing a call. For now, pass in NULL as the URI parameter.
PhoneUtils.placeCall(phone, number, null);
mIsCdmaRedialCall = true;
} else {
mIsCdmaRedialCall = false;
}
} else {
mIsCdmaRedialCall = false;
}
}
}
}
CDMA情况:
/**
* Performs Call logging based on Timeout or Ignore Call Waiting Call for CDMA,
* and finally calls Hangup on the Call Waiting connection.
*
* This method should be called only from the UI thread.
* @see sendCdmaCallWaitingReject()
*/
private void onCdmaCallWaitingReject() {
final Call ringingCall = mCM.getFirstActiveRingingCall();
// Call waiting timeout scenario
if (ringingCall.getState() == Call.State.WAITING) {
// Code for perform Call logging and missed call notification
Connection c = ringingCall.getLatestConnection();
if (c != null) {
String number = c.getAddress();
int presentation = c.getNumberPresentation();
final long date = c.getCreateTime();
final long duration = c.getDurationMillis();
final int callLogType = mCallWaitingTimeOut ?
Calls.MISSED_TYPE : Calls.INCOMING_TYPE;
// get the callerinfo object and then log the call with it.
Object o = c.getUserData();
final CallerInfo ci;
if ((o == null) || (o instanceof CallerInfo)) {
ci = (CallerInfo) o;
} else {
ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
}
// add this to judge dial type
final int dialType;
if (PhoneApp.getInstance().isVTCall())
dialType = CallLog.Calls.VIDEO_DIAL;
else
dialType = CallLog.Calls.VOICE_DIAL;
// Do final CNAP modifications of logNumber prior to logging [mimicking
// onDisconnect()]
final String logNumber = PhoneUtils.modifyForSpecialCnapCases(
mApplication, ci, number, presentation);
final int newPresentation = (ci != null) ? ci.numberPresentation : presentation;
if (DBG) log("- onCdmaCallWaitingReject(): logNumber set to: " + logNumber
+ ", newPresentation value is: " + newPresentation);
CallLogAsync.AddCallArgs args =
new CallLogAsync.AddCallArgs(
mApplication, ci, logNumber, presentation,
callLogType, date, duration, dialType);
mCallLog.addCall(args);
if (callLogType == Calls.MISSED_TYPE) {
// Add missed call notification
showMissedCallNotification(c, date);
} else {
// Remove Call waiting 20 second display timer in the queue
removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
}
// Hangup the RingingCall connection for CW
PhoneUtils.hangup(c);
}
//Reset the mCallWaitingTimeOut boolean
mCallWaitingTimeOut = false;
}
}
通过上述的红色部分,调用了: CallLogAsync.AddCallArgs 的AddCallArgs方法,而CallLogAsync.java是管理通讯录的接口。
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.phone; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import android.os.Looper; import android.provider.CallLog.Calls; import android.util.Log; import com.android.internal.telephony.CallerInfo; /** * Class to access the call logs database asynchronously since * database ops can take a long time depending on the system's load. * It uses AsyncTask which has its own thread pool. * * <pre class="prettyprint"> * Typical usage: * ============== * * // From an activity... * String mLastNumber = ""; * * CallLogAsync log = new CallLogAsync(); * * CallLogAsync.AddCallArgs addCallArgs = new CallLogAsync.AddCallArgs( * this, ci, number, presentation, type, timestamp, duration); * * log.addCall(addCallArgs); * * CallLogAsync.GetLastOutgoingCallArgs lastCallArgs = new CallLogAsync.GetLastOutgoingCallArgs( * this, new CallLogAsync.OnLastOutgoingCallComplete() { * public void lastOutgoingCall(String number) { mLastNumber = number; } * }); * log.getLastOutgoingCall(lastCallArgs); * </pre> * */ public class CallLogAsync { private static final String TAG = "CallLogAsync"; /** * Parameter object to hold the args to add a call in the call log DB. */ public static class AddCallArgs { /** * @param ci CallerInfo. * @param number To be logged. * @param presentation Of the number. * @param callType The type of call (e.g INCOMING_TYPE). @see * android.provider.CallLog for the list of values. * @param timestamp Of the call (millisecond since epoch). * @param durationInMillis Of the call (millisecond). */ public AddCallArgs(Context context, CallerInfo ci, String number, int presentation, int callType, long timestamp, long durationInMillis, int dialType) { // Note that the context is passed each time. We could // have stored it in a member but we've run into a bunch // of memory leaks in the past that resulted from storing // references to contexts in places that were long lived // when the contexts were expected to be short lived. For // example, if you initialize this class with an Activity // instead of an Application the Activity can't be GCed // until this class can, and Activities tend to hold // references to large amounts of RAM for things like the // bitmaps in their views. // // Having hit more than a few of those bugs in the past // we've grown cautious of storing references to Contexts // when it's not very clear that the thing holding the // references is tightly tied to the Context, for example // Views the Activity is displaying. this.context = context; this.ci = ci; this.number = number; this.presentation = presentation; this.callType = callType; this.timestamp = timestamp; this.durationInSec = (int)(durationInMillis / 1000); this.dialType = dialType; } // Since the members are accessed directly, we don't use the // mXxxx notation. public final Context context; public final CallerInfo ci; public final String number; public final int presentation; public final int callType; public final long timestamp; public final int durationInSec; public final int dialType; } /** * Parameter object to hold the args to get the last outgoing call * from the call log DB. */ public static class GetLastOutgoingCallArgs { public GetLastOutgoingCallArgs(Context context, OnLastOutgoingCallComplete callback) { this.context = context; this.callback = callback; } public final Context context; public final OnLastOutgoingCallComplete callback; } /** * Non blocking version of CallLog.addCall(...) */ public AsyncTask addCall(AddCallArgs args) { assertUiThread(); return new AddCallTask().execute(args); } /** Interface to retrieve the last dialed number asynchronously. */ public interface OnLastOutgoingCallComplete { /** @param number The last dialed number or an empty string if * none exists yet. */ void lastOutgoingCall(String number); } /** * CallLog.getLastOutgoingCall(...) */ public AsyncTask getLastOutgoingCall(GetLastOutgoingCallArgs args) { assertUiThread(); return new GetLastOutgoingCallTask(args.callback).execute(args); } /** * AsyncTask to save calls in the DB. */ private class AddCallTask extends AsyncTask<AddCallArgs, Void, Uri[]> { @Override protected Uri[] doInBackground(AddCallArgs... callList) { int count = callList.length; Uri[] result = new Uri[count]; //Add by kylin 2012.02.14 try { for (int i = 0; i < count; i++) { AddCallArgs c = callList[i]; // May block. result[i] = Calls.addCall( c.ci, c.context, c.number, c.presentation, c.callType, c.timestamp, c.durationInSec, c.dialType); } } catch (Exception e) { // TODO: handle exception } //end return result; } // Perform a simple sanity check to make sure the call was // written in the database. Typically there is only one result // per call so it is easy to identify which one failed. @Override protected void onPostExecute(Uri[] result) { for (Uri uri : result) { if (uri == null) { Log.e(TAG, "Failed to write call to the log."); } } } } /** * AsyncTask to get the last outgoing call from the DB. */ private class GetLastOutgoingCallTask extends AsyncTask<GetLastOutgoingCallArgs, Void, String> { private final OnLastOutgoingCallComplete mCallback; private String mNumber; public GetLastOutgoingCallTask(OnLastOutgoingCallComplete callback) { mCallback = callback; } // Happens on a background thread. We cannot run the callback // here because only the UI thread can modify the view // hierarchy (e.g enable/disable the dial button). The // callback is ran rom the post execute method. @Override protected String doInBackground(GetLastOutgoingCallArgs... list) { int count = list.length; String number = ""; for (GetLastOutgoingCallArgs args : list) { // May block. Select only the last one. number = Calls.getLastOutgoingCall(args.context); } return number; // passed to the onPostExecute method. } // Happens on the UI thread, it is safe to run the callback // that may do some work on the views. @Override protected void onPostExecute(String number) { assertUiThread(); mCallback.lastOutgoingCall(number); } } private void assertUiThread() { if (!Looper.getMainLooper().equals(Looper.myLooper())) { throw new RuntimeException("Not on the UI thread!"); } } }