1 /* 2 * Copyright (C) 2011 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.calllog; 18 19 import com.android.common.io.MoreCloseables; 20 import com.android.contacts.ContactsUtils; 21 import com.android.contacts.R; 22 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener; 23 import com.android.contacts.test.NeededForTesting; 24 import com.android.contacts.voicemail.VoicemailStatusHelper; 25 import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage; 26 import com.android.contacts.voicemail.VoicemailStatusHelperImpl; 27 import com.android.internal.telephony.CallerInfo; 28 import com.android.internal.telephony.ITelephony; 29 30 import android.app.Activity; 31 import android.app.KeyguardManager; 32 import android.app.ListFragment; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.database.Cursor; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.RemoteException; 39 import android.os.ServiceManager; 40 import android.provider.CallLog.Calls; 41 import android.telephony.PhoneNumberUtils; 42 import android.telephony.TelephonyManager; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.view.LayoutInflater; 46 import android.view.Menu; 47 import android.view.MenuInflater; 48 import android.view.MenuItem; 49 import android.view.View; 50 import android.view.ViewGroup; 51 import android.widget.ListView; 52 import android.widget.TextView; 53 54 import java.util.List; 55 56 /** 57 * Displays a list of call log entries. 58 */ 59 public class CallLogFragment extends ListFragment implements ViewPagerVisibilityListener, 60 CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher { 61 private static final String TAG = "CallLogFragment"; 62 63 private CallLogAdapter mAdapter; 64 private CallLogQueryHandler mCallLogQueryHandler; 65 private String mVoiceMailNumber; 66 private boolean mScrollToTop; 67 68 private boolean mShowOptionsMenu; 69 /** Whether there is at least one voicemail source installed. */ 70 private boolean mVoicemailSourcesAvailable = false; 71 /** Whether we are currently filtering over voicemail. */ 72 private boolean mShowingVoicemailOnly = false; 73 74 private VoicemailStatusHelper mVoicemailStatusHelper; 75 private View mStatusMessageView; 76 private TextView mStatusMessageText; 77 private TextView mStatusMessageAction; 78 private KeyguardManager mKeyguardManager; 79 80 @Override onCreate(Bundle state)81 public void onCreate(Bundle state) { 82 super.onCreate(state); 83 84 mVoiceMailNumber = ((TelephonyManager) getActivity().getSystemService( 85 Context.TELEPHONY_SERVICE)).getVoiceMailNumber(); 86 mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), this); 87 mKeyguardManager = 88 (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); 89 setHasOptionsMenu(true); 90 } 91 92 /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */ 93 @Override onCallsFetched(Cursor cursor)94 public void onCallsFetched(Cursor cursor) { 95 if (getActivity() == null || getActivity().isFinishing()) { 96 return; 97 } 98 mAdapter.setLoading(false); 99 mAdapter.changeCursor(cursor); 100 // This will update the state of the "Clear call log" menu item. 101 getActivity().invalidateOptionsMenu(); 102 if (mScrollToTop) { 103 final ListView listView = getListView(); 104 if (listView.getFirstVisiblePosition() > 5) { 105 listView.setSelection(5); 106 } 107 listView.smoothScrollToPosition(0); 108 mScrollToTop = false; 109 } 110 } 111 112 /** 113 * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider. 114 */ 115 @Override onVoicemailStatusFetched(Cursor statusCursor)116 public void onVoicemailStatusFetched(Cursor statusCursor) { 117 if (getActivity() == null || getActivity().isFinishing()) { 118 return; 119 } 120 updateVoicemailStatusMessage(statusCursor); 121 122 int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor); 123 setVoicemailSourcesAvailable(activeSources != 0); 124 MoreCloseables.closeQuietly(statusCursor); 125 } 126 127 /** Sets whether there are any voicemail sources available in the platform. */ setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable)128 private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) { 129 if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return; 130 mVoicemailSourcesAvailable = voicemailSourcesAvailable; 131 132 Activity activity = getActivity(); 133 if (activity != null) { 134 // This is so that the options menu content is updated. 135 activity.invalidateOptionsMenu(); 136 } 137 } 138 139 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)140 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { 141 View view = inflater.inflate(R.layout.call_log_fragment, container, false); 142 mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); 143 mStatusMessageView = view.findViewById(R.id.voicemail_status); 144 mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message); 145 mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action); 146 return view; 147 } 148 149 @Override onViewCreated(View view, Bundle savedInstanceState)150 public void onViewCreated(View view, Bundle savedInstanceState) { 151 super.onViewCreated(view, savedInstanceState); 152 String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity()); 153 mAdapter = new CallLogAdapter(getActivity(), this, 154 new ContactInfoHelper(getActivity(), currentCountryIso), mVoiceMailNumber); 155 setListAdapter(mAdapter); 156 getListView().setItemsCanFocus(true); 157 } 158 159 @Override onStart()160 public void onStart() { 161 mScrollToTop = true; 162 super.onStart(); 163 } 164 165 @Override onResume()166 public void onResume() { 167 super.onResume(); 168 refreshData(); 169 } 170 updateVoicemailStatusMessage(Cursor statusCursor)171 private void updateVoicemailStatusMessage(Cursor statusCursor) { 172 List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor); 173 if (messages.size() == 0) { 174 mStatusMessageView.setVisibility(View.GONE); 175 } else { 176 mStatusMessageView.setVisibility(View.VISIBLE); 177 // TODO: Change the code to show all messages. For now just pick the first message. 178 final StatusMessage message = messages.get(0); 179 if (message.showInCallLog()) { 180 mStatusMessageText.setText(message.callLogMessageId); 181 } 182 if (message.actionMessageId != -1) { 183 mStatusMessageAction.setText(message.actionMessageId); 184 } 185 if (message.actionUri != null) { 186 mStatusMessageAction.setVisibility(View.VISIBLE); 187 mStatusMessageAction.setOnClickListener(new View.OnClickListener() { 188 @Override 189 public void onClick(View v) { 190 getActivity().startActivity( 191 new Intent(Intent.ACTION_VIEW, message.actionUri)); 192 } 193 }); 194 } else { 195 mStatusMessageAction.setVisibility(View.GONE); 196 } 197 } 198 } 199 200 @Override onPause()201 public void onPause() { 202 super.onPause(); 203 // Kill the requests thread 204 mAdapter.stopRequestProcessing(); 205 } 206 207 @Override onStop()208 public void onStop() { 209 super.onStop(); 210 updateOnExit(); 211 } 212 213 @Override onDestroy()214 public void onDestroy() { 215 super.onDestroy(); 216 mAdapter.stopRequestProcessing(); 217 mAdapter.changeCursor(null); 218 } 219 220 @Override fetchCalls()221 public void fetchCalls() { 222 if (mShowingVoicemailOnly) { 223 mCallLogQueryHandler.fetchVoicemailOnly(); 224 } else { 225 mCallLogQueryHandler.fetchAllCalls(); 226 } 227 } 228 startCallsQuery()229 public void startCallsQuery() { 230 mAdapter.setLoading(true); 231 mCallLogQueryHandler.fetchAllCalls(); 232 if (mShowingVoicemailOnly) { 233 mShowingVoicemailOnly = false; 234 getActivity().invalidateOptionsMenu(); 235 } 236 } 237 startVoicemailStatusQuery()238 private void startVoicemailStatusQuery() { 239 mCallLogQueryHandler.fetchVoicemailStatus(); 240 } 241 242 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)243 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 244 super.onCreateOptionsMenu(menu, inflater); 245 if (mShowOptionsMenu) { 246 inflater.inflate(R.menu.call_log_options, menu); 247 } 248 } 249 250 @Override onPrepareOptionsMenu(Menu menu)251 public void onPrepareOptionsMenu(Menu menu) { 252 if (mShowOptionsMenu) { 253 menu.findItem(R.id.delete_all).setEnabled(mAdapter != null && !mAdapter.isEmpty()); 254 menu.findItem(R.id.show_voicemails_only).setVisible( 255 mVoicemailSourcesAvailable && !mShowingVoicemailOnly); 256 menu.findItem(R.id.show_all_calls).setVisible( 257 mVoicemailSourcesAvailable && mShowingVoicemailOnly); 258 } 259 } 260 261 @Override onOptionsItemSelected(MenuItem item)262 public boolean onOptionsItemSelected(MenuItem item) { 263 switch (item.getItemId()) { 264 case R.id.delete_all: 265 ClearCallLogDialog.show(getFragmentManager()); 266 return true; 267 268 case R.id.show_voicemails_only: 269 mCallLogQueryHandler.fetchVoicemailOnly(); 270 mShowingVoicemailOnly = true; 271 return true; 272 273 case R.id.show_all_calls: 274 mCallLogQueryHandler.fetchAllCalls(); 275 mShowingVoicemailOnly = false; 276 return true; 277 278 default: 279 return false; 280 } 281 } callSelectedEntry()282 public void callSelectedEntry() { 283 int position = getListView().getSelectedItemPosition(); 284 if (position < 0) { 285 // In touch mode you may often not have something selected, so 286 // just call the first entry to make sure that [send] [send] calls the 287 // most recent entry. 288 position = 0; 289 } 290 final Cursor cursor = (Cursor)mAdapter.getItem(position); 291 if (cursor != null) { 292 String number = cursor.getString(CallLogQuery.NUMBER); 293 if (TextUtils.isEmpty(number) 294 || number.equals(CallerInfo.UNKNOWN_NUMBER) 295 || number.equals(CallerInfo.PRIVATE_NUMBER) 296 || number.equals(CallerInfo.PAYPHONE_NUMBER)) { 297 // This number can't be called, do nothing 298 return; 299 } 300 Intent intent; 301 // If "number" is really a SIP address, construct a sip: URI. 302 if (PhoneNumberUtils.isUriNumber(number)) { 303 intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 304 Uri.fromParts("sip", number, null)); 305 } else { 306 // We're calling a regular PSTN phone number. 307 // Construct a tel: URI, but do some other possible cleanup first. 308 int callType = cursor.getInt(CallLogQuery.CALL_TYPE); 309 if (!number.startsWith("+") && 310 (callType == Calls.INCOMING_TYPE 311 || callType == Calls.MISSED_TYPE)) { 312 // If the caller-id matches a contact with a better qualified number, use it 313 number = mAdapter.getBetterNumberFromContacts(number); 314 } 315 intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 316 Uri.fromParts("tel", number, null)); 317 } 318 intent.setFlags( 319 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 320 startActivity(intent); 321 } 322 } 323 324 @NeededForTesting getAdapter()325 public CallLogAdapter getAdapter() { 326 return mAdapter; 327 } 328 329 @NeededForTesting getVoiceMailNumber()330 public String getVoiceMailNumber() { 331 return mVoiceMailNumber; 332 } 333 334 @Override onVisibilityChanged(boolean visible)335 public void onVisibilityChanged(boolean visible) { 336 if (mShowOptionsMenu != visible) { 337 mShowOptionsMenu = visible; 338 // Invalidate the options menu since we are changing the list of options shown in it. 339 Activity activity = getActivity(); 340 if (activity != null) { 341 activity.invalidateOptionsMenu(); 342 } 343 } 344 345 if (visible && isResumed()) { 346 refreshData(); 347 } 348 349 if (!visible) { 350 updateOnExit(); 351 } 352 } 353 354 /** Requests updates to the data to be shown. */ refreshData()355 private void refreshData() { 356 // Mark all entries in the contact info cache as out of date, so they will be looked up 357 // again once being shown. 358 mAdapter.invalidateCache(); 359 startCallsQuery(); 360 startVoicemailStatusQuery(); 361 updateOnEntry(); 362 } 363 364 /** Removes the missed call notifications. */ removeMissedCallNotifications()365 private void removeMissedCallNotifications() { 366 try { 367 ITelephony telephony = 368 ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 369 if (telephony != null) { 370 telephony.cancelMissedCallsNotification(); 371 } else { 372 Log.w(TAG, "Telephony service is null, can't call " + 373 "cancelMissedCallsNotification"); 374 } 375 } catch (RemoteException e) { 376 Log.e(TAG, "Failed to clear missed calls notification due to remote exception"); 377 } 378 } 379 380 /** Updates call data and notification state while leaving the call log tab. */ updateOnExit()381 private void updateOnExit() { 382 updateOnTransition(false); 383 } 384 385 /** Updates call data and notification state while entering the call log tab. */ updateOnEntry()386 private void updateOnEntry() { 387 updateOnTransition(true); 388 } 389 updateOnTransition(boolean onEntry)390 private void updateOnTransition(boolean onEntry) { 391 // We don't want to update any call data when keyguard is on because the user has likely not 392 // seen the new calls yet. 393 if (!mKeyguardManager.inKeyguardRestrictedInputMode()) { 394 // On either of the transitions we reset the new flag and update the notifications. 395 // While exiting we additionally consume all missed calls (by marking them as read). 396 // This will ensure that they no more appear in the "new" section when we return back. 397 mCallLogQueryHandler.markNewCallsAsOld(); 398 if (!onEntry) { 399 mCallLogQueryHandler.markMissedCallsAsRead(); 400 } 401 removeMissedCallNotifications(); 402 updateVoicemailNotifications(); 403 } 404 } 405 updateVoicemailNotifications()406 private void updateVoicemailNotifications() { 407 Intent serviceIntent = new Intent(getActivity(), CallLogNotificationsService.class); 408 serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS); 409 getActivity().startService(serviceIntent); 410 } 411 } 412