/* * Copyright (C) 2009 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.dialer; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.VoicemailContract.Voicemails; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.QuickContactBadge; import android.widget.TextView; import android.widget.Toast; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; import com.android.contacts.common.GeoUtil; import com.android.contacts.common.CallUtil; import com.android.dialer.calllog.CallDetailHistoryAdapter; import com.android.dialer.calllog.CallTypeHelper; import com.android.dialer.calllog.ContactInfo; import com.android.dialer.calllog.ContactInfoHelper; import com.android.dialer.calllog.PhoneAccountUtils; import com.android.dialer.calllog.PhoneNumberDisplayHelper; import com.android.dialer.calllog.PhoneNumberUtilsWrapper; import com.android.dialer.util.AsyncTaskExecutor; import com.android.dialer.util.AsyncTaskExecutors; import com.android.dialer.util.PrivilegedCallUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.voicemail.VoicemailPlaybackFragment; import com.android.dialer.voicemail.VoicemailStatusHelper; import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage; import com.android.dialer.voicemail.VoicemailStatusHelperImpl; import java.util.List; /** * Displays the details of a specific call log entry. *
* This activity can be either started with the URI of a single call log entry, or with the * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries. */ public class CallDetailActivity extends Activity implements ProximitySensorAware { private static final String TAG = "CallDetail"; private static final char LEFT_TO_RIGHT_EMBEDDING = '\u202A'; private static final char POP_DIRECTIONAL_FORMATTING = '\u202C'; /** The time to wait before enabling the blank the screen due to the proximity sensor. */ private static final long PROXIMITY_BLANK_DELAY_MILLIS = 100; /** The time to wait before disabling the blank the screen due to the proximity sensor. */ private static final long PROXIMITY_UNBLANK_DELAY_MILLIS = 500; /** The enumeration of {@link AsyncTask} objects used in this class. */ public enum Tasks { MARK_VOICEMAIL_READ, DELETE_VOICEMAIL_AND_FINISH, REMOVE_FROM_CALL_LOG_AND_FINISH, UPDATE_PHONE_CALL_DETAILS, } /** A long array extra containing ids of call log entries to display. */ public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS"; /** If we are started with a voicemail, we'll find the uri to play with this extra. */ public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI"; /** If we should immediately start playback of the voicemail, this extra will be set to true. */ public static final String EXTRA_VOICEMAIL_START_PLAYBACK = "EXTRA_VOICEMAIL_START_PLAYBACK"; /** If the activity was triggered from a notification. */ public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; public static final String VOICEMAIL_FRAGMENT_TAG = "voicemail_fragment"; private CallTypeHelper mCallTypeHelper; private PhoneNumberDisplayHelper mPhoneNumberHelper; private QuickContactBadge mQuickContactBadge; private TextView mCallerName; private TextView mCallerNumber; private TextView mAccountLabel; private AsyncTaskExecutor mAsyncTaskExecutor; private ContactInfoHelper mContactInfoHelper; private String mNumber = null; private String mDefaultCountryIso; /* package */ LayoutInflater mInflater; /* package */ Resources mResources; /** Helper to load contact photos. */ private ContactPhotoManager mContactPhotoManager; /** Helper to make async queries to content resolver. */ private CallDetailActivityQueryHandler mAsyncQueryHandler; /** Helper to get voicemail status messages. */ private VoicemailStatusHelper mVoicemailStatusHelper; // Views related to voicemail status message. private View mStatusMessageView; private TextView mStatusMessageText; private TextView mStatusMessageAction; private TextView mVoicemailTranscription; private LinearLayout mVoicemailHeader; private Uri mVoicemailUri; private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); /** Whether we should show "edit number before call" in the options menu. */ private boolean mHasEditNumberBeforeCallOption; /** Whether we should show "trash" in the options menu. */ private boolean mHasTrashOption; /** Whether we should show "remove from call log" in the options menu. */ private boolean mHasRemoveFromCallLogOption; private ProximitySensorManager mProximitySensorManager; private final ProximitySensorListener mProximitySensorListener = new ProximitySensorListener(); /** Listener to changes in the proximity sensor state. */ private class ProximitySensorListener implements ProximitySensorManager.Listener { /** Used to show a blank view and hide the action bar. */ private final Runnable mBlankRunnable = new Runnable() { @Override public void run() { View blankView = findViewById(R.id.blank); blankView.setVisibility(View.VISIBLE); getActionBar().hide(); } }; /** Used to remove the blank view and show the action bar. */ private final Runnable mUnblankRunnable = new Runnable() { @Override public void run() { View blankView = findViewById(R.id.blank); blankView.setVisibility(View.GONE); getActionBar().show(); } }; @Override public synchronized void onNear() { clearPendingRequests(); postDelayed(mBlankRunnable, PROXIMITY_BLANK_DELAY_MILLIS); } @Override public synchronized void onFar() { clearPendingRequests(); postDelayed(mUnblankRunnable, PROXIMITY_UNBLANK_DELAY_MILLIS); } /** Removed any delayed requests that may be pending. */ public synchronized void clearPendingRequests() { View blankView = findViewById(R.id.blank); blankView.removeCallbacks(mBlankRunnable); blankView.removeCallbacks(mUnblankRunnable); } /** Post a {@link Runnable} with a delay on the main thread. */ private synchronized void postDelayed(Runnable runnable, long delayMillis) { // Post these instead of executing immediately so that: // - They are guaranteed to be executed on the main thread. // - If the sensor values changes rapidly for some time, the UI will not be // updated immediately. View blankView = findViewById(R.id.blank); blankView.postDelayed(runnable, delayMillis); } } static final String[] CALL_LOG_PROJECTION = new String[] { CallLog.Calls.DATE, CallLog.Calls.DURATION, CallLog.Calls.NUMBER, CallLog.Calls.TYPE, CallLog.Calls.COUNTRY_ISO, CallLog.Calls.GEOCODED_LOCATION, CallLog.Calls.NUMBER_PRESENTATION, CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, CallLog.Calls.PHONE_ACCOUNT_ID, CallLog.Calls.FEATURES, CallLog.Calls.DATA_USAGE, CallLog.Calls.TRANSCRIPTION }; static final int DATE_COLUMN_INDEX = 0; static final int DURATION_COLUMN_INDEX = 1; static final int NUMBER_COLUMN_INDEX = 2; static final int CALL_TYPE_COLUMN_INDEX = 3; static final int COUNTRY_ISO_COLUMN_INDEX = 4; static final int GEOCODED_LOCATION_COLUMN_INDEX = 5; static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6; static final int ACCOUNT_COMPONENT_NAME = 7; static final int ACCOUNT_ID = 8; static final int FEATURES = 9; static final int DATA_USAGE = 10; static final int TRANSCRIPTION_COLUMN_INDEX = 11; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.call_detail); mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mResources = getResources(); mCallTypeHelper = new CallTypeHelper(getResources()); mPhoneNumberHelper = new PhoneNumberDisplayHelper(this, mResources); mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); mAsyncQueryHandler = new CallDetailActivityQueryHandler(this); mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI); mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo); mQuickContactBadge.setOverlay(null); mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); mCallerName = (TextView) findViewById(R.id.caller_name); mCallerNumber = (TextView) findViewById(R.id.caller_number); mAccountLabel = (TextView) findViewById(R.id.phone_account_label); mDefaultCountryIso = GeoUtil.getCurrentCountryIso(this); mContactPhotoManager = ContactPhotoManager.getInstance(this); mProximitySensorManager = new ProximitySensorManager(this, mProximitySensorListener); mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)); getActionBar().setDisplayHomeAsUpEnabled(true); optionallyHandleVoicemail(); if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) { closeSystemDialogs(); } } @Override public void onResume() { super.onResume(); updateData(getCallLogEntryUris()); } /** * Handle voicemail playback or hide voicemail ui. *
* If the Intent used to start this Activity contains the suitable extras, then start voicemail
* playback. If it doesn't, then don't inflate the voicemail ui.
*/
private void optionallyHandleVoicemail() {
if (hasVoicemail()) {
LayoutInflater inflater =
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mVoicemailHeader =
(LinearLayout) inflater.inflate(R.layout.call_details_voicemail_header, null);
View voicemailContainer = mVoicemailHeader.findViewById(R.id.voicemail_container);
mStatusMessageView = mVoicemailHeader.findViewById(R.id.voicemail_status);
mStatusMessageText =
(TextView) mVoicemailHeader.findViewById(R.id.voicemail_status_message);
mStatusMessageAction =
(TextView) mVoicemailHeader.findViewById(R.id.voicemail_status_action);
mVoicemailTranscription = (
TextView) mVoicemailHeader.findViewById(R.id.voicemail_transcription);
ListView historyList = (ListView) findViewById(R.id.history);
historyList.addHeaderView(mVoicemailHeader);
// Has voicemail: add the voicemail fragment. Add suitable arguments to set the uri
// to play and optionally start the playback.
// Do a query to fetch the voicemail status messages.
VoicemailPlaybackFragment playbackFragment;
playbackFragment = (VoicemailPlaybackFragment) getFragmentManager().findFragmentByTag(
VOICEMAIL_FRAGMENT_TAG);
if (playbackFragment == null) {
playbackFragment = new VoicemailPlaybackFragment();
Bundle fragmentArguments = new Bundle();
fragmentArguments.putParcelable(EXTRA_VOICEMAIL_URI, mVoicemailUri);
if (getIntent().getBooleanExtra(EXTRA_VOICEMAIL_START_PLAYBACK, false)) {
fragmentArguments.putBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, true);
}
playbackFragment.setArguments(fragmentArguments);
getFragmentManager().beginTransaction()
.add(R.id.voicemail_container, playbackFragment, VOICEMAIL_FRAGMENT_TAG)
.commitAllowingStateLoss();
}
voicemailContainer.setVisibility(View.VISIBLE);
mAsyncQueryHandler.startVoicemailStatusQuery(mVoicemailUri);
markVoicemailAsRead(mVoicemailUri);
}
}
private boolean hasVoicemail() {
return mVoicemailUri != null;
}
private void markVoicemailAsRead(final Uri voicemailUri) {
mAsyncTaskExecutor.submit(Tasks.MARK_VOICEMAIL_READ, new AsyncTask
* There are two ways the URIs can be provided to the activity: as the data on the intent, or as
* a list of ids in the call log added as an extra on the URI.
*
* If both are available, the data on the intent takes precedence.
*/
private Uri[] getCallLogEntryUris() {
final Uri uri = getIntent().getData();
if (uri != null) {
// If there is a data on the intent, it takes precedence over the extra.
return new Uri[]{ uri };
}
final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS);
final int numIds = ids == null ? 0 : ids.length;
final Uri[] uris = new Uri[numIds];
for (int index = 0; index < numIds; ++index) {
uris[index] = ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, ids[index]);
}
return uris;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CALL: {
// Make sure phone isn't already busy before starting direct call
TelephonyManager tm = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
DialerUtils.startActivityWithErrorToast(this,
PrivilegedCallUtil.getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_TEL,
mNumber, null)), R.string.call_not_available);
return true;
}
}
}
return super.onKeyDown(keyCode, event);
}
/**
* Update user interface with details of given call.
*
* @param callUris URIs into {@link android.provider.CallLog.Calls} of the calls to be displayed
*/
private void updateData(final Uri... callUris) {
class UpdateContactDetailsTask extends AsyncTask