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
17package com.android.dialer;
18
19import android.app.Activity;
20import android.content.ContentResolver;
21import android.content.ContentUris;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.net.Uri;
26import android.os.Bundle;
27import android.os.PowerManager;
28import android.provider.ContactsContract.CommonDataKinds.Phone;
29import android.provider.VoicemailContract.Voicemails;
30import android.telecom.PhoneAccount;
31import android.telecom.PhoneAccountHandle;
32import android.telephony.TelephonyManager;
33import android.text.BidiFormatter;
34import android.text.TextDirectionHeuristics;
35import android.text.TextUtils;
36import android.util.Log;
37import android.view.KeyEvent;
38import android.view.LayoutInflater;
39import android.view.Menu;
40import android.view.MenuItem;
41import android.view.View;
42import android.widget.LinearLayout;
43import android.widget.ListView;
44import android.widget.QuickContactBadge;
45import android.widget.TextView;
46import android.widget.Toast;
47
48import com.android.contacts.common.ContactPhotoManager;
49import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
50import com.android.contacts.common.util.PermissionsUtil;
51import com.android.contacts.common.GeoUtil;
52import com.android.contacts.common.CallUtil;
53import com.android.contacts.common.util.UriUtils;
54import com.android.dialer.calllog.CallDetailHistoryAdapter;
55import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener;
56import com.android.dialer.calllog.CallLogAsyncTaskUtil;
57import com.android.dialer.calllog.CallTypeHelper;
58import com.android.dialer.calllog.ContactInfo;
59import com.android.dialer.calllog.ContactInfoHelper;
60import com.android.dialer.calllog.PhoneAccountUtils;
61import com.android.dialer.calllog.PhoneNumberDisplayUtil;
62import com.android.dialer.util.DialerUtils;
63import com.android.dialer.util.IntentUtil;
64import com.android.dialer.util.PhoneNumberUtil;
65import com.android.dialer.util.TelecomUtil;
66
67import java.util.List;
68
69/**
70 * Displays the details of a specific call log entry.
71 * <p>
72 * This activity can be either started with the URI of a single call log entry, or with the
73 * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries.
74 */
75public class CallDetailActivity extends Activity
76        implements MenuItem.OnMenuItemClickListener {
77    private static final String TAG = "CallDetail";
78
79     /** A long array extra containing ids of call log entries to display. */
80    public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS";
81    /** If we are started with a voicemail, we'll find the uri to play with this extra. */
82    public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI";
83    /** If the activity was triggered from a notification. */
84    public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION";
85
86    public static final String VOICEMAIL_FRAGMENT_TAG = "voicemail_fragment";
87
88    private CallLogAsyncTaskListener mCallLogAsyncTaskListener = new CallLogAsyncTaskListener() {
89        @Override
90        public void onDeleteCall() {
91            finish();
92        }
93
94        @Override
95        public void onDeleteVoicemail() {
96            finish();
97        }
98
99        @Override
100        public void onGetCallDetails(PhoneCallDetails[] details) {
101            if (details == null) {
102                // Somewhere went wrong: we're going to bail out and show error to users.
103                Toast.makeText(mContext, R.string.toast_call_detail_error,
104                        Toast.LENGTH_SHORT).show();
105                finish();
106                return;
107            }
108
109            // We know that all calls are from the same number and the same contact, so pick the
110            // first.
111            PhoneCallDetails firstDetails = details[0];
112            mNumber = TextUtils.isEmpty(firstDetails.number) ?
113                    null : firstDetails.number.toString();
114            final int numberPresentation = firstDetails.numberPresentation;
115            final Uri contactUri = firstDetails.contactUri;
116            final Uri photoUri = firstDetails.photoUri;
117            final PhoneAccountHandle accountHandle = firstDetails.accountHandle;
118
119            // Cache the details about the phone number.
120            final boolean canPlaceCallsTo =
121                    PhoneNumberUtil.canPlaceCallsTo(mNumber, numberPresentation);
122            mIsVoicemailNumber =
123                    PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, mNumber);
124            final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber);
125
126            final CharSequence callLocationOrType = getNumberTypeOrLocation(firstDetails);
127
128            final CharSequence displayNumber = firstDetails.displayNumber;
129            final String displayNumberStr = mBidiFormatter.unicodeWrap(
130                    displayNumber.toString(), TextDirectionHeuristics.LTR);
131
132            if (!TextUtils.isEmpty(firstDetails.name)) {
133                mCallerName.setText(firstDetails.name);
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            mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
146
147            String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, accountHandle);
148            if (!TextUtils.isEmpty(accountLabel)) {
149                mAccountLabel.setText(accountLabel);
150                mAccountLabel.setVisibility(View.VISIBLE);
151            } else {
152                mAccountLabel.setVisibility(View.GONE);
153            }
154
155            mHasEditNumberBeforeCallOption =
156                    canPlaceCallsTo && !isSipNumber && !mIsVoicemailNumber;
157            mHasReportMenuOption = mContactInfoHelper.canReportAsInvalid(
158                    firstDetails.sourceType, firstDetails.objectId);
159            invalidateOptionsMenu();
160
161            ListView historyList = (ListView) findViewById(R.id.history);
162            historyList.setAdapter(
163                    new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details));
164
165            String lookupKey = contactUri == null ? null
166                    : UriUtils.getLookupKeyFromUri(contactUri);
167
168            final boolean isBusiness = mContactInfoHelper.isBusiness(firstDetails.sourceType);
169
170            final int contactType =
171                    mIsVoicemailNumber ? ContactPhotoManager.TYPE_VOICEMAIL :
172                    isBusiness ? ContactPhotoManager.TYPE_BUSINESS :
173                    ContactPhotoManager.TYPE_DEFAULT;
174
175            String nameForDefaultImage;
176            if (TextUtils.isEmpty(firstDetails.name)) {
177                nameForDefaultImage = firstDetails.displayNumber;
178            } else {
179                nameForDefaultImage = firstDetails.name.toString();
180            }
181
182            loadContactPhotos(
183                    contactUri, photoUri, nameForDefaultImage, lookupKey, contactType);
184            findViewById(R.id.call_detail).setVisibility(View.VISIBLE);
185        }
186
187        /**
188         * Determines the location geocode text for a call, or the phone number type
189         * (if available).
190         *
191         * @param details The call details.
192         * @return The phone number type or location.
193         */
194        private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) {
195            if (!TextUtils.isEmpty(details.name)) {
196                return Phone.getTypeLabel(mResources, details.numberType,
197                        details.numberLabel);
198            } else {
199                return details.geocode;
200            }
201        }
202    };
203
204    private Context mContext;
205    private CallTypeHelper mCallTypeHelper;
206    private QuickContactBadge mQuickContactBadge;
207    private TextView mCallerName;
208    private TextView mCallerNumber;
209    private TextView mAccountLabel;
210    private View mCallButton;
211    private ContactInfoHelper mContactInfoHelper;
212
213    protected String mNumber;
214    private boolean mIsVoicemailNumber;
215    private String mDefaultCountryIso;
216
217    /* package */ LayoutInflater mInflater;
218    /* package */ Resources mResources;
219    /** Helper to load contact photos. */
220    private ContactPhotoManager mContactPhotoManager;
221
222    private Uri mVoicemailUri;
223    private BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
224
225    /** Whether we should show "edit number before call" in the options menu. */
226    private boolean mHasEditNumberBeforeCallOption;
227    private boolean mHasReportMenuOption;
228
229    @Override
230    protected void onCreate(Bundle icicle) {
231        super.onCreate(icicle);
232
233        mContext = this;
234
235        setContentView(R.layout.call_detail);
236
237        mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
238        mResources = getResources();
239
240        mCallTypeHelper = new CallTypeHelper(getResources());
241
242        mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
243
244        mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo);
245        mQuickContactBadge.setOverlay(null);
246        mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
247        mCallerName = (TextView) findViewById(R.id.caller_name);
248        mCallerNumber = (TextView) findViewById(R.id.caller_number);
249        mAccountLabel = (TextView) findViewById(R.id.phone_account_label);
250        mDefaultCountryIso = GeoUtil.getCurrentCountryIso(this);
251        mContactPhotoManager = ContactPhotoManager.getInstance(this);
252
253        mCallButton = (View) findViewById(R.id.call_back_button);
254        mCallButton.setOnClickListener(new View.OnClickListener() {
255            @Override
256            public void onClick(View view) {
257                mContext.startActivity(IntentUtil.getCallIntent(mNumber));
258            }
259        });
260
261        mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this));
262        getActionBar().setDisplayHomeAsUpEnabled(true);
263
264        if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
265            closeSystemDialogs();
266        }
267    }
268
269    @Override
270    public void onResume() {
271        super.onResume();
272        getCallDetails();
273    }
274
275    public void getCallDetails() {
276        CallLogAsyncTaskUtil.getCallDetails(this, getCallLogEntryUris(), mCallLogAsyncTaskListener);
277    }
278
279    private boolean hasVoicemail() {
280        return mVoicemailUri != null;
281    }
282
283    /**
284     * Returns the list of URIs to show.
285     * <p>
286     * There are two ways the URIs can be provided to the activity: as the data on the intent, or as
287     * a list of ids in the call log added as an extra on the URI.
288     * <p>
289     * If both are available, the data on the intent takes precedence.
290     */
291    private Uri[] getCallLogEntryUris() {
292        final Uri uri = getIntent().getData();
293        if (uri != null) {
294            // If there is a data on the intent, it takes precedence over the extra.
295            return new Uri[]{ uri };
296        }
297        final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS);
298        final int numIds = ids == null ? 0 : ids.length;
299        final Uri[] uris = new Uri[numIds];
300        for (int index = 0; index < numIds; ++index) {
301            uris[index] = ContentUris.withAppendedId(
302                    TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]);
303        }
304        return uris;
305    }
306
307    /** Load the contact photos and places them in the corresponding views. */
308    private void loadContactPhotos(Uri contactUri, Uri photoUri, String displayName,
309            String lookupKey, int contactType) {
310
311        final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey,
312                contactType, true /* isCircular */);
313
314        mQuickContactBadge.assignContactUri(contactUri);
315        mQuickContactBadge.setContentDescription(
316                mResources.getString(R.string.description_contact_details, displayName));
317
318        mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, photoUri,
319                false /* darkTheme */, true /* isCircular */, request);
320    }
321
322    @Override
323    public boolean onCreateOptionsMenu(Menu menu) {
324        getMenuInflater().inflate(R.menu.call_details_options, menu);
325        return super.onCreateOptionsMenu(menu);
326    }
327
328    @Override
329    public boolean onPrepareOptionsMenu(Menu menu) {
330        // This action deletes all elements in the group from the call log.
331        // We don't have this action for voicemails, because you can just use the trash button.
332        menu.findItem(R.id.menu_remove_from_call_log)
333                .setVisible(!hasVoicemail())
334                .setOnMenuItemClickListener(this);
335        menu.findItem(R.id.menu_edit_number_before_call)
336                .setVisible(mHasEditNumberBeforeCallOption)
337                .setOnMenuItemClickListener(this);
338        menu.findItem(R.id.menu_trash)
339                .setVisible(hasVoicemail())
340                .setOnMenuItemClickListener(this);
341        menu.findItem(R.id.menu_report)
342                .setVisible(mHasReportMenuOption)
343                .setOnMenuItemClickListener(this);
344        return super.onPrepareOptionsMenu(menu);
345    }
346
347    @Override
348    public boolean onMenuItemClick(MenuItem item) {
349        switch (item.getItemId()) {
350            case R.id.menu_remove_from_call_log:
351                final StringBuilder callIds = new StringBuilder();
352                for (Uri callUri : getCallLogEntryUris()) {
353                    if (callIds.length() != 0) {
354                        callIds.append(",");
355                    }
356                    callIds.append(ContentUris.parseId(callUri));
357                }
358                CallLogAsyncTaskUtil.deleteCalls(
359                        this, callIds.toString(), mCallLogAsyncTaskListener);
360                break;
361            case R.id.menu_edit_number_before_call:
362                startActivity(new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(mNumber)));
363                break;
364            case R.id.menu_trash:
365                CallLogAsyncTaskUtil.deleteVoicemail(
366                        this, mVoicemailUri, mCallLogAsyncTaskListener);
367                break;
368        }
369        return true;
370    }
371
372    private void closeSystemDialogs() {
373        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
374    }
375}
376