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