• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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