当来电或去电时,通过记录里的信息是怎么保存下来的呢?
在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!");
}
}
}