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.contacts; 18 19 import com.android.internal.telephony.CallerInfo; 20 21 import android.app.ListActivity; 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.Resources; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.provider.CallLog; 31 import android.provider.CallLog.Calls; 32 import android.provider.ContactsContract.Contacts; 33 import android.provider.ContactsContract.PhoneLookup; 34 import android.provider.ContactsContract.CommonDataKinds.Phone; 35 import android.provider.Contacts.Intents.Insert; 36 import android.telephony.PhoneNumberUtils; 37 import android.telephony.TelephonyManager; 38 import android.text.TextUtils; 39 import android.text.format.DateUtils; 40 import android.view.KeyEvent; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.AdapterView; 45 import android.widget.BaseAdapter; 46 import android.widget.ImageView; 47 import android.widget.TextView; 48 import android.widget.Toast; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 53 /** 54 * Displays the details of a specific call log entry. 55 */ 56 public class CallDetailActivity extends ListActivity implements 57 AdapterView.OnItemClickListener { 58 private static final String TAG = "CallDetail"; 59 60 private TextView mCallType; 61 private ImageView mCallTypeIcon; 62 private TextView mCallTime; 63 private TextView mCallDuration; 64 65 private String mNumber = null; 66 67 /* package */ LayoutInflater mInflater; 68 /* package */ Resources mResources; 69 70 static final String[] CALL_LOG_PROJECTION = new String[] { 71 CallLog.Calls.DATE, 72 CallLog.Calls.DURATION, 73 CallLog.Calls.NUMBER, 74 CallLog.Calls.TYPE, 75 }; 76 77 static final int DATE_COLUMN_INDEX = 0; 78 static final int DURATION_COLUMN_INDEX = 1; 79 static final int NUMBER_COLUMN_INDEX = 2; 80 static final int CALL_TYPE_COLUMN_INDEX = 3; 81 82 static final String[] PHONES_PROJECTION = new String[] { 83 PhoneLookup._ID, 84 PhoneLookup.DISPLAY_NAME, 85 PhoneLookup.TYPE, 86 PhoneLookup.LABEL, 87 PhoneLookup.NUMBER, 88 }; 89 static final int COLUMN_INDEX_ID = 0; 90 static final int COLUMN_INDEX_NAME = 1; 91 static final int COLUMN_INDEX_TYPE = 2; 92 static final int COLUMN_INDEX_LABEL = 3; 93 static final int COLUMN_INDEX_NUMBER = 4; 94 95 @Override onCreate(Bundle icicle)96 protected void onCreate(Bundle icicle) { 97 super.onCreate(icicle); 98 99 setContentView(R.layout.call_detail); 100 101 mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 102 mResources = getResources(); 103 104 mCallType = (TextView) findViewById(R.id.type); 105 mCallTypeIcon = (ImageView) findViewById(R.id.icon); 106 mCallTime = (TextView) findViewById(R.id.time); 107 mCallDuration = (TextView) findViewById(R.id.duration); 108 109 getListView().setOnItemClickListener(this); 110 } 111 112 @Override onResume()113 public void onResume() { 114 super.onResume(); 115 updateData(getIntent().getData()); 116 } 117 118 @Override onKeyDown(int keyCode, KeyEvent event)119 public boolean onKeyDown(int keyCode, KeyEvent event) { 120 switch (keyCode) { 121 case KeyEvent.KEYCODE_CALL: { 122 // Make sure phone isn't already busy before starting direct call 123 TelephonyManager tm = (TelephonyManager) 124 getSystemService(Context.TELEPHONY_SERVICE); 125 if (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) { 126 Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 127 Uri.fromParts("tel", mNumber, null)); 128 startActivity(callIntent); 129 return true; 130 } 131 } 132 } 133 134 return super.onKeyDown(keyCode, event); 135 } 136 137 /** 138 * Update user interface with details of given call. 139 * 140 * @param callUri Uri into {@link CallLog.Calls} 141 */ updateData(Uri callUri)142 private void updateData(Uri callUri) { 143 ContentResolver resolver = getContentResolver(); 144 Cursor callCursor = resolver.query(callUri, CALL_LOG_PROJECTION, null, null, null); 145 try { 146 if (callCursor != null && callCursor.moveToFirst()) { 147 // Read call log specifics 148 mNumber = callCursor.getString(NUMBER_COLUMN_INDEX); 149 long date = callCursor.getLong(DATE_COLUMN_INDEX); 150 long duration = callCursor.getLong(DURATION_COLUMN_INDEX); 151 int callType = callCursor.getInt(CALL_TYPE_COLUMN_INDEX); 152 153 // Pull out string in format [relative], [date] 154 CharSequence dateClause = DateUtils.formatDateRange(this, date, date, 155 DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | 156 DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR); 157 mCallTime.setText(dateClause); 158 159 // Set the duration 160 if (callType == Calls.MISSED_TYPE) { 161 mCallDuration.setVisibility(View.GONE); 162 } else { 163 mCallDuration.setVisibility(View.VISIBLE); 164 mCallDuration.setText(formatDuration(duration)); 165 } 166 167 // Set the call type icon and caption 168 String callText = null; 169 switch (callType) { 170 case Calls.INCOMING_TYPE: 171 mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_incoming_call); 172 mCallType.setText(R.string.type_incoming); 173 callText = getString(R.string.callBack); 174 break; 175 176 case Calls.OUTGOING_TYPE: 177 mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_outgoing_call); 178 mCallType.setText(R.string.type_outgoing); 179 callText = getString(R.string.callAgain); 180 break; 181 182 case Calls.MISSED_TYPE: 183 mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_missed_call); 184 mCallType.setText(R.string.type_missed); 185 callText = getString(R.string.returnCall); 186 break; 187 } 188 189 if (mNumber.equals(CallerInfo.UNKNOWN_NUMBER) || 190 mNumber.equals(CallerInfo.PRIVATE_NUMBER)) { 191 // List is empty, let the empty view show instead. 192 TextView emptyText = (TextView) findViewById(R.id.emptyText); 193 if (emptyText != null) { 194 emptyText.setText(mNumber.equals(CallerInfo.PRIVATE_NUMBER) 195 ? R.string.private_num : R.string.unknown); 196 } 197 } else { 198 // Perform a reverse-phonebook lookup to find the PERSON_ID 199 String callLabel = null; 200 Uri personUri = null; 201 Uri phoneUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, 202 Uri.encode(mNumber)); 203 Cursor phonesCursor = resolver.query(phoneUri, PHONES_PROJECTION, null, null, null); 204 try { 205 if (phonesCursor != null && phonesCursor.moveToFirst()) { 206 long personId = phonesCursor.getLong(COLUMN_INDEX_ID); 207 personUri = ContentUris.withAppendedId( 208 Contacts.CONTENT_URI, personId); 209 callText = getString(R.string.recentCalls_callNumber, 210 phonesCursor.getString(COLUMN_INDEX_NAME)); 211 mNumber = phonesCursor.getString(COLUMN_INDEX_NUMBER); 212 callLabel = Phone.getDisplayLabel(this, 213 phonesCursor.getInt(COLUMN_INDEX_TYPE), 214 phonesCursor.getString(COLUMN_INDEX_LABEL)).toString(); 215 } else { 216 mNumber = PhoneNumberUtils.formatNumber(mNumber); 217 } 218 } finally { 219 if (phonesCursor != null) phonesCursor.close(); 220 } 221 222 // Build list of various available actions 223 List<ViewEntry> actions = new ArrayList<ViewEntry>(); 224 225 Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 226 Uri.fromParts("tel", mNumber, null)); 227 ViewEntry entry = new ViewEntry(android.R.drawable.sym_action_call, callText, 228 callIntent); 229 entry.number = mNumber; 230 entry.label = callLabel; 231 actions.add(entry); 232 233 Intent smsIntent = new Intent(Intent.ACTION_SENDTO, 234 Uri.fromParts("sms", mNumber, null)); 235 actions.add(new ViewEntry(R.drawable.sym_action_sms, 236 getString(R.string.menu_sendTextMessage), smsIntent)); 237 238 // Let user view contact details if they exist, otherwise add option 239 // to create new contact from this number. 240 if (personUri != null) { 241 Intent viewIntent = new Intent(Intent.ACTION_VIEW, personUri); 242 actions.add(new ViewEntry(R.drawable.sym_action_view_contact, 243 getString(R.string.menu_viewContact), viewIntent)); 244 } else { 245 Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 246 createIntent.setType(Contacts.CONTENT_ITEM_TYPE); 247 createIntent.putExtra(Insert.PHONE, mNumber); 248 actions.add(new ViewEntry(R.drawable.sym_action_add, 249 getString(R.string.recentCalls_addToContact), createIntent)); 250 } 251 252 ViewAdapter adapter = new ViewAdapter(this, actions); 253 setListAdapter(adapter); 254 } 255 } else { 256 // Something went wrong reading in our primary data, so we're going to 257 // bail out and show error to users. 258 Toast.makeText(this, R.string.toast_call_detail_error, 259 Toast.LENGTH_SHORT).show(); 260 finish(); 261 } 262 } finally { 263 if (callCursor != null) { 264 callCursor.close(); 265 } 266 } 267 } 268 formatDuration(long elapsedSeconds)269 private String formatDuration(long elapsedSeconds) { 270 long minutes = 0; 271 long seconds = 0; 272 273 if (elapsedSeconds >= 60) { 274 minutes = elapsedSeconds / 60; 275 elapsedSeconds -= minutes * 60; 276 } 277 seconds = elapsedSeconds; 278 279 return getString(R.string.callDetailsDurationFormat, minutes, seconds); 280 } 281 282 static final class ViewEntry { 283 public int icon = -1; 284 public String text = null; 285 public Intent intent = null; 286 public String label = null; 287 public String number = null; 288 ViewEntry(int icon, String text, Intent intent)289 public ViewEntry(int icon, String text, Intent intent) { 290 this.icon = icon; 291 this.text = text; 292 this.intent = intent; 293 } 294 } 295 296 static final class ViewAdapter extends BaseAdapter { 297 298 private final List<ViewEntry> mActions; 299 300 private final LayoutInflater mInflater; 301 ViewAdapter(Context context, List<ViewEntry> actions)302 public ViewAdapter(Context context, List<ViewEntry> actions) { 303 mActions = actions; 304 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 305 } 306 getCount()307 public int getCount() { 308 return mActions.size(); 309 } 310 getItem(int position)311 public Object getItem(int position) { 312 return mActions.get(position); 313 } 314 getItemId(int position)315 public long getItemId(int position) { 316 return position; 317 } 318 getView(int position, View convertView, ViewGroup parent)319 public View getView(int position, View convertView, ViewGroup parent) { 320 // Make sure we have a valid convertView to start with 321 if (convertView == null) { 322 convertView = mInflater.inflate(R.layout.call_detail_list_item, parent, false); 323 } 324 325 // Fill action with icon and text. 326 ViewEntry entry = mActions.get(position); 327 convertView.setTag(entry); 328 329 ImageView icon = (ImageView) convertView.findViewById(R.id.icon); 330 TextView text = (TextView) convertView.findViewById(android.R.id.text1); 331 332 icon.setImageResource(entry.icon); 333 text.setText(entry.text); 334 335 View line2 = convertView.findViewById(R.id.line2); 336 boolean numberEmpty = TextUtils.isEmpty(entry.number); 337 boolean labelEmpty = TextUtils.isEmpty(entry.label) || numberEmpty; 338 if (labelEmpty && numberEmpty) { 339 line2.setVisibility(View.GONE); 340 } else { 341 line2.setVisibility(View.VISIBLE); 342 343 TextView label = (TextView) convertView.findViewById(R.id.label); 344 if (labelEmpty) { 345 label.setVisibility(View.GONE); 346 } else { 347 label.setText(entry.label); 348 label.setVisibility(View.VISIBLE); 349 } 350 351 TextView number = (TextView) convertView.findViewById(R.id.number); 352 number.setText(entry.number); 353 } 354 355 return convertView; 356 } 357 } 358 onItemClick(AdapterView parent, View view, int position, long id)359 public void onItemClick(AdapterView parent, View view, int position, long id) { 360 // Handle passing action off to correct handler. 361 if (view.getTag() instanceof ViewEntry) { 362 ViewEntry entry = (ViewEntry) view.getTag(); 363 if (entry.intent != null) { 364 startActivity(entry.intent); 365 } 366 } 367 } 368 } 369