/* * 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.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.v7.app.AppCompatActivity; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.widget.ListView; import android.widget.QuickContactBadge; import android.widget.TextView; import android.widget.Toast; import com.android.contacts.common.CallUtil; import com.android.contacts.common.ClipboardUtils; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; import com.android.contacts.common.GeoUtil; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.interactions.TouchPointManager; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.UriUtils; import com.android.dialer.calllog.CallDetailHistoryAdapter; import com.android.dialer.calllog.CallLogAsyncTaskUtil; import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener; import com.android.dialer.calllog.CallTypeHelper; import com.android.dialer.calllog.ContactInfoHelper; import com.android.dialer.calllog.PhoneAccountUtils; import com.android.dialer.database.FilteredNumberAsyncQueryHandler; import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; import com.android.dialer.filterednumber.BlockNumberDialogFragment; import com.android.dialer.filterednumber.FilteredNumbersUtil; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil.CallIntentBuilder; import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.util.TelecomUtil; import com.android.incallui.Call.LogState; /** * 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 AppCompatActivity implements MenuItem.OnMenuItemClickListener, View.OnClickListener, BlockNumberDialogFragment.Callback { private static final String TAG = CallDetailActivity.class.getSimpleName(); /** 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 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 CallLogAsyncTaskListener mCallLogAsyncTaskListener = new CallLogAsyncTaskListener() { @Override public void onDeleteCall() { finish(); } @Override public void onDeleteVoicemail() { finish(); } @Override public void onGetCallDetails(PhoneCallDetails[] details) { if (details == null) { // Somewhere went wrong: we're going to bail out and show error to users. Toast.makeText(mContext, R.string.toast_call_detail_error, Toast.LENGTH_SHORT).show(); finish(); return; } // All calls are from the same number and same contact, so pick the first detail. mDetails = details[0]; mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString(); mPostDialDigits = TextUtils.isEmpty(mDetails.postDialDigits) ? "" : mDetails.postDialDigits; mDisplayNumber = mDetails.displayNumber; final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails); final CharSequence displayNumber; if (!TextUtils.isEmpty(mDetails.postDialDigits)) { displayNumber = mDetails.number + mDetails.postDialDigits; } else { displayNumber = mDetails.displayNumber; } final String displayNumberStr = mBidiFormatter.unicodeWrap( displayNumber.toString(), TextDirectionHeuristics.LTR); mDetails.nameDisplayOrder = mContactsPreferences.getDisplayOrder(); if (!TextUtils.isEmpty(mDetails.getPreferredName())) { mCallerName.setText(mDetails.getPreferredName()); mCallerNumber.setText(callLocationOrType + " " + displayNumberStr); } else { mCallerName.setText(displayNumberStr); if (!TextUtils.isEmpty(callLocationOrType)) { mCallerNumber.setText(callLocationOrType); mCallerNumber.setVisibility(View.VISIBLE); } else { mCallerNumber.setVisibility(View.GONE); } } String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, mDetails.accountHandle); if (!TextUtils.isEmpty(accountLabel)) { mAccountLabel.setText(accountLabel); mAccountLabel.setVisibility(View.VISIBLE); } else { mAccountLabel.setVisibility(View.GONE); } final boolean canPlaceCallsTo = PhoneNumberUtil.canPlaceCallsTo(mNumber, mDetails.numberPresentation); mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); mCopyNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); mBlockNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber); final boolean isVoicemailNumber = PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); final boolean showEditNumberBeforeCallAction = canPlaceCallsTo && !isSipNumber && !isVoicemailNumber; mEditBeforeCallActionItem.setVisibility( showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE); final boolean showReportAction = mContactInfoHelper.canReportAsInvalid( mDetails.sourceType, mDetails.objectId); mReportActionItem.setVisibility( showReportAction ? View.VISIBLE : View.GONE); invalidateOptionsMenu(); mHistoryList.setAdapter( new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details)); updateFilteredNumberChanges(); updateContactPhoto(); findViewById(R.id.call_detail).setVisibility(View.VISIBLE); } /** * Determines the location geocode text for a call, or the phone number type * (if available). * * @param details The call details. * @return The phone number type or location. */ private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) { if (!TextUtils.isEmpty(details.namePrimary)) { return Phone.getTypeLabel(mResources, details.numberType, details.numberLabel); } else { return details.geocode; } } }; private Context mContext; private ContactInfoHelper mContactInfoHelper; private ContactsPreferences mContactsPreferences; private CallTypeHelper mCallTypeHelper; private ContactPhotoManager mContactPhotoManager; private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); private LayoutInflater mInflater; private Resources mResources; private PhoneCallDetails mDetails; protected String mNumber; private Uri mVoicemailUri; private String mPostDialDigits = ""; private String mDisplayNumber; private ListView mHistoryList; private QuickContactBadge mQuickContactBadge; private TextView mCallerName; private TextView mCallerNumber; private TextView mAccountLabel; private View mCallButton; private TextView mBlockNumberActionItem; private View mEditBeforeCallActionItem; private View mReportActionItem; private View mCopyNumberActionItem; private Integer mBlockedNumberId; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); mContext = this; mResources = getResources(); mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)); mContactsPreferences = new ContactsPreferences(mContext); mCallTypeHelper = new CallTypeHelper(getResources()); mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(getContentResolver()); mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.call_detail); mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mHistoryList = (ListView) findViewById(R.id.history); mHistoryList.addHeaderView(mInflater.inflate(R.layout.call_detail_header, null)); mHistoryList.addFooterView( mInflater.inflate(R.layout.call_detail_footer, null), null, false); mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo); mQuickContactBadge.setOverlay(null); if (CompatUtils.hasPrioritizedMimeType()) { 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); mContactPhotoManager = ContactPhotoManager.getInstance(this); mCallButton = findViewById(R.id.call_back_button); mCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (TextUtils.isEmpty(mNumber)) { return; } mContext.startActivity( new CallIntentBuilder(getDialableNumber()) .setCallInitiationType(LogState.INITIATION_CALL_DETAILS) .build()); } }); mBlockNumberActionItem = (TextView) findViewById(R.id.call_detail_action_block); mBlockNumberActionItem.setOnClickListener(this); mEditBeforeCallActionItem = findViewById(R.id.call_detail_action_edit_before_call); mEditBeforeCallActionItem.setOnClickListener(this); mReportActionItem = findViewById(R.id.call_detail_action_report); mReportActionItem.setOnClickListener(this); mCopyNumberActionItem = findViewById(R.id.call_detail_action_copy); mCopyNumberActionItem.setOnClickListener(this); if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) { closeSystemDialogs(); } } @Override public void onResume() { super.onResume(); mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); getCallDetails(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY()); } return super.dispatchTouchEvent(ev); } public void getCallDetails() { CallLogAsyncTaskUtil.getCallDetails(this, getCallLogEntryUris(), mCallLogAsyncTaskListener); } /** * Returns the list of URIs to show. *

* 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( TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]); } return uris; } @Override public boolean onCreateOptionsMenu(Menu menu) { final MenuItem deleteMenuItem = menu.add( Menu.NONE, R.id.call_detail_delete_menu_item, Menu.NONE, R.string.call_details_delete); deleteMenuItem.setIcon(R.drawable.ic_delete_24dp); deleteMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); deleteMenuItem.setOnMenuItemClickListener(this); return super.onCreateOptionsMenu(menu); } @Override public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.call_detail_delete_menu_item) { if (hasVoicemail()) { CallLogAsyncTaskUtil.deleteVoicemail( this, mVoicemailUri, mCallLogAsyncTaskListener); } else { final StringBuilder callIds = new StringBuilder(); for (Uri callUri : getCallLogEntryUris()) { if (callIds.length() != 0) { callIds.append(","); } callIds.append(ContentUris.parseId(callUri)); } CallLogAsyncTaskUtil.deleteCalls( this, callIds.toString(), mCallLogAsyncTaskListener); } } return true; } @Override public void onClick(View view) { int resId = view.getId(); if (resId == R.id.call_detail_action_block) { BlockNumberDialogFragment.show( mBlockedNumberId, mNumber, mDetails.countryIso, mDisplayNumber, R.id.call_detail, getFragmentManager(), this); } else if (resId == R.id.call_detail_action_copy) { ClipboardUtils.copyText(mContext, null, mNumber, true); } else if (resId == R.id.call_detail_action_edit_before_call) { Intent dialIntent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(getDialableNumber())); DialerUtils.startActivityWithErrorToast(mContext, dialIntent); } else { Log.wtf(TAG, "Unexpected onClick event from " + view); } } @Override public void onFilterNumberSuccess() { Logger.logInteraction(InteractionEvent.BLOCK_NUMBER_CALL_DETAIL); updateFilteredNumberChanges(); } @Override public void onUnfilterNumberSuccess() { Logger.logInteraction(InteractionEvent.UNBLOCK_NUMBER_CALL_DETAIL); updateFilteredNumberChanges(); } @Override public void onChangeFilteredNumberUndo() { updateFilteredNumberChanges(); } private void updateFilteredNumberChanges() { if (mDetails == null || !FilteredNumbersUtil.canBlockNumber(this, mNumber, mDetails.countryIso)) { return; } final boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber( new OnCheckBlockedListener() { @Override public void onCheckComplete(Integer id) { mBlockedNumberId = id; updateBlockActionItem(); } }, mNumber, mDetails.countryIso); if (!success) { updateBlockActionItem(); } } // Loads and displays the contact photo. private void updateContactPhoto() { if (mDetails == null) { return; } final boolean isVoicemailNumber = PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType); int contactType = ContactPhotoManager.TYPE_DEFAULT; if (isVoicemailNumber) { contactType = ContactPhotoManager.TYPE_VOICEMAIL; } else if (isBusiness) { contactType = ContactPhotoManager.TYPE_BUSINESS; } final String displayName = TextUtils.isEmpty(mDetails.namePrimary) ? mDetails.displayNumber : mDetails.namePrimary.toString(); final String lookupKey = mDetails.contactUri == null ? null : UriUtils.getLookupKeyFromUri(mDetails.contactUri); final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */); mQuickContactBadge.assignContactUri(mDetails.contactUri); mQuickContactBadge.setContentDescription( mResources.getString(R.string.description_contact_details, displayName)); mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, mDetails.photoUri, false /* darkTheme */, true /* isCircular */, request); } private void updateBlockActionItem() { if (mBlockedNumberId == null) { mBlockNumberActionItem.setText(R.string.action_block_number); mBlockNumberActionItem.setCompoundDrawablesRelativeWithIntrinsicBounds( R.drawable.ic_call_detail_block, 0, 0, 0); } else { mBlockNumberActionItem.setText(R.string.action_unblock_number); mBlockNumberActionItem.setCompoundDrawablesRelativeWithIntrinsicBounds( R.drawable.ic_call_detail_unblock, 0, 0, 0); } mBlockNumberActionItem.setVisibility(View.VISIBLE); } private void closeSystemDialogs() { sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } private String getDialableNumber() { return mNumber + mPostDialDigits; } @NeededForTesting public boolean hasVoicemail() { return mVoicemailUri != null; } }