1/* 2 * Copyright (C) 2009 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 17package com.android.dialer; 18 19import android.app.Activity; 20import android.content.ContentResolver; 21import android.content.ContentUris; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.net.Uri; 26import android.os.Bundle; 27import android.os.PowerManager; 28import android.provider.ContactsContract.CommonDataKinds.Phone; 29import android.provider.VoicemailContract.Voicemails; 30import android.telecom.PhoneAccount; 31import android.telecom.PhoneAccountHandle; 32import android.telephony.TelephonyManager; 33import android.text.BidiFormatter; 34import android.text.TextDirectionHeuristics; 35import android.text.TextUtils; 36import android.util.Log; 37import android.view.KeyEvent; 38import android.view.LayoutInflater; 39import android.view.Menu; 40import android.view.MenuItem; 41import android.view.View; 42import android.widget.LinearLayout; 43import android.widget.ListView; 44import android.widget.QuickContactBadge; 45import android.widget.TextView; 46import android.widget.Toast; 47 48import com.android.contacts.common.ContactPhotoManager; 49import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; 50import com.android.contacts.common.util.PermissionsUtil; 51import com.android.contacts.common.GeoUtil; 52import com.android.contacts.common.CallUtil; 53import com.android.contacts.common.util.UriUtils; 54import com.android.dialer.calllog.CallDetailHistoryAdapter; 55import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener; 56import com.android.dialer.calllog.CallLogAsyncTaskUtil; 57import com.android.dialer.calllog.CallTypeHelper; 58import com.android.dialer.calllog.ContactInfo; 59import com.android.dialer.calllog.ContactInfoHelper; 60import com.android.dialer.calllog.PhoneAccountUtils; 61import com.android.dialer.calllog.PhoneNumberDisplayUtil; 62import com.android.dialer.util.DialerUtils; 63import com.android.dialer.util.IntentUtil; 64import com.android.dialer.util.PhoneNumberUtil; 65import com.android.dialer.util.TelecomUtil; 66 67import java.util.List; 68 69/** 70 * Displays the details of a specific call log entry. 71 * <p> 72 * This activity can be either started with the URI of a single call log entry, or with the 73 * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries. 74 */ 75public class CallDetailActivity extends Activity 76 implements MenuItem.OnMenuItemClickListener { 77 private static final String TAG = "CallDetail"; 78 79 /** A long array extra containing ids of call log entries to display. */ 80 public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS"; 81 /** If we are started with a voicemail, we'll find the uri to play with this extra. */ 82 public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI"; 83 /** If the activity was triggered from a notification. */ 84 public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; 85 86 public static final String VOICEMAIL_FRAGMENT_TAG = "voicemail_fragment"; 87 88 private CallLogAsyncTaskListener mCallLogAsyncTaskListener = new CallLogAsyncTaskListener() { 89 @Override 90 public void onDeleteCall() { 91 finish(); 92 } 93 94 @Override 95 public void onDeleteVoicemail() { 96 finish(); 97 } 98 99 @Override 100 public void onGetCallDetails(PhoneCallDetails[] details) { 101 if (details == null) { 102 // Somewhere went wrong: we're going to bail out and show error to users. 103 Toast.makeText(mContext, R.string.toast_call_detail_error, 104 Toast.LENGTH_SHORT).show(); 105 finish(); 106 return; 107 } 108 109 // We know that all calls are from the same number and the same contact, so pick the 110 // first. 111 PhoneCallDetails firstDetails = details[0]; 112 mNumber = TextUtils.isEmpty(firstDetails.number) ? 113 null : firstDetails.number.toString(); 114 final int numberPresentation = firstDetails.numberPresentation; 115 final Uri contactUri = firstDetails.contactUri; 116 final Uri photoUri = firstDetails.photoUri; 117 final PhoneAccountHandle accountHandle = firstDetails.accountHandle; 118 119 // Cache the details about the phone number. 120 final boolean canPlaceCallsTo = 121 PhoneNumberUtil.canPlaceCallsTo(mNumber, numberPresentation); 122 mIsVoicemailNumber = 123 PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, mNumber); 124 final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber); 125 126 final CharSequence callLocationOrType = getNumberTypeOrLocation(firstDetails); 127 128 final CharSequence displayNumber = firstDetails.displayNumber; 129 final String displayNumberStr = mBidiFormatter.unicodeWrap( 130 displayNumber.toString(), TextDirectionHeuristics.LTR); 131 132 if (!TextUtils.isEmpty(firstDetails.name)) { 133 mCallerName.setText(firstDetails.name); 134 mCallerNumber.setText(callLocationOrType + " " + displayNumberStr); 135 } else { 136 mCallerName.setText(displayNumberStr); 137 if (!TextUtils.isEmpty(callLocationOrType)) { 138 mCallerNumber.setText(callLocationOrType); 139 mCallerNumber.setVisibility(View.VISIBLE); 140 } else { 141 mCallerNumber.setVisibility(View.GONE); 142 } 143 } 144 145 mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); 146 147 String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, accountHandle); 148 if (!TextUtils.isEmpty(accountLabel)) { 149 mAccountLabel.setText(accountLabel); 150 mAccountLabel.setVisibility(View.VISIBLE); 151 } else { 152 mAccountLabel.setVisibility(View.GONE); 153 } 154 155 mHasEditNumberBeforeCallOption = 156 canPlaceCallsTo && !isSipNumber && !mIsVoicemailNumber; 157 mHasReportMenuOption = mContactInfoHelper.canReportAsInvalid( 158 firstDetails.sourceType, firstDetails.objectId); 159 invalidateOptionsMenu(); 160 161 ListView historyList = (ListView) findViewById(R.id.history); 162 historyList.setAdapter( 163 new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details)); 164 165 String lookupKey = contactUri == null ? null 166 : UriUtils.getLookupKeyFromUri(contactUri); 167 168 final boolean isBusiness = mContactInfoHelper.isBusiness(firstDetails.sourceType); 169 170 final int contactType = 171 mIsVoicemailNumber ? ContactPhotoManager.TYPE_VOICEMAIL : 172 isBusiness ? ContactPhotoManager.TYPE_BUSINESS : 173 ContactPhotoManager.TYPE_DEFAULT; 174 175 String nameForDefaultImage; 176 if (TextUtils.isEmpty(firstDetails.name)) { 177 nameForDefaultImage = firstDetails.displayNumber; 178 } else { 179 nameForDefaultImage = firstDetails.name.toString(); 180 } 181 182 loadContactPhotos( 183 contactUri, photoUri, nameForDefaultImage, lookupKey, contactType); 184 findViewById(R.id.call_detail).setVisibility(View.VISIBLE); 185 } 186 187 /** 188 * Determines the location geocode text for a call, or the phone number type 189 * (if available). 190 * 191 * @param details The call details. 192 * @return The phone number type or location. 193 */ 194 private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) { 195 if (!TextUtils.isEmpty(details.name)) { 196 return Phone.getTypeLabel(mResources, details.numberType, 197 details.numberLabel); 198 } else { 199 return details.geocode; 200 } 201 } 202 }; 203 204 private Context mContext; 205 private CallTypeHelper mCallTypeHelper; 206 private QuickContactBadge mQuickContactBadge; 207 private TextView mCallerName; 208 private TextView mCallerNumber; 209 private TextView mAccountLabel; 210 private View mCallButton; 211 private ContactInfoHelper mContactInfoHelper; 212 213 protected String mNumber; 214 private boolean mIsVoicemailNumber; 215 private String mDefaultCountryIso; 216 217 /* package */ LayoutInflater mInflater; 218 /* package */ Resources mResources; 219 /** Helper to load contact photos. */ 220 private ContactPhotoManager mContactPhotoManager; 221 222 private Uri mVoicemailUri; 223 private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); 224 225 /** Whether we should show "edit number before call" in the options menu. */ 226 private boolean mHasEditNumberBeforeCallOption; 227 private boolean mHasReportMenuOption; 228 229 @Override 230 protected void onCreate(Bundle icicle) { 231 super.onCreate(icicle); 232 233 mContext = this; 234 235 setContentView(R.layout.call_detail); 236 237 mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 238 mResources = getResources(); 239 240 mCallTypeHelper = new CallTypeHelper(getResources()); 241 242 mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI); 243 244 mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo); 245 mQuickContactBadge.setOverlay(null); 246 mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); 247 mCallerName = (TextView) findViewById(R.id.caller_name); 248 mCallerNumber = (TextView) findViewById(R.id.caller_number); 249 mAccountLabel = (TextView) findViewById(R.id.phone_account_label); 250 mDefaultCountryIso = GeoUtil.getCurrentCountryIso(this); 251 mContactPhotoManager = ContactPhotoManager.getInstance(this); 252 253 mCallButton = (View) findViewById(R.id.call_back_button); 254 mCallButton.setOnClickListener(new View.OnClickListener() { 255 @Override 256 public void onClick(View view) { 257 mContext.startActivity(IntentUtil.getCallIntent(mNumber)); 258 } 259 }); 260 261 mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)); 262 getActionBar().setDisplayHomeAsUpEnabled(true); 263 264 if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) { 265 closeSystemDialogs(); 266 } 267 } 268 269 @Override 270 public void onResume() { 271 super.onResume(); 272 getCallDetails(); 273 } 274 275 public void getCallDetails() { 276 CallLogAsyncTaskUtil.getCallDetails(this, getCallLogEntryUris(), mCallLogAsyncTaskListener); 277 } 278 279 private boolean hasVoicemail() { 280 return mVoicemailUri != null; 281 } 282 283 /** 284 * Returns the list of URIs to show. 285 * <p> 286 * There are two ways the URIs can be provided to the activity: as the data on the intent, or as 287 * a list of ids in the call log added as an extra on the URI. 288 * <p> 289 * If both are available, the data on the intent takes precedence. 290 */ 291 private Uri[] getCallLogEntryUris() { 292 final Uri uri = getIntent().getData(); 293 if (uri != null) { 294 // If there is a data on the intent, it takes precedence over the extra. 295 return new Uri[]{ uri }; 296 } 297 final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS); 298 final int numIds = ids == null ? 0 : ids.length; 299 final Uri[] uris = new Uri[numIds]; 300 for (int index = 0; index < numIds; ++index) { 301 uris[index] = ContentUris.withAppendedId( 302 TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]); 303 } 304 return uris; 305 } 306 307 /** Load the contact photos and places them in the corresponding views. */ 308 private void loadContactPhotos(Uri contactUri, Uri photoUri, String displayName, 309 String lookupKey, int contactType) { 310 311 final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey, 312 contactType, true /* isCircular */); 313 314 mQuickContactBadge.assignContactUri(contactUri); 315 mQuickContactBadge.setContentDescription( 316 mResources.getString(R.string.description_contact_details, displayName)); 317 318 mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, photoUri, 319 false /* darkTheme */, true /* isCircular */, request); 320 } 321 322 @Override 323 public boolean onCreateOptionsMenu(Menu menu) { 324 getMenuInflater().inflate(R.menu.call_details_options, menu); 325 return super.onCreateOptionsMenu(menu); 326 } 327 328 @Override 329 public boolean onPrepareOptionsMenu(Menu menu) { 330 // This action deletes all elements in the group from the call log. 331 // We don't have this action for voicemails, because you can just use the trash button. 332 menu.findItem(R.id.menu_remove_from_call_log) 333 .setVisible(!hasVoicemail()) 334 .setOnMenuItemClickListener(this); 335 menu.findItem(R.id.menu_edit_number_before_call) 336 .setVisible(mHasEditNumberBeforeCallOption) 337 .setOnMenuItemClickListener(this); 338 menu.findItem(R.id.menu_trash) 339 .setVisible(hasVoicemail()) 340 .setOnMenuItemClickListener(this); 341 menu.findItem(R.id.menu_report) 342 .setVisible(mHasReportMenuOption) 343 .setOnMenuItemClickListener(this); 344 return super.onPrepareOptionsMenu(menu); 345 } 346 347 @Override 348 public boolean onMenuItemClick(MenuItem item) { 349 switch (item.getItemId()) { 350 case R.id.menu_remove_from_call_log: 351 final StringBuilder callIds = new StringBuilder(); 352 for (Uri callUri : getCallLogEntryUris()) { 353 if (callIds.length() != 0) { 354 callIds.append(","); 355 } 356 callIds.append(ContentUris.parseId(callUri)); 357 } 358 CallLogAsyncTaskUtil.deleteCalls( 359 this, callIds.toString(), mCallLogAsyncTaskListener); 360 break; 361 case R.id.menu_edit_number_before_call: 362 startActivity(new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(mNumber))); 363 break; 364 case R.id.menu_trash: 365 CallLogAsyncTaskUtil.deleteVoicemail( 366 this, mVoicemailUri, mCallLogAsyncTaskListener); 367 break; 368 } 369 return true; 370 } 371 372 private void closeSystemDialogs() { 373 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 374 } 375} 376