• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.dialer;
18 
19 import android.content.ContentUris;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Resources;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.provider.ContactsContract.CommonDataKinds.Phone;
26 import android.support.v7.app.AppCompatActivity;
27 import android.text.BidiFormatter;
28 import android.text.TextDirectionHeuristics;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.MotionEvent;
35 import android.view.View;
36 import android.widget.ListView;
37 import android.widget.QuickContactBadge;
38 import android.widget.TextView;
39 import android.widget.Toast;
40 
41 import com.android.contacts.common.CallUtil;
42 import com.android.contacts.common.ClipboardUtils;
43 import com.android.contacts.common.ContactPhotoManager;
44 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
45 import com.android.contacts.common.GeoUtil;
46 import com.android.contacts.common.compat.CompatUtils;
47 import com.android.contacts.common.interactions.TouchPointManager;
48 import com.android.contacts.common.preference.ContactsPreferences;
49 import com.android.contacts.common.testing.NeededForTesting;
50 import com.android.contacts.common.util.UriUtils;
51 import com.android.dialer.calllog.CallDetailHistoryAdapter;
52 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
53 import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener;
54 import com.android.dialer.calllog.CallTypeHelper;
55 import com.android.dialer.calllog.ContactInfoHelper;
56 import com.android.dialer.calllog.PhoneAccountUtils;
57 import com.android.dialer.compat.FilteredNumberCompat;
58 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
59 import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
60 import com.android.dialer.filterednumber.BlockNumberDialogFragment;
61 import com.android.dialer.filterednumber.FilteredNumbersUtil;
62 import com.android.dialer.logging.InteractionEvent;
63 import com.android.dialer.logging.Logger;
64 import com.android.dialer.util.DialerUtils;
65 import com.android.dialer.util.IntentUtil.CallIntentBuilder;
66 import com.android.dialer.util.PhoneNumberUtil;
67 import com.android.dialer.util.TelecomUtil;
68 import com.android.incallui.Call.LogState;
69 
70 /**
71  * Displays the details of a specific call log entry.
72  * <p>
73  * This activity can be either started with the URI of a single call log entry, or with the
74  * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries.
75  */
76 public class CallDetailActivity extends AppCompatActivity
77         implements MenuItem.OnMenuItemClickListener, View.OnClickListener,
78                 BlockNumberDialogFragment.Callback {
79     private static final String TAG = CallDetailActivity.class.getSimpleName();
80 
81      /** A long array extra containing ids of call log entries to display. */
82     public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS";
83     /** If we are started with a voicemail, we'll find the uri to play with this extra. */
84     public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI";
85     /** If the activity was triggered from a notification. */
86     public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION";
87 
88     public static final String VOICEMAIL_FRAGMENT_TAG = "voicemail_fragment";
89 
90     private CallLogAsyncTaskListener mCallLogAsyncTaskListener = new CallLogAsyncTaskListener() {
91         @Override
92         public void onDeleteCall() {
93             finish();
94         }
95 
96         @Override
97         public void onDeleteVoicemail() {
98             finish();
99         }
100 
101         @Override
102         public void onGetCallDetails(PhoneCallDetails[] details) {
103             if (details == null) {
104                 // Somewhere went wrong: we're going to bail out and show error to users.
105                 Toast.makeText(mContext, R.string.toast_call_detail_error,
106                         Toast.LENGTH_SHORT).show();
107                 finish();
108                 return;
109             }
110 
111             // All calls are from the same number and same contact, so pick the first detail.
112             mDetails = details[0];
113             mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString();
114             mPostDialDigits = TextUtils.isEmpty(mDetails.postDialDigits)
115                     ? "" : mDetails.postDialDigits;
116             mDisplayNumber = mDetails.displayNumber;
117 
118             final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails);
119 
120             final CharSequence displayNumber;
121             if (!TextUtils.isEmpty(mDetails.postDialDigits)) {
122                 displayNumber = mDetails.number + mDetails.postDialDigits;
123             } else {
124                 displayNumber = mDetails.displayNumber;
125             }
126 
127             final String displayNumberStr = mBidiFormatter.unicodeWrap(
128                     displayNumber.toString(), TextDirectionHeuristics.LTR);
129 
130             mDetails.nameDisplayOrder = mContactsPreferences.getDisplayOrder();
131 
132             if (!TextUtils.isEmpty(mDetails.getPreferredName())) {
133                 mCallerName.setText(mDetails.getPreferredName());
134                 mCallerNumber.setText(callLocationOrType + " " + displayNumberStr);
135             } else {
136                 mCallerName.setText(displayNumberStr);
137                 if (!TextUtils.isEmpty(callLocationOrType)) {
138                     mCallerNumber.setText(callLocationOrType);
139                     mCallerNumber.setVisibility(View.VISIBLE);
140                 } else {
141                     mCallerNumber.setVisibility(View.GONE);
142                 }
143             }
144 
145             CharSequence accountLabel = PhoneAccountUtils.getAccountLabel(mContext,
146                     mDetails.accountHandle);
147             CharSequence accountContentDescription =
148                     PhoneCallDetails.createAccountLabelDescription(mResources, mDetails.viaNumber,
149                             accountLabel);
150             if (!TextUtils.isEmpty(mDetails.viaNumber)) {
151                 if (!TextUtils.isEmpty(accountLabel)) {
152                     accountLabel = mResources.getString(R.string.call_log_via_number_phone_account,
153                             accountLabel, mDetails.viaNumber);
154                 } else {
155                     accountLabel = mResources.getString(R.string.call_log_via_number,
156                             mDetails.viaNumber);
157                 }
158             }
159             if (!TextUtils.isEmpty(accountLabel)) {
160                 mAccountLabel.setText(accountLabel);
161                 mAccountLabel.setContentDescription(accountContentDescription);
162                 mAccountLabel.setVisibility(View.VISIBLE);
163             } else {
164                 mAccountLabel.setVisibility(View.GONE);
165             }
166 
167             final boolean canPlaceCallsTo =
168                     PhoneNumberUtil.canPlaceCallsTo(mNumber, mDetails.numberPresentation);
169             mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
170             mCopyNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
171 
172             updateBlockActionItemVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
173 
174             final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber);
175             final boolean isVoicemailNumber =
176                     PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber);
177             final boolean showEditNumberBeforeCallAction =
178                     canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
179             mEditBeforeCallActionItem.setVisibility(
180                     showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE);
181 
182             final boolean showReportAction = mContactInfoHelper.canReportAsInvalid(
183                     mDetails.sourceType, mDetails.objectId);
184             mReportActionItem.setVisibility(
185                     showReportAction ? View.VISIBLE : View.GONE);
186 
187             invalidateOptionsMenu();
188 
189             mHistoryList.setAdapter(
190                     new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details));
191 
192             updateFilteredNumberChanges();
193             updateContactPhoto();
194 
195             findViewById(R.id.call_detail).setVisibility(View.VISIBLE);
196         }
197 
198         /**
199          * Determines the location geocode text for a call, or the phone number type
200          * (if available).
201          *
202          * @param details The call details.
203          * @return The phone number type or location.
204          */
205         private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) {
206             if (!TextUtils.isEmpty(details.namePrimary)) {
207                 return Phone.getTypeLabel(mResources, details.numberType,
208                         details.numberLabel);
209             } else {
210                 return details.geocode;
211             }
212         }
213     };
214 
215     private Context mContext;
216     private ContactInfoHelper mContactInfoHelper;
217     private ContactsPreferences mContactsPreferences;
218     private CallTypeHelper mCallTypeHelper;
219     private ContactPhotoManager mContactPhotoManager;
220     private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
221     private BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
222     private LayoutInflater mInflater;
223     private Resources mResources;
224 
225     private PhoneCallDetails mDetails;
226     protected String mNumber;
227     private Uri mVoicemailUri;
228     private String mPostDialDigits = "";
229     private String mDisplayNumber;
230 
231     private ListView mHistoryList;
232     private QuickContactBadge mQuickContactBadge;
233     private TextView mCallerName;
234     private TextView mCallerNumber;
235     private TextView mAccountLabel;
236     private View mCallButton;
237 
238     private TextView mBlockNumberActionItem;
239     private View mEditBeforeCallActionItem;
240     private View mReportActionItem;
241     private View mCopyNumberActionItem;
242 
243     private Integer mBlockedNumberId;
244 
245     @Override
onCreate(Bundle icicle)246     protected void onCreate(Bundle icicle) {
247         super.onCreate(icicle);
248 
249         mContext = this;
250         mResources = getResources();
251         mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this));
252         mContactsPreferences = new ContactsPreferences(mContext);
253         mCallTypeHelper = new CallTypeHelper(getResources());
254         mFilteredNumberAsyncQueryHandler =
255                 new FilteredNumberAsyncQueryHandler(getContentResolver());
256 
257         mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
258 
259         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
260 
261         setContentView(R.layout.call_detail);
262         mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
263 
264         mHistoryList = (ListView) findViewById(R.id.history);
265         mHistoryList.addHeaderView(mInflater.inflate(R.layout.call_detail_header, null));
266         mHistoryList.addFooterView(
267                 mInflater.inflate(R.layout.call_detail_footer, null), null, false);
268 
269         mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo);
270         mQuickContactBadge.setOverlay(null);
271         if (CompatUtils.hasPrioritizedMimeType()) {
272             mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
273         }
274         mCallerName = (TextView) findViewById(R.id.caller_name);
275         mCallerNumber = (TextView) findViewById(R.id.caller_number);
276         mAccountLabel = (TextView) findViewById(R.id.phone_account_label);
277         mContactPhotoManager = ContactPhotoManager.getInstance(this);
278 
279         mCallButton = findViewById(R.id.call_back_button);
280         mCallButton.setOnClickListener(new View.OnClickListener() {
281             @Override
282             public void onClick(View view) {
283                 if (TextUtils.isEmpty(mNumber)) {
284                     return;
285                 }
286                 mContext.startActivity(
287                         new CallIntentBuilder(getDialableNumber())
288                                 .setCallInitiationType(LogState.INITIATION_CALL_DETAILS)
289                                 .build());
290             }
291         });
292 
293 
294         mBlockNumberActionItem = (TextView) findViewById(R.id.call_detail_action_block);
295         updateBlockActionItemVisibility(View.VISIBLE);
296         mBlockNumberActionItem.setOnClickListener(this);
297         mEditBeforeCallActionItem = findViewById(R.id.call_detail_action_edit_before_call);
298         mEditBeforeCallActionItem.setOnClickListener(this);
299         mReportActionItem = findViewById(R.id.call_detail_action_report);
300         mReportActionItem.setOnClickListener(this);
301 
302         mCopyNumberActionItem = findViewById(R.id.call_detail_action_copy);
303         mCopyNumberActionItem.setOnClickListener(this);
304 
305         if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
306             closeSystemDialogs();
307         }
308     }
309 
updateBlockActionItemVisibility(int visibility)310     private void updateBlockActionItemVisibility(int visibility) {
311         if (!FilteredNumberCompat.canAttemptBlockOperations(mContext)) {
312             visibility = View.GONE;
313         }
314         mBlockNumberActionItem.setVisibility(visibility);
315     }
316 
317     @Override
onResume()318     public void onResume() {
319         super.onResume();
320         mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
321         getCallDetails();
322     }
323 
324     @Override
dispatchTouchEvent(MotionEvent ev)325     public boolean dispatchTouchEvent(MotionEvent ev) {
326         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
327             TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
328         }
329         return super.dispatchTouchEvent(ev);
330     }
331 
getCallDetails()332     public void getCallDetails() {
333         CallLogAsyncTaskUtil.getCallDetails(this, getCallLogEntryUris(), mCallLogAsyncTaskListener);
334     }
335 
336     /**
337      * Returns the list of URIs to show.
338      * <p>
339      * There are two ways the URIs can be provided to the activity: as the data on the intent, or as
340      * a list of ids in the call log added as an extra on the URI.
341      * <p>
342      * If both are available, the data on the intent takes precedence.
343      */
getCallLogEntryUris()344     private Uri[] getCallLogEntryUris() {
345         final Uri uri = getIntent().getData();
346         if (uri != null) {
347             // If there is a data on the intent, it takes precedence over the extra.
348             return new Uri[]{ uri };
349         }
350         final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS);
351         final int numIds = ids == null ? 0 : ids.length;
352         final Uri[] uris = new Uri[numIds];
353         for (int index = 0; index < numIds; ++index) {
354             uris[index] = ContentUris.withAppendedId(
355                     TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]);
356         }
357         return uris;
358     }
359 
360     @Override
onCreateOptionsMenu(Menu menu)361     public boolean onCreateOptionsMenu(Menu menu) {
362         final MenuItem deleteMenuItem = menu.add(
363                 Menu.NONE,
364                 R.id.call_detail_delete_menu_item,
365                 Menu.NONE,
366                 R.string.call_details_delete);
367         deleteMenuItem.setIcon(R.drawable.ic_delete_24dp);
368         deleteMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
369         deleteMenuItem.setOnMenuItemClickListener(this);
370 
371         return super.onCreateOptionsMenu(menu);
372     }
373 
374     @Override
onMenuItemClick(MenuItem item)375     public boolean onMenuItemClick(MenuItem item) {
376         if (item.getItemId() == R.id.call_detail_delete_menu_item) {
377             if (hasVoicemail()) {
378                 CallLogAsyncTaskUtil.deleteVoicemail(
379                         this, mVoicemailUri, mCallLogAsyncTaskListener);
380             } else {
381                 final StringBuilder callIds = new StringBuilder();
382                 for (Uri callUri : getCallLogEntryUris()) {
383                     if (callIds.length() != 0) {
384                         callIds.append(",");
385                     }
386                     callIds.append(ContentUris.parseId(callUri));
387                 }
388                 CallLogAsyncTaskUtil.deleteCalls(
389                         this, callIds.toString(), mCallLogAsyncTaskListener);
390             }
391         }
392         return true;
393     }
394 
395     @Override
onClick(View view)396     public void onClick(View view) {
397         int resId = view.getId();
398         if (resId == R.id.call_detail_action_block) {
399             FilteredNumberCompat
400                     .showBlockNumberDialogFlow(mContext.getContentResolver(), mBlockedNumberId,
401                             mNumber, mDetails.countryIso, mDisplayNumber, R.id.call_detail,
402                             getFragmentManager(), this);
403         } else if (resId == R.id.call_detail_action_copy) {
404             ClipboardUtils.copyText(mContext, null, mNumber, true);
405         } else if (resId == R.id.call_detail_action_edit_before_call) {
406             Intent dialIntent = new Intent(Intent.ACTION_DIAL,
407                     CallUtil.getCallUri(getDialableNumber()));
408             DialerUtils.startActivityWithErrorToast(mContext, dialIntent);
409         } else {
410             Log.wtf(TAG, "Unexpected onClick event from " + view);
411         }
412     }
413 
414     @Override
onFilterNumberSuccess()415     public void onFilterNumberSuccess() {
416         Logger.logInteraction(InteractionEvent.BLOCK_NUMBER_CALL_DETAIL);
417         updateFilteredNumberChanges();
418     }
419 
420     @Override
onUnfilterNumberSuccess()421     public void onUnfilterNumberSuccess() {
422         Logger.logInteraction(InteractionEvent.UNBLOCK_NUMBER_CALL_DETAIL);
423         updateFilteredNumberChanges();
424     }
425 
426     @Override
onChangeFilteredNumberUndo()427     public void onChangeFilteredNumberUndo() {
428         updateFilteredNumberChanges();
429     }
430 
updateFilteredNumberChanges()431     private void updateFilteredNumberChanges() {
432         if (mDetails == null ||
433                 !FilteredNumbersUtil.canBlockNumber(this, mNumber, mDetails.countryIso)) {
434             return;
435         }
436 
437         final boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber(
438                 new OnCheckBlockedListener() {
439                     @Override
440                     public void onCheckComplete(Integer id) {
441                         mBlockedNumberId = id;
442                         updateBlockActionItem();
443                     }
444                 }, mNumber, mDetails.countryIso);
445 
446         if (!success) {
447             updateBlockActionItem();
448         }
449     }
450 
451     // Loads and displays the contact photo.
updateContactPhoto()452     private void updateContactPhoto() {
453         if (mDetails == null) {
454             return;
455         }
456 
457         final boolean isVoicemailNumber =
458                 PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber);
459         final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType);
460         int contactType = ContactPhotoManager.TYPE_DEFAULT;
461         if (isVoicemailNumber) {
462             contactType = ContactPhotoManager.TYPE_VOICEMAIL;
463         } else if (isBusiness) {
464             contactType = ContactPhotoManager.TYPE_BUSINESS;
465         }
466 
467         final String displayName = TextUtils.isEmpty(mDetails.namePrimary)
468                 ? mDetails.displayNumber : mDetails.namePrimary.toString();
469         final String lookupKey = mDetails.contactUri == null
470                 ? null : UriUtils.getLookupKeyFromUri(mDetails.contactUri);
471 
472         final DefaultImageRequest request =
473                 new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */);
474 
475         mQuickContactBadge.assignContactUri(mDetails.contactUri);
476         mQuickContactBadge.setContentDescription(
477                 mResources.getString(R.string.description_contact_details, displayName));
478 
479         mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, mDetails.photoUri,
480                 false /* darkTheme */, true /* isCircular */, request);
481     }
482 
updateBlockActionItem()483     private void updateBlockActionItem() {
484         if (mBlockedNumberId == null) {
485             mBlockNumberActionItem.setText(R.string.action_block_number);
486             mBlockNumberActionItem.setCompoundDrawablesRelativeWithIntrinsicBounds(
487                     R.drawable.ic_call_detail_block, 0, 0, 0);
488         } else {
489             mBlockNumberActionItem.setText(R.string.action_unblock_number);
490             mBlockNumberActionItem.setCompoundDrawablesRelativeWithIntrinsicBounds(
491                     R.drawable.ic_call_detail_unblock, 0, 0, 0);
492         }
493     }
494 
closeSystemDialogs()495     private void closeSystemDialogs() {
496         sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
497     }
498 
getDialableNumber()499     private String getDialableNumber() {
500         return mNumber + mPostDialDigits;
501     }
502 
503     @NeededForTesting
hasVoicemail()504     public boolean hasVoicemail() {
505         return mVoicemailUri != null;
506     }
507 }
508