PageRenderTime 106ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/android/contacts/calllog/CallLogFragment.java

https://bitbucket.org/ProuDroid/packages_apps_contacts
Java | 615 lines | 464 code | 64 blank | 87 comment | 74 complexity | f4d5aa29c0a91dc6461ad9c21d8e94fd MD5 | raw file
  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. package com.android.contacts.calllog;
  17. import android.app.Activity;
  18. import android.app.KeyguardManager;
  19. import android.app.ListFragment;
  20. import android.content.Context;
  21. import android.content.Intent;
  22. import android.database.ContentObserver;
  23. import android.database.Cursor;
  24. import android.net.Uri;
  25. import android.os.Bundle;
  26. import android.os.Handler;
  27. import android.os.RemoteException;
  28. import android.os.ServiceManager;
  29. import android.provider.CallLog;
  30. import android.provider.CallLog.Calls;
  31. import android.provider.ContactsContract;
  32. import android.telephony.PhoneNumberUtils;
  33. import android.telephony.PhoneStateListener;
  34. import android.telephony.TelephonyManager;
  35. import android.text.TextUtils;
  36. import android.util.Log;
  37. import android.view.LayoutInflater;
  38. import android.view.Menu;
  39. import android.view.MenuInflater;
  40. import android.view.MenuItem;
  41. import android.view.View;
  42. import android.view.ViewGroup;
  43. import android.widget.ListView;
  44. import android.widget.TextView;
  45. import com.android.common.io.MoreCloseables;
  46. import com.android.contacts.ContactsUtils;
  47. import com.android.contacts.R;
  48. import com.android.contacts.util.Constants;
  49. import com.android.contacts.util.EmptyLoader;
  50. import com.android.contacts.voicemail.VoicemailStatusHelper;
  51. import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
  52. import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
  53. import com.android.internal.telephony.CallerInfo;
  54. import com.android.internal.telephony.ITelephony;
  55. import com.google.common.annotations.VisibleForTesting;
  56. import java.util.List;
  57. /**
  58. * Displays a list of call log entries.
  59. */
  60. public class CallLogFragment extends ListFragment
  61. implements CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher {
  62. private static final String TAG = "CallLogFragment";
  63. /**
  64. * ID of the empty loader to defer other fragments.
  65. */
  66. private static final int EMPTY_LOADER_ID = 0;
  67. private CallLogAdapter mAdapter;
  68. private CallLogQueryHandler mCallLogQueryHandler;
  69. private boolean mScrollToTop;
  70. /** Whether there is at least one voicemail source installed. */
  71. private boolean mVoicemailSourcesAvailable = false;
  72. private VoicemailStatusHelper mVoicemailStatusHelper;
  73. private View mStatusMessageView;
  74. private TextView mStatusMessageText;
  75. private TextView mStatusMessageAction;
  76. private TextView mFilterStatusView;
  77. private KeyguardManager mKeyguardManager;
  78. private boolean mEmptyLoaderRunning;
  79. private boolean mCallLogFetched;
  80. private boolean mVoicemailStatusFetched;
  81. private final Handler mHandler = new Handler();
  82. private TelephonyManager mTelephonyManager;
  83. private PhoneStateListener mPhoneStateListener;
  84. private class CustomContentObserver extends ContentObserver {
  85. public CustomContentObserver() {
  86. super(mHandler);
  87. }
  88. @Override
  89. public void onChange(boolean selfChange) {
  90. mRefreshDataRequired = true;
  91. }
  92. }
  93. // See issue 6363009
  94. private final ContentObserver mCallLogObserver = new CustomContentObserver();
  95. private final ContentObserver mContactsObserver = new CustomContentObserver();
  96. private boolean mRefreshDataRequired = true;
  97. // Exactly same variable is in Fragment as a package private.
  98. private boolean mMenuVisible = true;
  99. // Default to all calls.
  100. private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
  101. @Override
  102. public void onCreate(Bundle state) {
  103. super.onCreate(state);
  104. mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), this);
  105. mKeyguardManager =
  106. (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
  107. getActivity().getContentResolver().registerContentObserver(
  108. CallLog.CONTENT_URI, true, mCallLogObserver);
  109. getActivity().getContentResolver().registerContentObserver(
  110. ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
  111. setHasOptionsMenu(true);
  112. }
  113. /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
  114. @Override
  115. public void onCallsFetched(Cursor cursor) {
  116. if (getActivity() == null || getActivity().isFinishing()) {
  117. return;
  118. }
  119. mAdapter.setLoading(false);
  120. mAdapter.changeCursor(cursor);
  121. // This will update the state of the "Clear call log" menu item.
  122. getActivity().invalidateOptionsMenu();
  123. if (mScrollToTop) {
  124. final ListView listView = getListView();
  125. // The smooth-scroll animation happens over a fixed time period.
  126. // As a result, if it scrolls through a large portion of the list,
  127. // each frame will jump so far from the previous one that the user
  128. // will not experience the illusion of downward motion. Instead,
  129. // if we're not already near the top of the list, we instantly jump
  130. // near the top, and animate from there.
  131. if (listView.getFirstVisiblePosition() > 5) {
  132. listView.setSelection(5);
  133. }
  134. // Workaround for framework issue: the smooth-scroll doesn't
  135. // occur if setSelection() is called immediately before.
  136. mHandler.post(new Runnable() {
  137. @Override
  138. public void run() {
  139. if (getActivity() == null || getActivity().isFinishing()) {
  140. return;
  141. }
  142. listView.smoothScrollToPosition(0);
  143. }
  144. });
  145. mScrollToTop = false;
  146. }
  147. mCallLogFetched = true;
  148. destroyEmptyLoaderIfAllDataFetched();
  149. }
  150. /**
  151. * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider.
  152. */
  153. @Override
  154. public void onVoicemailStatusFetched(Cursor statusCursor) {
  155. if (getActivity() == null || getActivity().isFinishing()) {
  156. return;
  157. }
  158. updateVoicemailStatusMessage(statusCursor);
  159. int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
  160. setVoicemailSourcesAvailable(activeSources != 0);
  161. MoreCloseables.closeQuietly(statusCursor);
  162. mVoicemailStatusFetched = true;
  163. destroyEmptyLoaderIfAllDataFetched();
  164. }
  165. private void destroyEmptyLoaderIfAllDataFetched() {
  166. if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
  167. mEmptyLoaderRunning = false;
  168. getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
  169. }
  170. }
  171. /** Sets whether there are any voicemail sources available in the platform. */
  172. private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) {
  173. if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return;
  174. mVoicemailSourcesAvailable = voicemailSourcesAvailable;
  175. Activity activity = getActivity();
  176. if (activity != null) {
  177. // This is so that the options menu content is updated.
  178. activity.invalidateOptionsMenu();
  179. }
  180. }
  181. @Override
  182. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
  183. View view = inflater.inflate(R.layout.call_log_fragment, container, false);
  184. mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
  185. mStatusMessageView = view.findViewById(R.id.voicemail_status);
  186. mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message);
  187. mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action);
  188. mFilterStatusView = (TextView) view.findViewById(R.id.filter_status);
  189. return view;
  190. }
  191. @Override
  192. public void onViewCreated(View view, Bundle savedInstanceState) {
  193. super.onViewCreated(view, savedInstanceState);
  194. String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
  195. mAdapter = new CallLogAdapter(getActivity(), this,
  196. new ContactInfoHelper(getActivity(), currentCountryIso));
  197. setListAdapter(mAdapter);
  198. getListView().setItemsCanFocus(true);
  199. }
  200. /**
  201. * Based on the new intent, decide whether the list should be configured
  202. * to scroll up to display the first item.
  203. */
  204. public void configureScreenFromIntent(Intent newIntent) {
  205. // Typically, when switching to the call-log we want to show the user
  206. // the same section of the list that they were most recently looking
  207. // at. However, under some circumstances, we want to automatically
  208. // scroll to the top of the list to present the newest call items.
  209. // For example, immediately after a call is finished, we want to
  210. // display information about that call.
  211. mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType());
  212. }
  213. @Override
  214. public void onStart() {
  215. // Start the empty loader now to defer other fragments. We destroy it when both calllog
  216. // and the voicemail status are fetched.
  217. getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
  218. new EmptyLoader.Callback(getActivity()));
  219. mEmptyLoaderRunning = true;
  220. super.onStart();
  221. }
  222. @Override
  223. public void onResume() {
  224. super.onResume();
  225. refreshData();
  226. }
  227. private void updateVoicemailStatusMessage(Cursor statusCursor) {
  228. List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
  229. if (messages.size() == 0) {
  230. mStatusMessageView.setVisibility(View.GONE);
  231. } else {
  232. mStatusMessageView.setVisibility(View.VISIBLE);
  233. // TODO: Change the code to show all messages. For now just pick the first message.
  234. final StatusMessage message = messages.get(0);
  235. if (message.showInCallLog()) {
  236. mStatusMessageText.setText(message.callLogMessageId);
  237. }
  238. if (message.actionMessageId != -1) {
  239. mStatusMessageAction.setText(message.actionMessageId);
  240. }
  241. if (message.actionUri != null) {
  242. mStatusMessageAction.setVisibility(View.VISIBLE);
  243. mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
  244. @Override
  245. public void onClick(View v) {
  246. getActivity().startActivity(
  247. new Intent(Intent.ACTION_VIEW, message.actionUri));
  248. }
  249. });
  250. } else {
  251. mStatusMessageAction.setVisibility(View.GONE);
  252. }
  253. }
  254. }
  255. @Override
  256. public void onPause() {
  257. super.onPause();
  258. // Kill the requests thread
  259. mAdapter.stopRequestProcessing();
  260. }
  261. @Override
  262. public void onStop() {
  263. super.onStop();
  264. updateOnExit();
  265. }
  266. @Override
  267. public void onDestroy() {
  268. super.onDestroy();
  269. mAdapter.stopRequestProcessing();
  270. mAdapter.changeCursor(null);
  271. getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
  272. getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
  273. unregisterPhoneCallReceiver();
  274. }
  275. @Override
  276. public void fetchCalls() {
  277. mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
  278. }
  279. public void startCallsQuery() {
  280. mAdapter.setLoading(true);
  281. mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
  282. }
  283. private void startVoicemailStatusQuery() {
  284. mCallLogQueryHandler.fetchVoicemailStatus();
  285. }
  286. @Override
  287. public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
  288. super.onCreateOptionsMenu(menu, inflater);
  289. inflater.inflate(R.menu.call_log_options, menu);
  290. }
  291. @Override
  292. public void onPrepareOptionsMenu(Menu menu) {
  293. final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all);
  294. // Check if all the menu items are inflated correctly. As a shortcut, we assume all
  295. // menu items are ready if the first item is non-null.
  296. if (itemDeleteAll != null) {
  297. itemDeleteAll.setEnabled(mAdapter != null && !mAdapter.isEmpty());
  298. showAllFilterMenuOptions(menu);
  299. hideCurrentFilterMenuOption(menu);
  300. // Only hide if not available. Let the above calls handle showing.
  301. if (!mVoicemailSourcesAvailable) {
  302. menu.findItem(R.id.show_voicemails_only).setVisible(false);
  303. }
  304. }
  305. }
  306. private void hideCurrentFilterMenuOption(Menu menu) {
  307. MenuItem item = null;
  308. switch (mCallTypeFilter) {
  309. case CallLogQueryHandler.CALL_TYPE_ALL:
  310. item = menu.findItem(R.id.show_all_calls);
  311. break;
  312. case Calls.INCOMING_TYPE:
  313. item = menu.findItem(R.id.show_incoming_only);
  314. break;
  315. case Calls.OUTGOING_TYPE:
  316. item = menu.findItem(R.id.show_outgoing_only);
  317. break;
  318. case Calls.MISSED_TYPE:
  319. item = menu.findItem(R.id.show_missed_only);
  320. break;
  321. case Calls.VOICEMAIL_TYPE:
  322. menu.findItem(R.id.show_voicemails_only);
  323. break;
  324. }
  325. if (item != null) {
  326. item.setVisible(false);
  327. }
  328. }
  329. private void showAllFilterMenuOptions(Menu menu) {
  330. menu.findItem(R.id.show_all_calls).setVisible(true);
  331. menu.findItem(R.id.show_incoming_only).setVisible(true);
  332. menu.findItem(R.id.show_outgoing_only).setVisible(true);
  333. menu.findItem(R.id.show_missed_only).setVisible(true);
  334. menu.findItem(R.id.show_voicemails_only).setVisible(true);
  335. }
  336. @Override
  337. public boolean onOptionsItemSelected(MenuItem item) {
  338. switch (item.getItemId()) {
  339. case R.id.delete_all:
  340. ClearCallLogDialog.show(getFragmentManager());
  341. return true;
  342. case R.id.show_outgoing_only:
  343. // We only need the phone call receiver when there is an active call type filter.
  344. // Not many people may use the filters so don't register the receiver until now .
  345. registerPhoneCallReceiver();
  346. mCallLogQueryHandler.fetchCalls(Calls.OUTGOING_TYPE);
  347. updateFilterTypeAndHeader(Calls.OUTGOING_TYPE);
  348. return true;
  349. case R.id.show_incoming_only:
  350. registerPhoneCallReceiver();
  351. mCallLogQueryHandler.fetchCalls(Calls.INCOMING_TYPE);
  352. updateFilterTypeAndHeader(Calls.INCOMING_TYPE);
  353. return true;
  354. case R.id.show_missed_only:
  355. registerPhoneCallReceiver();
  356. mCallLogQueryHandler.fetchCalls(Calls.MISSED_TYPE);
  357. updateFilterTypeAndHeader(Calls.MISSED_TYPE);
  358. return true;
  359. case R.id.show_voicemails_only:
  360. registerPhoneCallReceiver();
  361. mCallLogQueryHandler.fetchCalls(Calls.VOICEMAIL_TYPE);
  362. updateFilterTypeAndHeader(Calls.VOICEMAIL_TYPE);
  363. return true;
  364. case R.id.show_all_calls:
  365. // Filter is being turned off, receiver no longer needed.
  366. unregisterPhoneCallReceiver();
  367. mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
  368. updateFilterTypeAndHeader(CallLogQueryHandler.CALL_TYPE_ALL);
  369. return true;
  370. default:
  371. return false;
  372. }
  373. }
  374. private void updateFilterTypeAndHeader(int filterType) {
  375. mCallTypeFilter = filterType;
  376. switch (filterType) {
  377. case CallLogQueryHandler.CALL_TYPE_ALL:
  378. mFilterStatusView.setVisibility(View.GONE);
  379. break;
  380. case Calls.INCOMING_TYPE:
  381. showFilterStatus(R.string.call_log_incoming_header);
  382. break;
  383. case Calls.OUTGOING_TYPE:
  384. showFilterStatus(R.string.call_log_outgoing_header);
  385. break;
  386. case Calls.MISSED_TYPE:
  387. showFilterStatus(R.string.call_log_missed_header);
  388. break;
  389. case Calls.VOICEMAIL_TYPE:
  390. showFilterStatus(R.string.call_log_voicemail_header);
  391. break;
  392. }
  393. }
  394. private void showFilterStatus(int resId) {
  395. mFilterStatusView.setText(resId);
  396. mFilterStatusView.setVisibility(View.VISIBLE);
  397. }
  398. public void callSelectedEntry() {
  399. int position = getListView().getSelectedItemPosition();
  400. if (position < 0) {
  401. // In touch mode you may often not have something selected, so
  402. // just call the first entry to make sure that [send] [send] calls the
  403. // most recent entry.
  404. position = 0;
  405. }
  406. final Cursor cursor = (Cursor)mAdapter.getItem(position);
  407. if (cursor != null) {
  408. String number = cursor.getString(CallLogQuery.NUMBER);
  409. if (TextUtils.isEmpty(number)
  410. || number.equals(CallerInfo.UNKNOWN_NUMBER)
  411. || number.equals(CallerInfo.PRIVATE_NUMBER)
  412. || number.equals(CallerInfo.PAYPHONE_NUMBER)) {
  413. // This number can't be called, do nothing
  414. return;
  415. }
  416. Intent intent;
  417. // If "number" is really a SIP address, construct a sip: URI.
  418. if (PhoneNumberUtils.isUriNumber(number)) {
  419. intent = ContactsUtils.getCallIntent(
  420. Uri.fromParts(Constants.SCHEME_SIP, number, null));
  421. } else {
  422. // We're calling a regular PSTN phone number.
  423. // Construct a tel: URI, but do some other possible cleanup first.
  424. int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
  425. if (!number.startsWith("+") &&
  426. (callType == Calls.INCOMING_TYPE
  427. || callType == Calls.MISSED_TYPE)) {
  428. // If the caller-id matches a contact with a better qualified number, use it
  429. String countryIso = cursor.getString(CallLogQuery.COUNTRY_ISO);
  430. number = mAdapter.getBetterNumberFromContacts(number, countryIso);
  431. }
  432. intent = ContactsUtils.getCallIntent(
  433. Uri.fromParts(Constants.SCHEME_TEL, number, null));
  434. }
  435. intent.setFlags(
  436. Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
  437. startActivity(intent);
  438. }
  439. }
  440. @VisibleForTesting
  441. CallLogAdapter getAdapter() {
  442. return mAdapter;
  443. }
  444. @Override
  445. public void setMenuVisibility(boolean menuVisible) {
  446. super.setMenuVisibility(menuVisible);
  447. if (mMenuVisible != menuVisible) {
  448. mMenuVisible = menuVisible;
  449. if (!menuVisible) {
  450. updateOnExit();
  451. } else if (isResumed()) {
  452. refreshData();
  453. }
  454. }
  455. }
  456. /** Requests updates to the data to be shown. */
  457. private void refreshData() {
  458. // Prevent unnecessary refresh.
  459. if (mRefreshDataRequired) {
  460. // Mark all entries in the contact info cache as out of date, so they will be looked up
  461. // again once being shown.
  462. mAdapter.invalidateCache();
  463. startCallsQuery();
  464. startVoicemailStatusQuery();
  465. updateOnEntry();
  466. mRefreshDataRequired = false;
  467. }
  468. }
  469. /** Removes the missed call notifications. */
  470. private void removeMissedCallNotifications() {
  471. try {
  472. ITelephony telephony =
  473. ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
  474. if (telephony != null) {
  475. telephony.cancelMissedCallsNotification();
  476. } else {
  477. Log.w(TAG, "Telephony service is null, can't call " +
  478. "cancelMissedCallsNotification");
  479. }
  480. } catch (RemoteException e) {
  481. Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
  482. }
  483. }
  484. /** Updates call data and notification state while leaving the call log tab. */
  485. private void updateOnExit() {
  486. updateOnTransition(false);
  487. }
  488. /** Updates call data and notification state while entering the call log tab. */
  489. private void updateOnEntry() {
  490. updateOnTransition(true);
  491. }
  492. private void updateOnTransition(boolean onEntry) {
  493. // We don't want to update any call data when keyguard is on because the user has likely not
  494. // seen the new calls yet.
  495. // This might be called before onCreate() and thus we need to check null explicitly.
  496. if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
  497. // On either of the transitions we reset the new flag and update the notifications.
  498. // While exiting we additionally consume all missed calls (by marking them as read).
  499. // This will ensure that they no more appear in the "new" section when we return back.
  500. mCallLogQueryHandler.markNewCallsAsOld();
  501. if (!onEntry) {
  502. mCallLogQueryHandler.markMissedCallsAsRead();
  503. }
  504. removeMissedCallNotifications();
  505. updateVoicemailNotifications();
  506. }
  507. }
  508. private void updateVoicemailNotifications() {
  509. Intent serviceIntent = new Intent(getActivity(), CallLogNotificationsService.class);
  510. serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
  511. getActivity().startService(serviceIntent);
  512. }
  513. /**
  514. * Register a phone call filter to reset the call type when a phone call is place.
  515. */
  516. private void registerPhoneCallReceiver() {
  517. if (mPhoneStateListener != null) {
  518. return; // Already registered.
  519. }
  520. mTelephonyManager = (TelephonyManager) getActivity().getSystemService(
  521. Context.TELEPHONY_SERVICE);
  522. mPhoneStateListener = new PhoneStateListener() {
  523. @Override
  524. public void onCallStateChanged(int state, String incomingNumber) {
  525. if (state != TelephonyManager.CALL_STATE_OFFHOOK &&
  526. state != TelephonyManager.CALL_STATE_RINGING) {
  527. return;
  528. }
  529. mHandler.post(new Runnable() {
  530. @Override
  531. public void run() {
  532. if (getActivity() == null || getActivity().isFinishing()) {
  533. return;
  534. }
  535. updateFilterTypeAndHeader(CallLogQueryHandler.CALL_TYPE_ALL);
  536. }
  537. });
  538. }
  539. };
  540. mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
  541. }
  542. /**
  543. * Un-registers the phone call receiver.
  544. */
  545. private void unregisterPhoneCallReceiver() {
  546. if (mPhoneStateListener != null) {
  547. mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
  548. mPhoneStateListener = null;
  549. }
  550. }
  551. }