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 StickyTabs.saveTab(this, getIntent()); 130 return true; 131 } 132 } 133 } 134 135 return super.onKeyDown(keyCode, event); 136 } 137 138 /** 139 * Update user interface with details of given call. 140 * 141 * @param callUri Uri into {@link CallLog.Calls} 142 */ updateData(Uri callUri)143 private void updateData(Uri callUri) { 144 ContentResolver resolver = getContentResolver(); 145 Cursor callCursor = resolver.query(callUri, CALL_LOG_PROJECTION, null, null, null); 146 try { 147 if (callCursor != null && callCursor.moveToFirst()) { 148 // Read call log specifics 149 mNumber = callCursor.getString(NUMBER_COLUMN_INDEX); 150 long date = callCursor.getLong(DATE_COLUMN_INDEX); 151 long duration = callCursor.getLong(DURATION_COLUMN_INDEX); 152 int callType = callCursor.getInt(CALL_TYPE_COLUMN_INDEX); 153 154 // Pull out string in format [relative], [date] 155 CharSequence dateClause = DateUtils.formatDateRange(this, date, date, 156 DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | 157 DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR); 158 mCallTime.setText(dateClause); 159 160 // Set the duration 161 if (callType == Calls.MISSED_TYPE) { 162 mCallDuration.setVisibility(View.GONE); 163 } else { 164 mCallDuration.setVisibility(View.VISIBLE); 165 mCallDuration.setText(formatDuration(duration)); 166 } 167 168 // Set the call type icon and caption 169 String callText = null; 170 switch (callType) { 171 case Calls.INCOMING_TYPE: 172 mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_incoming_call); 173 mCallType.setText(R.string.type_incoming); 174 callText = getString(R.string.callBack); 175 break; 176 177 case Calls.OUTGOING_TYPE: 178 mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_outgoing_call); 179 mCallType.setText(R.string.type_outgoing); 180 callText = getString(R.string.callAgain); 181 break; 182 183 case Calls.MISSED_TYPE: 184 mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_missed_call); 185 mCallType.setText(R.string.type_missed); 186 callText = getString(R.string.returnCall); 187 break; 188 } 189 190 if (mNumber.equals(CallerInfo.UNKNOWN_NUMBER) || 191 mNumber.equals(CallerInfo.PRIVATE_NUMBER)) { 192 // List is empty, let the empty view show instead. 193 TextView emptyText = (TextView) findViewById(R.id.emptyText); 194 if (emptyText != null) { 195 emptyText.setText(mNumber.equals(CallerInfo.PRIVATE_NUMBER) 196 ? R.string.private_num : R.string.unknown); 197 } 198 } else { 199 // Perform a reverse-phonebook lookup to find the PERSON_ID 200 String callLabel = null; 201 Uri personUri = null; 202 Uri phoneUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, 203 Uri.encode(mNumber)); 204 Cursor phonesCursor = resolver.query(phoneUri, PHONES_PROJECTION, null, null, null); 205 try { 206 if (phonesCursor != null && phonesCursor.moveToFirst()) { 207 long personId = phonesCursor.getLong(COLUMN_INDEX_ID); 208 personUri = ContentUris.withAppendedId( 209 Contacts.CONTENT_URI, personId); 210 callText = getString(R.string.recentCalls_callNumber, 211 phonesCursor.getString(COLUMN_INDEX_NAME)); 212 mNumber = PhoneNumberUtils.formatNumber( 213 phonesCursor.getString(COLUMN_INDEX_NUMBER)); 214 callLabel = Phone.getDisplayLabel(this, 215 phonesCursor.getInt(COLUMN_INDEX_TYPE), 216 phonesCursor.getString(COLUMN_INDEX_LABEL)).toString(); 217 } else { 218 mNumber = PhoneNumberUtils.formatNumber(mNumber); 219 } 220 } finally { 221 if (phonesCursor != null) phonesCursor.close(); 222 } 223 224 // Build list of various available actions 225 List<ViewEntry> actions = new ArrayList<ViewEntry>(); 226 227 Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 228 Uri.fromParts("tel", mNumber, null)); 229 ViewEntry entry = new ViewEntry(android.R.drawable.sym_action_call, callText, 230 callIntent); 231 entry.number = mNumber; 232 entry.label = callLabel; 233 actions.add(entry); 234 235 Intent smsIntent = new Intent(Intent.ACTION_SENDTO, 236 Uri.fromParts("sms", mNumber, null)); 237 actions.add(new ViewEntry(R.drawable.sym_action_sms, 238 getString(R.string.menu_sendTextMessage), smsIntent)); 239 240 // Let user view contact details if they exist, otherwise add option 241 // to create new contact from this number. 242 if (personUri != null) { 243 Intent viewIntent = new Intent(Intent.ACTION_VIEW, personUri); 244 StickyTabs.setTab(viewIntent, getIntent()); 245 actions.add(new ViewEntry(R.drawable.sym_action_view_contact, 246 getString(R.string.menu_viewContact), viewIntent)); 247 } else { 248 Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 249 createIntent.setType(Contacts.CONTENT_ITEM_TYPE); 250 createIntent.putExtra(Insert.PHONE, mNumber); 251 actions.add(new ViewEntry(R.drawable.sym_action_add, 252 getString(R.string.recentCalls_addToContact), createIntent)); 253 } 254 255 ViewAdapter adapter = new ViewAdapter(this, actions); 256 setListAdapter(adapter); 257 } 258 } else { 259 // Something went wrong reading in our primary data, so we're going to 260 // bail out and show error to users. 261 Toast.makeText(this, R.string.toast_call_detail_error, 262 Toast.LENGTH_SHORT).show(); 263 finish(); 264 } 265 } finally { 266 if (callCursor != null) { 267 callCursor.close(); 268 } 269 } 270 } 271 formatDuration(long elapsedSeconds)272 private String formatDuration(long elapsedSeconds) { 273 long minutes = 0; 274 long seconds = 0; 275 276 if (elapsedSeconds >= 60) { 277 minutes = elapsedSeconds / 60; 278 elapsedSeconds -= minutes * 60; 279 } 280 seconds = elapsedSeconds; 281 282 return getString(R.string.callDetailsDurationFormat, minutes, seconds); 283 } 284 285 static final class ViewEntry { 286 public int icon = -1; 287 public String text = null; 288 public Intent intent = null; 289 public String label = null; 290 public String number = null; 291 ViewEntry(int icon, String text, Intent intent)292 public ViewEntry(int icon, String text, Intent intent) { 293 this.icon = icon; 294 this.text = text; 295 this.intent = intent; 296 } 297 } 298 299 static final class ViewAdapter extends BaseAdapter { 300 301 private final List<ViewEntry> mActions; 302 303 private final LayoutInflater mInflater; 304 ViewAdapter(Context context, List<ViewEntry> actions)305 public ViewAdapter(Context context, List<ViewEntry> actions) { 306 mActions = actions; 307 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 308 } 309 getCount()310 public int getCount() { 311 return mActions.size(); 312 } 313 getItem(int position)314 public Object getItem(int position) { 315 return mActions.get(position); 316 } 317 getItemId(int position)318 public long getItemId(int position) { 319 return position; 320 } 321 getView(int position, View convertView, ViewGroup parent)322 public View getView(int position, View convertView, ViewGroup parent) { 323 // Make sure we have a valid convertView to start with 324 if (convertView == null) { 325 convertView = mInflater.inflate(R.layout.call_detail_list_item, parent, false); 326 } 327 328 // Fill action with icon and text. 329 ViewEntry entry = mActions.get(position); 330 convertView.setTag(entry); 331 332 ImageView icon = (ImageView) convertView.findViewById(R.id.icon); 333 TextView text = (TextView) convertView.findViewById(android.R.id.text1); 334 335 icon.setImageResource(entry.icon); 336 text.setText(entry.text); 337 338 View line2 = convertView.findViewById(R.id.line2); 339 boolean numberEmpty = TextUtils.isEmpty(entry.number); 340 boolean labelEmpty = TextUtils.isEmpty(entry.label) || numberEmpty; 341 if (labelEmpty && numberEmpty) { 342 line2.setVisibility(View.GONE); 343 } else { 344 line2.setVisibility(View.VISIBLE); 345 346 TextView label = (TextView) convertView.findViewById(R.id.label); 347 if (labelEmpty) { 348 label.setVisibility(View.GONE); 349 } else { 350 label.setText(entry.label); 351 label.setVisibility(View.VISIBLE); 352 } 353 354 TextView number = (TextView) convertView.findViewById(R.id.number); 355 number.setText(entry.number); 356 } 357 358 return convertView; 359 } 360 } 361 onItemClick(AdapterView parent, View view, int position, long id)362 public void onItemClick(AdapterView parent, View view, int position, long id) { 363 // Handle passing action off to correct handler. 364 if (view.getTag() instanceof ViewEntry) { 365 ViewEntry entry = (ViewEntry) view.getTag(); 366 if (entry.intent != null) { 367 if (Intent.ACTION_CALL_PRIVILEGED.equals(entry.intent.getAction())) { 368 StickyTabs.saveTab(this, getIntent()); 369 } 370 startActivity(entry.intent); 371 } 372 } 373 } 374 375 @Override startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)376 public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, 377 boolean globalSearch) { 378 if (globalSearch) { 379 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); 380 } else { 381 ContactsSearchManager.startSearch(this, initialQuery); 382 } 383 } 384 } 385