diff options
Diffstat (limited to 'src/com/android/dialer/calllog')
34 files changed, 0 insertions, 7949 deletions
diff --git a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java deleted file mode 100644 index ac56332ce..000000000 --- a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.Context; -import android.provider.CallLog.Calls; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.android.contacts.common.CallUtil; -import com.android.dialer.PhoneCallDetails; -import com.android.dialer.R; -import com.android.dialer.util.DialerUtils; -import com.google.common.collect.Lists; - -import java.util.ArrayList; - -/** - * Adapter for a ListView containing history items from the details of a call. - */ -public class CallDetailHistoryAdapter extends BaseAdapter { - /** Each history item shows the detail of a call. */ - private static final int VIEW_TYPE_HISTORY_ITEM = 1; - - private final Context mContext; - private final LayoutInflater mLayoutInflater; - private final CallTypeHelper mCallTypeHelper; - private final PhoneCallDetails[] mPhoneCallDetails; - - /** - * List of items to be concatenated together for duration strings. - */ - private ArrayList<CharSequence> mDurationItems = Lists.newArrayList(); - - public CallDetailHistoryAdapter(Context context, LayoutInflater layoutInflater, - CallTypeHelper callTypeHelper, PhoneCallDetails[] phoneCallDetails) { - mContext = context; - mLayoutInflater = layoutInflater; - mCallTypeHelper = callTypeHelper; - mPhoneCallDetails = phoneCallDetails; - } - - @Override - public boolean isEnabled(int position) { - // None of history will be clickable. - return false; - } - - @Override - public int getCount() { - return mPhoneCallDetails.length; - } - - @Override - public Object getItem(int position) { - return mPhoneCallDetails[position]; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public int getItemViewType(int position) { - return VIEW_TYPE_HISTORY_ITEM; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // Make sure we have a valid convertView to start with - final View result = convertView == null - ? mLayoutInflater.inflate(R.layout.call_detail_history_item, parent, false) - : convertView; - - PhoneCallDetails details = mPhoneCallDetails[position]; - CallTypeIconsView callTypeIconView = - (CallTypeIconsView) result.findViewById(R.id.call_type_icon); - TextView callTypeTextView = (TextView) result.findViewById(R.id.call_type_text); - TextView dateView = (TextView) result.findViewById(R.id.date); - TextView durationView = (TextView) result.findViewById(R.id.duration); - - int callType = details.callTypes[0]; - boolean isVideoCall = (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO - && CallUtil.isVideoEnabled(mContext); - - callTypeIconView.clear(); - callTypeIconView.add(callType); - callTypeIconView.setShowVideo(isVideoCall); - callTypeTextView.setText(mCallTypeHelper.getCallTypeText(callType, isVideoCall)); - // Set the date. - CharSequence dateValue = DateUtils.formatDateRange(mContext, details.date, details.date, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR); - dateView.setText(dateValue); - // Set the duration - if (Calls.VOICEMAIL_TYPE == callType || CallTypeHelper.isMissedCallType(callType)) { - durationView.setVisibility(View.GONE); - } else { - durationView.setVisibility(View.VISIBLE); - durationView.setText(formatDurationAndDataUsage(details.duration, details.dataUsage)); - } - - return result; - } - - private CharSequence formatDuration(long elapsedSeconds) { - long minutes = 0; - long seconds = 0; - - if (elapsedSeconds >= 60) { - minutes = elapsedSeconds / 60; - elapsedSeconds -= minutes * 60; - seconds = elapsedSeconds; - return mContext.getString(R.string.callDetailsDurationFormat, minutes, seconds); - } else { - seconds = elapsedSeconds; - return mContext.getString(R.string.callDetailsShortDurationFormat, seconds); - } - } - - /** - * Formats a string containing the call duration and the data usage (if specified). - * - * @param elapsedSeconds Total elapsed seconds. - * @param dataUsage Data usage in bytes, or null if not specified. - * @return String containing call duration and data usage. - */ - private CharSequence formatDurationAndDataUsage(long elapsedSeconds, Long dataUsage) { - CharSequence duration = formatDuration(elapsedSeconds); - - if (dataUsage != null) { - mDurationItems.clear(); - mDurationItems.add(duration); - mDurationItems.add(Formatter.formatShortFileSize(mContext, dataUsage)); - - return DialerUtils.join(mContext.getResources(), mDurationItems); - } else { - return duration; - } - } -} diff --git a/src/com/android/dialer/calllog/CallLogActivity.java b/src/com/android/dialer/calllog/CallLogActivity.java deleted file mode 100644 index 1823a5bd3..000000000 --- a/src/com/android/dialer/calllog/CallLogActivity.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.dialer.calllog; - -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.os.Handler; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.support.v13.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.ViewGroup; - -import com.android.contacts.common.interactions.TouchPointManager; -import com.android.contacts.common.list.ViewPagerTabs; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.commonbind.analytics.AnalyticsUtil; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.R; -import com.android.dialer.TransactionSafeActivity; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.util.DialerUtils; - -public class CallLogActivity extends TransactionSafeActivity implements ViewPager.OnPageChangeListener { - private ViewPager mViewPager; - private ViewPagerTabs mViewPagerTabs; - private ViewPagerAdapter mViewPagerAdapter; - private CallLogFragment mAllCallsFragment; - private CallLogFragment mMissedCallsFragment; - - private String[] mTabTitles; - - private static final int TAB_INDEX_ALL = 0; - private static final int TAB_INDEX_MISSED = 1; - - private static final int TAB_INDEX_COUNT = 2; - - private boolean mIsResumed; - - public class ViewPagerAdapter extends FragmentPagerAdapter { - public ViewPagerAdapter(FragmentManager fm) { - super(fm); - } - - @Override - public long getItemId(int position) { - return getRtlPosition(position); - } - - @Override - public Fragment getItem(int position) { - switch (getRtlPosition(position)) { - case TAB_INDEX_ALL: - return new CallLogFragment( - CallLogQueryHandler.CALL_TYPE_ALL, true /* isCallLogActivity */); - case TAB_INDEX_MISSED: - return new CallLogFragment(Calls.MISSED_TYPE, true /* isCallLogActivity */); - } - throw new IllegalStateException("No fragment at position " + position); - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - final CallLogFragment fragment = - (CallLogFragment) super.instantiateItem(container, position); - switch (position) { - case TAB_INDEX_ALL: - mAllCallsFragment = fragment; - break; - case TAB_INDEX_MISSED: - mMissedCallsFragment = fragment; - break; - } - return fragment; - } - - @Override - public CharSequence getPageTitle(int position) { - return mTabTitles[position]; - } - - @Override - public int getCount() { - return TAB_INDEX_COUNT; - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY()); - } - return super.dispatchTouchEvent(ev); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.call_log_activity); - getWindow().setBackgroundDrawable(null); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setElevation(0); - - int startingTab = TAB_INDEX_ALL; - final Intent intent = getIntent(); - if (intent != null) { - final int callType = intent.getIntExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, -1); - if (callType == CallLog.Calls.MISSED_TYPE) { - startingTab = TAB_INDEX_MISSED; - } - } - - mTabTitles = new String[TAB_INDEX_COUNT]; - mTabTitles[0] = getString(R.string.call_log_all_title); - mTabTitles[1] = getString(R.string.call_log_missed_title); - - mViewPager = (ViewPager) findViewById(R.id.call_log_pager); - - mViewPagerAdapter = new ViewPagerAdapter(getFragmentManager()); - mViewPager.setAdapter(mViewPagerAdapter); - mViewPager.setOffscreenPageLimit(1); - mViewPager.setOnPageChangeListener(this); - - mViewPagerTabs = (ViewPagerTabs) findViewById(R.id.viewpager_header); - - mViewPagerTabs.setViewPager(mViewPager); - mViewPager.setCurrentItem(startingTab); - } - - @Override - protected void onResume() { - mIsResumed = true; - super.onResume(); - sendScreenViewForChildFragment(mViewPager.getCurrentItem()); - } - - @Override - protected void onPause() { - mIsResumed = false; - super.onPause(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - final MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.call_log_options, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all); - if (mAllCallsFragment != null && itemDeleteAll != null) { - // If onPrepareOptionsMenu is called before fragments are loaded, don't do anything. - final CallLogAdapter adapter = mAllCallsFragment.getAdapter(); - itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty()); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (!isSafeToCommitTransactions()) { - return true; - } - - if (item.getItemId() == android.R.id.home) { - final Intent intent = new Intent(this, DialtactsActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - } else if (item.getItemId() == R.id.delete_all) { - ClearCallLogDialog.show(getFragmentManager()); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mViewPagerTabs.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - - @Override - public void onPageSelected(int position) { - if (mIsResumed) { - sendScreenViewForChildFragment(position); - } - mViewPagerTabs.onPageSelected(position); - } - - @Override - public void onPageScrollStateChanged(int state) { - mViewPagerTabs.onPageScrollStateChanged(state); - } - - private void sendScreenViewForChildFragment(int position) { - Logger.logScreenView(ScreenEvent.CALL_LOG_FILTER, this); - } - - private int getRtlPosition(int position) { - if (DialerUtils.isRtl()) { - return mViewPagerAdapter.getCount() - 1 - position; - } - return position; - } -} diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java deleted file mode 100644 index 3958611b9..000000000 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import com.google.common.annotations.VisibleForTesting; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Trace; -import android.preference.PreferenceManager; -import android.provider.CallLog; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.telecom.PhoneAccountHandle; -import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.AccessibilityDelegate; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.PhoneNumberUtilsCompat; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.PhoneCallDetails; -import com.android.dialer.R; -import com.android.dialer.calllog.calllogcache.CallLogCache; -import com.android.dialer.contactinfo.ContactInfoCache; -import com.android.dialer.contactinfo.ContactInfoCache.OnContactInfoChangedListener; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.VoicemailArchiveContract; -import com.android.dialer.filterednumber.BlockNumberDialogFragment.Callback; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; -import com.android.dialer.service.ExtendedBlockingButtonRenderer; -import com.android.dialer.util.PhoneNumberUtil; -import com.android.dialer.voicemail.VoicemailPlaybackPresenter; - -import java.util.HashMap; -import java.util.Map; - -/** - * Adapter class to fill in data for the Call Log. - */ -public class CallLogAdapter extends GroupingListAdapter - implements CallLogGroupBuilder.GroupCreator, - VoicemailPlaybackPresenter.OnVoicemailDeletedListener, - ExtendedBlockingButtonRenderer.Listener { - - // Types of activities the call log adapter is used for - public static final int ACTIVITY_TYPE_CALL_LOG = 1; - public static final int ACTIVITY_TYPE_ARCHIVE = 2; - public static final int ACTIVITY_TYPE_DIALTACTS = 3; - - /** Interface used to initiate a refresh of the content. */ - public interface CallFetcher { - public void fetchCalls(); - } - - private static final int NO_EXPANDED_LIST_ITEM = -1; - // ConcurrentHashMap doesn't store null values. Use this value for numbers which aren't blocked. - private static final int NOT_BLOCKED = -1; - - private static final int VOICEMAIL_PROMO_CARD_POSITION = 0; - - protected static final int VIEW_TYPE_NORMAL = 0; - private static final int VIEW_TYPE_VOICEMAIL_PROMO_CARD = 1; - - /** - * The key for the show voicemail promo card preference which will determine whether the promo - * card was permanently dismissed or not. - */ - private static final String SHOW_VOICEMAIL_PROMO_CARD = "show_voicemail_promo_card"; - private static final boolean SHOW_VOICEMAIL_PROMO_CARD_DEFAULT = true; - - protected final Context mContext; - private final ContactInfoHelper mContactInfoHelper; - protected final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; - private final CallFetcher mCallFetcher; - private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - private final Map<String, Boolean> mBlockedNumberCache = new ArrayMap<>(); - - protected ContactInfoCache mContactInfoCache; - - private final int mActivityType; - - private static final String KEY_EXPANDED_POSITION = "expanded_position"; - private static final String KEY_EXPANDED_ROW_ID = "expanded_row_id"; - - // Tracks the position of the currently expanded list item. - private int mCurrentlyExpandedPosition = RecyclerView.NO_POSITION; - // Tracks the rowId of the currently expanded list item, so the position can be updated if there - // are any changes to the call log entries, such as additions or removals. - private long mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM; - private int mHiddenPosition = RecyclerView.NO_POSITION; - private Uri mHiddenItemUri = null; - private boolean mPendingHide = false; - - /** - * Hashmap, keyed by call Id, used to track the day group for a call. As call log entries are - * put into the primary call groups in {@link com.android.dialer.calllog.CallLogGroupBuilder}, - * they are also assigned a secondary "day group". This hashmap tracks the day group assigned - * to all calls in the call log. This information is used to trigger the display of a day - * group header above the call log entry at the start of a day group. - * Note: Multiple calls are grouped into a single primary "call group" in the call log, and - * the cursor used to bind rows includes all of these calls. When determining if a day group - * change has occurred it is necessary to look at the last entry in the call log to determine - * its day group. This hashmap provides a means of determining the previous day group without - * having to reverse the cursor to the start of the previous day call log entry. - */ - private HashMap<Long, Integer> mDayGroups = new HashMap<>(); - - private boolean mLoading = true; - - private SharedPreferences mPrefs; - - private ContactsPreferences mContactsPreferences; - - protected boolean mShowVoicemailPromoCard = false; - - /** Instance of helper class for managing views. */ - private final CallLogListItemHelper mCallLogListItemHelper; - - /** Cache for repeated requests to Telecom/Telephony. */ - protected final CallLogCache mCallLogCache; - - /** Helper to group call log entries. */ - private final CallLogGroupBuilder mCallLogGroupBuilder; - - /** - * The OnClickListener used to expand or collapse the action buttons of a call log entry. - */ - private final View.OnClickListener mExpandCollapseListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) v.getTag(); - if (viewHolder == null) { - return; - } - - if (mVoicemailPlaybackPresenter != null) { - // Always reset the voicemail playback state on expand or collapse. - mVoicemailPlaybackPresenter.resetAll(); - } - - if (viewHolder.getAdapterPosition() == mCurrentlyExpandedPosition) { - // Hide actions, if the clicked item is the expanded item. - viewHolder.showActions(false); - - mCurrentlyExpandedPosition = RecyclerView.NO_POSITION; - mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM; - } else { - if (viewHolder.callType == CallLog.Calls.MISSED_TYPE) { - CallLogAsyncTaskUtil.markCallAsRead(mContext, viewHolder.callIds); - if (mActivityType == ACTIVITY_TYPE_DIALTACTS) { - ((DialtactsActivity) v.getContext()).updateTabUnreadCounts(); - } - } - expandViewHolderActions(viewHolder); - } - - } - }; - - /** - * Click handler used to dismiss the promo card when the user taps the "ok" button. - */ - private final View.OnClickListener mOkActionListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - dismissVoicemailPromoCard(); - } - }; - - /** - * Click handler used to send the user to the voicemail settings screen and then dismiss the - * promo card. - */ - private final View.OnClickListener mVoicemailSettingsActionListener = - new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL); - mContext.startActivity(intent); - dismissVoicemailPromoCard(); - } - }; - - private void expandViewHolderActions(CallLogListItemViewHolder viewHolder) { - // If another item is expanded, notify it that it has changed. Its actions will be - // hidden when it is re-binded because we change mCurrentlyExpandedPosition below. - if (mCurrentlyExpandedPosition != RecyclerView.NO_POSITION) { - notifyItemChanged(mCurrentlyExpandedPosition); - } - // Show the actions for the clicked list item. - viewHolder.showActions(true); - mCurrentlyExpandedPosition = viewHolder.getAdapterPosition(); - mCurrentlyExpandedRowId = viewHolder.rowId; - } - - /** - * Expand the actions on a list item when focused in Talkback mode, to aid discoverability. - */ - private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { - @Override - public boolean onRequestSendAccessibilityEvent( - ViewGroup host, View child, AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { - // Only expand if actions are not already expanded, because triggering the expand - // function on clicks causes the action views to lose the focus indicator. - CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) host.getTag(); - if (mCurrentlyExpandedPosition != viewHolder.getAdapterPosition()) { - if (mVoicemailPlaybackPresenter != null) { - // Always reset the voicemail playback state on expand. - mVoicemailPlaybackPresenter.resetAll(); - } - - expandViewHolderActions((CallLogListItemViewHolder) host.getTag()); - } - } - return super.onRequestSendAccessibilityEvent(host, child, event); - } - }; - - protected final OnContactInfoChangedListener mOnContactInfoChangedListener = - new OnContactInfoChangedListener() { - @Override - public void onContactInfoChanged() { - notifyDataSetChanged(); - } - }; - - public CallLogAdapter( - Context context, - CallFetcher callFetcher, - ContactInfoHelper contactInfoHelper, - VoicemailPlaybackPresenter voicemailPlaybackPresenter, - int activityType) { - super(context); - - mContext = context; - mCallFetcher = callFetcher; - mContactInfoHelper = contactInfoHelper; - mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; - if (mVoicemailPlaybackPresenter != null) { - mVoicemailPlaybackPresenter.setOnVoicemailDeletedListener(this); - } - - mActivityType = activityType; - - mContactInfoCache = new ContactInfoCache( - mContactInfoHelper, mOnContactInfoChangedListener); - if (!PermissionsUtil.hasContactsPermissions(context)) { - mContactInfoCache.disableRequestProcessing(); - } - - Resources resources = mContext.getResources(); - CallTypeHelper callTypeHelper = new CallTypeHelper(resources); - - mCallLogCache = CallLogCache.getCallLogCache(mContext); - - PhoneCallDetailsHelper phoneCallDetailsHelper = - new PhoneCallDetailsHelper(mContext, resources, mCallLogCache); - mCallLogListItemHelper = - new CallLogListItemHelper(phoneCallDetailsHelper, resources, mCallLogCache); - mCallLogGroupBuilder = new CallLogGroupBuilder(this); - mFilteredNumberAsyncQueryHandler = - new FilteredNumberAsyncQueryHandler(mContext.getContentResolver()); - - mPrefs = PreferenceManager.getDefaultSharedPreferences(context); - mContactsPreferences = new ContactsPreferences(mContext); - maybeShowVoicemailPromoCard(); - } - - public void onSaveInstanceState(Bundle outState) { - outState.putInt(KEY_EXPANDED_POSITION, mCurrentlyExpandedPosition); - outState.putLong(KEY_EXPANDED_ROW_ID, mCurrentlyExpandedRowId); - } - - public void onRestoreInstanceState(Bundle savedInstanceState) { - if (savedInstanceState != null) { - mCurrentlyExpandedPosition = - savedInstanceState.getInt(KEY_EXPANDED_POSITION, RecyclerView.NO_POSITION); - mCurrentlyExpandedRowId = - savedInstanceState.getLong(KEY_EXPANDED_ROW_ID, NO_EXPANDED_LIST_ITEM); - } - } - - @Override - public void onBlockedNumber(String number,String countryIso) { - String cacheKey = PhoneNumberUtils.formatNumberToE164(number, countryIso); - if (!TextUtils.isEmpty(cacheKey)) { - mBlockedNumberCache.put(cacheKey, true); - notifyDataSetChanged(); - } - } - - @Override - public void onUnblockedNumber( String number, String countryIso) { - String cacheKey = PhoneNumberUtils.formatNumberToE164(number, countryIso); - if (!TextUtils.isEmpty(cacheKey)) { - mBlockedNumberCache.put(cacheKey, false); - notifyDataSetChanged(); - } - } - - /** - * Requery on background thread when {@link Cursor} changes. - */ - @Override - protected void onContentChanged() { - mCallFetcher.fetchCalls(); - } - - public void setLoading(boolean loading) { - mLoading = loading; - } - - public boolean isEmpty() { - if (mLoading) { - // We don't want the empty state to show when loading. - return false; - } else { - return getItemCount() == 0; - } - } - - public void invalidateCache() { - mContactInfoCache.invalidate(); - } - - public void onResume() { - if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) { - mContactInfoCache.start(); - } - mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); - } - - public void onPause() { - pauseCache(); - - if (mHiddenItemUri != null) { - CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null); - } - } - - @VisibleForTesting - /* package */ void pauseCache() { - mContactInfoCache.stop(); - mCallLogCache.reset(); - } - - @Override - protected void addGroups(Cursor cursor) { - mCallLogGroupBuilder.addGroups(cursor); - } - - @Override - public void addVoicemailGroups(Cursor cursor) { - mCallLogGroupBuilder.addVoicemailGroups(cursor); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == VIEW_TYPE_VOICEMAIL_PROMO_CARD) { - return createVoicemailPromoCardViewHolder(parent); - } - return createCallLogEntryViewHolder(parent); - } - - /** - * Creates a new call log entry {@link ViewHolder}. - * - * @param parent the parent view. - * @return The {@link ViewHolder}. - */ - private ViewHolder createCallLogEntryViewHolder(ViewGroup parent) { - LayoutInflater inflater = LayoutInflater.from(mContext); - View view = inflater.inflate(R.layout.call_log_list_item, parent, false); - CallLogListItemViewHolder viewHolder = CallLogListItemViewHolder.create( - view, - mContext, - this, - mExpandCollapseListener, - mCallLogCache, - mCallLogListItemHelper, - mVoicemailPlaybackPresenter, - mFilteredNumberAsyncQueryHandler, - new Callback() { - @Override - public void onFilterNumberSuccess() { - Logger.logInteraction( - InteractionEvent.BLOCK_NUMBER_CALL_LOG); - } - - @Override - public void onUnfilterNumberSuccess() { - Logger.logInteraction( - InteractionEvent.UNBLOCK_NUMBER_CALL_LOG); - } - - @Override - public void onChangeFilteredNumberUndo() {} - }, mActivityType == ACTIVITY_TYPE_ARCHIVE); - - viewHolder.callLogEntryView.setTag(viewHolder); - viewHolder.callLogEntryView.setAccessibilityDelegate(mAccessibilityDelegate); - - viewHolder.primaryActionView.setTag(viewHolder); - - return viewHolder; - } - - /** - * Binds the views in the entry to the data in the call log. - * TODO: This gets called 20-30 times when Dialer starts up for a single call log entry and - * should not. It invokes cross-process methods and the repeat execution can get costly. - * - * @param viewHolder The view corresponding to this entry. - * @param position The position of the entry. - */ - @Override - public void onBindViewHolder(ViewHolder viewHolder, int position) { - Trace.beginSection("onBindViewHolder: " + position); - - switch (getItemViewType(position)) { - case VIEW_TYPE_VOICEMAIL_PROMO_CARD: - bindVoicemailPromoCardViewHolder(viewHolder); - break; - default: - bindCallLogListViewHolder(viewHolder, position); - break; - } - - Trace.endSection(); - } - - /** - * Binds the promo card view holder. - * - * @param viewHolder The promo card view holder. - */ - protected void bindVoicemailPromoCardViewHolder(ViewHolder viewHolder) { - PromoCardViewHolder promoCardViewHolder = (PromoCardViewHolder) viewHolder; - - promoCardViewHolder.getSecondaryActionView() - .setOnClickListener(mVoicemailSettingsActionListener); - promoCardViewHolder.getPrimaryActionView().setOnClickListener(mOkActionListener); - } - - /** - * Binds the view holder for the call log list item view. - * - * @param viewHolder The call log list item view holder. - * @param position The position of the list item. - */ - - private void bindCallLogListViewHolder(ViewHolder viewHolder, int position) { - Cursor c = (Cursor) getItem(position); - if (c == null) { - return; - } - - int count = getGroupSize(position); - - final String number = c.getString(CallLogQuery.NUMBER); - final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO); - final String postDialDigits = CompatUtils.isNCompatible() - && mActivityType != ACTIVITY_TYPE_ARCHIVE ? - c.getString(CallLogQuery.POST_DIAL_DIGITS) : ""; - final String viaNumber = CompatUtils.isNCompatible() - && mActivityType != ACTIVITY_TYPE_ARCHIVE ? - c.getString(CallLogQuery.VIA_NUMBER) : ""; - final int numberPresentation = c.getInt(CallLogQuery.NUMBER_PRESENTATION); - final PhoneAccountHandle accountHandle = PhoneAccountUtils.getAccount( - c.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME), - c.getString(CallLogQuery.ACCOUNT_ID)); - final ContactInfo cachedContactInfo = ContactInfoHelper.getContactInfo(c); - final boolean isVoicemailNumber = - mCallLogCache.isVoicemailNumber(accountHandle, number); - - // Note: Binding of the action buttons is done as required in configureActionViews when the - // user expands the actions ViewStub. - - ContactInfo info = ContactInfo.EMPTY; - if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemailNumber) { - // Lookup contacts with this number - info = mContactInfoCache.getValue(number + postDialDigits, - countryIso, cachedContactInfo); - } - CharSequence formattedNumber = info.formattedNumber == null - ? null : PhoneNumberUtilsCompat.createTtsSpannable(info.formattedNumber); - - final PhoneCallDetails details = new PhoneCallDetails( - mContext, number, numberPresentation, formattedNumber, - postDialDigits, isVoicemailNumber); - details.viaNumber = viaNumber; - details.accountHandle = accountHandle; - details.countryIso = countryIso; - details.date = c.getLong(CallLogQuery.DATE); - details.duration = c.getLong(CallLogQuery.DURATION); - details.features = getCallFeatures(c, count); - details.geocode = c.getString(CallLogQuery.GEOCODED_LOCATION); - details.transcription = c.getString(CallLogQuery.TRANSCRIPTION); - details.callTypes = getCallTypes(c, count); - - if (!c.isNull(CallLogQuery.DATA_USAGE)) { - details.dataUsage = c.getLong(CallLogQuery.DATA_USAGE); - } - - if (!TextUtils.isEmpty(info.name) || !TextUtils.isEmpty(info.nameAlternative)) { - details.contactUri = info.lookupUri; - details.namePrimary = info.name; - details.nameAlternative = info.nameAlternative; - details.nameDisplayOrder = mContactsPreferences.getDisplayOrder(); - details.numberType = info.type; - details.numberLabel = info.label; - details.photoUri = info.photoUri; - details.sourceType = info.sourceType; - details.objectId = info.objectId; - details.contactUserType = info.userType; - } - - final CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder; - views.info = info; - views.rowId = c.getLong(CallLogQuery.ID); - // Store values used when the actions ViewStub is inflated on expansion. - views.number = number; - views.postDialDigits = details.postDialDigits; - views.displayNumber = details.displayNumber; - views.numberPresentation = numberPresentation; - - views.accountHandle = accountHandle; - // Stash away the Ids of the calls so that we can support deleting a row in the call log. - views.callIds = getCallIds(c, count); - views.isBusiness = mContactInfoHelper.isBusiness(info.sourceType); - views.numberType = (String) Phone.getTypeLabel(mContext.getResources(), details.numberType, - details.numberLabel); - // Default case: an item in the call log. - views.primaryActionView.setVisibility(View.VISIBLE); - views.workIconView.setVisibility( - details.contactUserType == ContactsUtils.USER_TYPE_WORK ? View.VISIBLE : View.GONE); - - // Check if the day group has changed and display a header if necessary. - int currentGroup = getDayGroupForCall(views.rowId); - int previousGroup = getPreviousDayGroup(c); - if (currentGroup != previousGroup) { - views.dayGroupHeader.setVisibility(View.VISIBLE); - views.dayGroupHeader.setText(getGroupDescription(currentGroup)); - } else { - views.dayGroupHeader.setVisibility(View.GONE); - } - - if (mActivityType == ACTIVITY_TYPE_ARCHIVE) { - views.callType = CallLog.Calls.VOICEMAIL_TYPE; - views.voicemailUri = VoicemailArchiveContract.VoicemailArchive.buildWithId(c.getInt( - c.getColumnIndex(VoicemailArchiveContract.VoicemailArchive._ID))) - .toString(); - - } else { - if (details.callTypes[0] == CallLog.Calls.VOICEMAIL_TYPE || - details.callTypes[0] == CallLog.Calls.MISSED_TYPE) { - details.isRead = c.getInt(CallLogQuery.IS_READ) == 1; - } - views.callType = c.getInt(CallLogQuery.CALL_TYPE); - views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI); - } - - mCallLogListItemHelper.setPhoneCallDetails(views, details); - - if (mCurrentlyExpandedRowId == views.rowId) { - // In case ViewHolders were added/removed, update the expanded position if the rowIds - // match so that we can restore the correct expanded state on rebind. - mCurrentlyExpandedPosition = position; - views.showActions(true); - } else { - views.showActions(false); - } - views.updatePhoto(); - - mCallLogListItemHelper.setPhoneCallDetails(views, details); - } - - private String getPreferredDisplayName(ContactInfo contactInfo) { - if (mContactsPreferences.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY || - TextUtils.isEmpty(contactInfo.nameAlternative)) { - return contactInfo.name; - } - return contactInfo.nameAlternative; - } - - @Override - public int getItemCount() { - return super.getItemCount() + (mShowVoicemailPromoCard ? 1 : 0) - - (mHiddenPosition != RecyclerView.NO_POSITION ? 1 : 0); - } - - @Override - public int getItemViewType(int position) { - if (position == VOICEMAIL_PROMO_CARD_POSITION && mShowVoicemailPromoCard) { - return VIEW_TYPE_VOICEMAIL_PROMO_CARD; - } - return super.getItemViewType(position); - } - - /** - * Retrieves an item at the specified position, taking into account the presence of a promo - * card. - * - * @param position The position to retrieve. - * @return The item at that position. - */ - @Override - public Object getItem(int position) { - return super.getItem(position - (mShowVoicemailPromoCard ? 1 : 0) - + ((mHiddenPosition != RecyclerView.NO_POSITION && position >= mHiddenPosition) - ? 1 : 0)); - } - - @Override - public int getGroupSize(int position) { - return super.getGroupSize(position - (mShowVoicemailPromoCard ? 1 : 0)); - } - - protected boolean isCallLogActivity() { - return mActivityType == ACTIVITY_TYPE_CALL_LOG; - } - - /** - * In order to implement the "undo" function, when a voicemail is "deleted" i.e. when the user - * clicks the delete button, the deleted item is temporarily hidden from the list. If a user - * clicks delete on a second item before the first item's undo option has expired, the first - * item is immediately deleted so that only one item can be "undoed" at a time. - */ - @Override - public void onVoicemailDeleted(Uri uri) { - if (mHiddenItemUri == null) { - // Immediately hide the currently expanded card. - mHiddenPosition = mCurrentlyExpandedPosition; - notifyDataSetChanged(); - } else { - // This means that there was a previous item that was hidden in the UI but not - // yet deleted from the database (call it a "pending delete"). Delete this previous item - // now since it is only possible to do one "undo" at a time. - CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null); - - // Set pending hide action so that the current item is hidden only after the previous - // item is permanently deleted. - mPendingHide = true; - } - - collapseExpandedCard(); - - // Save the new hidden item uri in case it needs to be deleted from the database when - // a user attempts to delete another item. - mHiddenItemUri = uri; - } - - private void collapseExpandedCard() { - mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM; - mCurrentlyExpandedPosition = RecyclerView.NO_POSITION; - } - - /** - * When the list is changing all stored position is no longer valid. - */ - public void invalidatePositions() { - mCurrentlyExpandedPosition = RecyclerView.NO_POSITION; - mHiddenPosition = RecyclerView.NO_POSITION; - } - - /** - * When the user clicks "undo", the hidden item is unhidden. - */ - @Override - public void onVoicemailDeleteUndo() { - mHiddenPosition = RecyclerView.NO_POSITION; - mHiddenItemUri = null; - - mPendingHide = false; - notifyDataSetChanged(); - } - - /** - * This callback signifies that a database deletion has completed. This means that if there is - * an item pending deletion, it will be hidden because the previous item that was in "undo" mode - * has been removed from the database. Otherwise it simply resets the hidden state because there - * are no pending deletes and thus no hidden items. - */ - @Override - public void onVoicemailDeletedInDatabase() { - if (mPendingHide) { - mHiddenPosition = mCurrentlyExpandedPosition; - mPendingHide = false; - } else { - // There should no longer be any hidden item because it has been deleted from the - // database. - mHiddenPosition = RecyclerView.NO_POSITION; - mHiddenItemUri = null; - } - } - - /** - * Retrieves the day group of the previous call in the call log. Used to determine if the day - * group has changed and to trigger display of the day group text. - * - * @param cursor The call log cursor. - * @return The previous day group, or DAY_GROUP_NONE if this is the first call. - */ - private int getPreviousDayGroup(Cursor cursor) { - // We want to restore the position in the cursor at the end. - int startingPosition = cursor.getPosition(); - int dayGroup = CallLogGroupBuilder.DAY_GROUP_NONE; - if (cursor.moveToPrevious()) { - // If the previous entry is hidden (deleted in the UI but not in the database), skip it - // and check the card above it. A list with the voicemail promo card at the top will be - // 1-indexed because the 0th index is the promo card iteself. - int previousViewPosition = mShowVoicemailPromoCard ? startingPosition : - startingPosition - 1; - if (previousViewPosition != mHiddenPosition || - (previousViewPosition == mHiddenPosition && cursor.moveToPrevious())) { - long previousRowId = cursor.getLong(CallLogQuery.ID); - dayGroup = getDayGroupForCall(previousRowId); - } - } - cursor.moveToPosition(startingPosition); - return dayGroup; - } - - /** - * Given a call Id, look up the day group that the call belongs to. The day group data is - * populated in {@link com.android.dialer.calllog.CallLogGroupBuilder}. - * - * @param callId The call to retrieve the day group for. - * @return The day group for the call. - */ - private int getDayGroupForCall(long callId) { - if (mDayGroups.containsKey(callId)) { - return mDayGroups.get(callId); - } - return CallLogGroupBuilder.DAY_GROUP_NONE; - } - - /** - * Returns the call types for the given number of items in the cursor. - * <p> - * It uses the next {@code count} rows in the cursor to extract the types. - * <p> - * It position in the cursor is unchanged by this function. - */ - private int[] getCallTypes(Cursor cursor, int count) { - if (mActivityType == ACTIVITY_TYPE_ARCHIVE) { - return new int[] {CallLog.Calls.VOICEMAIL_TYPE}; - } - int position = cursor.getPosition(); - int[] callTypes = new int[count]; - for (int index = 0; index < count; ++index) { - callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE); - cursor.moveToNext(); - } - cursor.moveToPosition(position); - return callTypes; - } - - /** - * Determine the features which were enabled for any of the calls that make up a call log - * entry. - * - * @param cursor The cursor. - * @param count The number of calls for the current call log entry. - * @return The features. - */ - private int getCallFeatures(Cursor cursor, int count) { - int features = 0; - int position = cursor.getPosition(); - for (int index = 0; index < count; ++index) { - features |= cursor.getInt(CallLogQuery.FEATURES); - cursor.moveToNext(); - } - cursor.moveToPosition(position); - return features; - } - - /** - * Sets whether processing of requests for contact details should be enabled. - * - * This method should be called in tests to disable such processing of requests when not - * needed. - */ - @VisibleForTesting - void disableRequestProcessingForTest() { - // TODO: Remove this and test the cache directly. - mContactInfoCache.disableRequestProcessing(); - } - - @VisibleForTesting - void injectContactInfoForTest(String number, String countryIso, ContactInfo contactInfo) { - // TODO: Remove this and test the cache directly. - mContactInfoCache.injectContactInfoForTest(number, countryIso, contactInfo); - } - - /** - * Stores the day group associated with a call in the call log. - * - * @param rowId The row Id of the current call. - * @param dayGroup The day group the call belongs in. - */ - @Override - public void setDayGroup(long rowId, int dayGroup) { - if (!mDayGroups.containsKey(rowId)) { - mDayGroups.put(rowId, dayGroup); - } - } - - /** - * Clears the day group associations on re-bind of the call log. - */ - @Override - public void clearDayGroups() { - mDayGroups.clear(); - } - - /** - * Retrieves the call Ids represented by the current call log row. - * - * @param cursor Call log cursor to retrieve call Ids from. - * @param groupSize Number of calls associated with the current call log row. - * @return Array of call Ids. - */ - private long[] getCallIds(final Cursor cursor, final int groupSize) { - // We want to restore the position in the cursor at the end. - int startingPosition = cursor.getPosition(); - long[] ids = new long[groupSize]; - // Copy the ids of the rows in the group. - for (int index = 0; index < groupSize; ++index) { - ids[index] = cursor.getLong(CallLogQuery.ID); - cursor.moveToNext(); - } - cursor.moveToPosition(startingPosition); - return ids; - } - - /** - * Determines the description for a day group. - * - * @param group The day group to retrieve the description for. - * @return The day group description. - */ - private CharSequence getGroupDescription(int group) { - if (group == CallLogGroupBuilder.DAY_GROUP_TODAY) { - return mContext.getResources().getString(R.string.call_log_header_today); - } else if (group == CallLogGroupBuilder.DAY_GROUP_YESTERDAY) { - return mContext.getResources().getString(R.string.call_log_header_yesterday); - } else { - return mContext.getResources().getString(R.string.call_log_header_other); - } - } - - /** - * Determines if the voicemail promo card should be shown or not. The voicemail promo card will - * be shown as the first item in the voicemail tab. - */ - private void maybeShowVoicemailPromoCard() { - boolean showPromoCard = mPrefs.getBoolean(SHOW_VOICEMAIL_PROMO_CARD, - SHOW_VOICEMAIL_PROMO_CARD_DEFAULT); - mShowVoicemailPromoCard = mActivityType != ACTIVITY_TYPE_ARCHIVE && - (mVoicemailPlaybackPresenter != null) && showPromoCard; - } - - /** - * Dismisses the voicemail promo card and refreshes the call log. - */ - private void dismissVoicemailPromoCard() { - mPrefs.edit().putBoolean(SHOW_VOICEMAIL_PROMO_CARD, false).apply(); - mShowVoicemailPromoCard = false; - notifyItemRemoved(VOICEMAIL_PROMO_CARD_POSITION); - } - - /** - * Creates the view holder for the voicemail promo card. - * - * @param parent The parent view. - * @return The {@link ViewHolder}. - */ - protected ViewHolder createVoicemailPromoCardViewHolder(ViewGroup parent) { - LayoutInflater inflater = LayoutInflater.from(mContext); - View view = inflater.inflate(R.layout.voicemail_promo_card, parent, false); - - PromoCardViewHolder viewHolder = PromoCardViewHolder.create(view); - return viewHolder; - } -} diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java deleted file mode 100644 index 34b2f0ea9..000000000 --- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import com.google.common.annotations.VisibleForTesting; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.CallLog; -import android.provider.VoicemailContract.Voicemails; -import android.telecom.PhoneAccountHandle; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.PhoneCallDetails; -import com.android.dialer.compat.CallsSdkCompat; -import com.android.dialer.database.VoicemailArchiveContract; -import com.android.dialer.util.AsyncTaskExecutor; -import com.android.dialer.util.AsyncTaskExecutors; -import com.android.dialer.util.PhoneNumberUtil; -import com.android.dialer.util.TelecomUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; - -public class CallLogAsyncTaskUtil { - private static String TAG = CallLogAsyncTaskUtil.class.getSimpleName(); - - /** The enumeration of {@link AsyncTask} objects used in this class. */ - public enum Tasks { - DELETE_VOICEMAIL, - DELETE_CALL, - DELETE_BLOCKED_CALL, - MARK_VOICEMAIL_READ, - MARK_CALL_READ, - GET_CALL_DETAILS, - UPDATE_DURATION - } - - private static final class CallDetailQuery { - - private static final String[] CALL_LOG_PROJECTION_INTERNAL = new String[] { - CallLog.Calls.DATE, - CallLog.Calls.DURATION, - CallLog.Calls.NUMBER, - CallLog.Calls.TYPE, - CallLog.Calls.COUNTRY_ISO, - CallLog.Calls.GEOCODED_LOCATION, - CallLog.Calls.NUMBER_PRESENTATION, - CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, - CallLog.Calls.PHONE_ACCOUNT_ID, - CallLog.Calls.FEATURES, - CallLog.Calls.DATA_USAGE, - CallLog.Calls.TRANSCRIPTION - }; - public static final String[] CALL_LOG_PROJECTION; - - static final int DATE_COLUMN_INDEX = 0; - static final int DURATION_COLUMN_INDEX = 1; - static final int NUMBER_COLUMN_INDEX = 2; - static final int CALL_TYPE_COLUMN_INDEX = 3; - static final int COUNTRY_ISO_COLUMN_INDEX = 4; - static final int GEOCODED_LOCATION_COLUMN_INDEX = 5; - static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6; - static final int ACCOUNT_COMPONENT_NAME = 7; - static final int ACCOUNT_ID = 8; - static final int FEATURES = 9; - static final int DATA_USAGE = 10; - static final int TRANSCRIPTION_COLUMN_INDEX = 11; - static final int POST_DIAL_DIGITS = 12; - static final int VIA_NUMBER = 13; - - static { - ArrayList<String> projectionList = new ArrayList<>(); - projectionList.addAll(Arrays.asList(CALL_LOG_PROJECTION_INTERNAL)); - if (CompatUtils.isNCompatible()) { - projectionList.add(CallsSdkCompat.POST_DIAL_DIGITS); - projectionList.add(CallsSdkCompat.VIA_NUMBER); - } - projectionList.trimToSize(); - CALL_LOG_PROJECTION = projectionList.toArray(new String[projectionList.size()]); - } - } - - private static class CallLogDeleteBlockedCallQuery { - static final String[] PROJECTION = new String[] { - CallLog.Calls._ID, - CallLog.Calls.DATE - }; - - static final int ID_COLUMN_INDEX = 0; - static final int DATE_COLUMN_INDEX = 1; - } - - public interface CallLogAsyncTaskListener { - void onDeleteCall(); - void onDeleteVoicemail(); - void onGetCallDetails(PhoneCallDetails[] details); - } - - public interface OnCallLogQueryFinishedListener { - void onQueryFinished(boolean hasEntry); - } - - // Try to identify if a call log entry corresponds to a number which was blocked. We match by - // by comparing its creation time to the time it was added in the InCallUi and seeing if they - // fall within a certain threshold. - private static final int MATCH_BLOCKED_CALL_THRESHOLD_MS = 3000; - - private static AsyncTaskExecutor sAsyncTaskExecutor; - - private static void initTaskExecutor() { - sAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); - } - - public static void getCallDetails( - final Context context, - final Uri[] callUris, - final CallLogAsyncTaskListener callLogAsyncTaskListener) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.GET_CALL_DETAILS, - new AsyncTask<Void, Void, PhoneCallDetails[]>() { - @Override - public PhoneCallDetails[] doInBackground(Void... params) { - // TODO: All calls correspond to the same person, so make a single lookup. - final int numCalls = callUris.length; - PhoneCallDetails[] details = new PhoneCallDetails[numCalls]; - try { - for (int index = 0; index < numCalls; ++index) { - details[index] = - getPhoneCallDetailsForUri(context, callUris[index]); - } - return details; - } catch (IllegalArgumentException e) { - // Something went wrong reading in our primary data. - Log.w(TAG, "Invalid URI starting call details", e); - return null; - } - } - - @Override - public void onPostExecute(PhoneCallDetails[] phoneCallDetails) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onGetCallDetails(phoneCallDetails); - } - } - }); - } - - /** - * Return the phone call details for a given call log URI. - */ - private static PhoneCallDetails getPhoneCallDetailsForUri(Context context, Uri callUri) { - Cursor cursor = context.getContentResolver().query( - callUri, CallDetailQuery.CALL_LOG_PROJECTION, null, null, null); - - try { - if (cursor == null || !cursor.moveToFirst()) { - throw new IllegalArgumentException("Cannot find content: " + callUri); - } - - // Read call log. - final String countryIso = cursor.getString(CallDetailQuery.COUNTRY_ISO_COLUMN_INDEX); - final String number = cursor.getString(CallDetailQuery.NUMBER_COLUMN_INDEX); - final String postDialDigits = CompatUtils.isNCompatible() - ? cursor.getString(CallDetailQuery.POST_DIAL_DIGITS) : ""; - final String viaNumber = CompatUtils.isNCompatible() ? - cursor.getString(CallDetailQuery.VIA_NUMBER) : ""; - final int numberPresentation = - cursor.getInt(CallDetailQuery.NUMBER_PRESENTATION_COLUMN_INDEX); - - final PhoneAccountHandle accountHandle = PhoneAccountUtils.getAccount( - cursor.getString(CallDetailQuery.ACCOUNT_COMPONENT_NAME), - cursor.getString(CallDetailQuery.ACCOUNT_ID)); - - // If this is not a regular number, there is no point in looking it up in the contacts. - ContactInfoHelper contactInfoHelper = - new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)); - boolean isVoicemail = PhoneNumberUtil.isVoicemailNumber(context, accountHandle, number); - boolean shouldLookupNumber = - PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemail; - ContactInfo info = ContactInfo.EMPTY; - - if (shouldLookupNumber) { - ContactInfo lookupInfo = contactInfoHelper.lookupNumber(number, countryIso); - info = lookupInfo != null ? lookupInfo : ContactInfo.EMPTY; - } - - PhoneCallDetails details = new PhoneCallDetails( - context, number, numberPresentation, info.formattedNumber, - postDialDigits, isVoicemail); - - details.viaNumber = viaNumber; - details.accountHandle = accountHandle; - details.contactUri = info.lookupUri; - details.namePrimary = info.name; - details.nameAlternative = info.nameAlternative; - details.numberType = info.type; - details.numberLabel = info.label; - details.photoUri = info.photoUri; - details.sourceType = info.sourceType; - details.objectId = info.objectId; - - details.callTypes = new int[] { - cursor.getInt(CallDetailQuery.CALL_TYPE_COLUMN_INDEX) - }; - details.date = cursor.getLong(CallDetailQuery.DATE_COLUMN_INDEX); - details.duration = cursor.getLong(CallDetailQuery.DURATION_COLUMN_INDEX); - details.features = cursor.getInt(CallDetailQuery.FEATURES); - details.geocode = cursor.getString(CallDetailQuery.GEOCODED_LOCATION_COLUMN_INDEX); - details.transcription = cursor.getString(CallDetailQuery.TRANSCRIPTION_COLUMN_INDEX); - - details.countryIso = !TextUtils.isEmpty(countryIso) ? countryIso - : GeoUtil.getCurrentCountryIso(context); - - if (!cursor.isNull(CallDetailQuery.DATA_USAGE)) { - details.dataUsage = cursor.getLong(CallDetailQuery.DATA_USAGE); - } - - return details; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - - /** - * Delete specified calls from the call log. - * - * @param context The context. - * @param callIds String of the callIds to delete from the call log, delimited by commas (","). - * @param callLogAsyncTaskListener The listener to invoke after the entries have been deleted. - */ - public static void deleteCalls( - final Context context, - final String callIds, - final CallLogAsyncTaskListener callLogAsyncTaskListener) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.DELETE_CALL, new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... params) { - context.getContentResolver().delete( - TelecomUtil.getCallLogUri(context), - CallLog.Calls._ID + " IN (" + callIds + ")", null); - return null; - } - - @Override - public void onPostExecute(Void result) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onDeleteCall(); - } - } - }); - } - - /** - * Deletes the last call made by the number within a threshold of the call time added in the - * call log, assuming it is a blocked call for which no entry should be shown. - * - * @param context The context. - * @param number Number of blocked call, for which to delete the call log entry. - * @param timeAddedMs The time the number was added to InCall, in milliseconds. - * @param listener The listener to invoke after looking up for a call log entry matching the - * number and time added. - */ - public static void deleteBlockedCall( - final Context context, - final String number, - final long timeAddedMs, - final OnCallLogQueryFinishedListener listener) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.DELETE_BLOCKED_CALL, new AsyncTask<Void, Void, Long>() { - @Override - public Long doInBackground(Void... params) { - // First, lookup the call log entry of the most recent call with this number. - Cursor cursor = context.getContentResolver().query( - TelecomUtil.getCallLogUri(context), - CallLogDeleteBlockedCallQuery.PROJECTION, - CallLog.Calls.NUMBER + "= ?", - new String[] { number }, - CallLog.Calls.DATE + " DESC LIMIT 1"); - - // If match is found, delete this call log entry and return the call log entry id. - if (cursor.moveToFirst()) { - long creationTime = - cursor.getLong(CallLogDeleteBlockedCallQuery.DATE_COLUMN_INDEX); - if (timeAddedMs > creationTime - && timeAddedMs - creationTime < MATCH_BLOCKED_CALL_THRESHOLD_MS) { - long callLogEntryId = - cursor.getLong(CallLogDeleteBlockedCallQuery.ID_COLUMN_INDEX); - context.getContentResolver().delete( - TelecomUtil.getCallLogUri(context), - CallLog.Calls._ID + " IN (" + callLogEntryId + ")", - null); - return callLogEntryId; - } - } - return (long) -1; - } - - @Override - public void onPostExecute(Long callLogEntryId) { - if (listener != null) { - listener.onQueryFinished(callLogEntryId >= 0); - } - } - }); - } - - - public static void markVoicemailAsRead(final Context context, final Uri voicemailUri) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.MARK_VOICEMAIL_READ, new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... params) { - ContentValues values = new ContentValues(); - values.put(Voicemails.IS_READ, true); - context.getContentResolver().update( - voicemailUri, values, Voicemails.IS_READ + " = 0", null); - - Intent intent = new Intent(context, CallLogNotificationsService.class); - intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); - context.startService(intent); - return null; - } - }); - } - - public static void deleteVoicemail( - final Context context, - final Uri voicemailUri, - final CallLogAsyncTaskListener callLogAsyncTaskListener) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL, new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... params) { - context.getContentResolver().delete(voicemailUri, null, null); - return null; - } - - @Override - public void onPostExecute(Void result) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onDeleteVoicemail(); - } - } - }); - } - - public static void markCallAsRead(final Context context, final long[] callIds) { - if (!PermissionsUtil.hasPhonePermissions(context)) { - return; - } - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.MARK_CALL_READ, new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... params) { - - StringBuilder where = new StringBuilder(); - where.append(CallLog.Calls.TYPE).append(" = ").append(CallLog.Calls.MISSED_TYPE); - where.append(" AND "); - - Long[] callIdLongs = new Long[callIds.length]; - for (int i = 0; i < callIds.length; i++) { - callIdLongs[i] = callIds[i]; - } - where.append(CallLog.Calls._ID).append( - " IN (" + TextUtils.join(",", callIdLongs) + ")"); - - ContentValues values = new ContentValues(1); - values.put(CallLog.Calls.IS_READ, "1"); - context.getContentResolver().update( - CallLog.Calls.CONTENT_URI, values, where.toString(), null); - return null; - } - }); - } - - /** - * Updates the duration of a voicemail call log entry if the duration given is greater than 0, - * and if if the duration currently in the database is less than or equal to 0 (non-existent). - */ - public static void updateVoicemailDuration( - final Context context, - final Uri voicemailUri, - final long duration) { - if (duration <= 0 || !PermissionsUtil.hasPhonePermissions(context)) { - return; - } - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.UPDATE_DURATION, new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... params) { - ContentResolver contentResolver = context.getContentResolver(); - Cursor cursor = contentResolver.query( - voicemailUri, - new String[] { VoicemailArchiveContract.VoicemailArchive.DURATION }, - null, null, null); - if (cursor != null && cursor.moveToFirst() && cursor.getInt( - cursor.getColumnIndex( - VoicemailArchiveContract.VoicemailArchive.DURATION)) <= 0) { - ContentValues values = new ContentValues(1); - values.put(CallLog.Calls.DURATION, duration); - context.getContentResolver().update(voicemailUri, values, null, null); - } - return null; - } - }); - } - - @VisibleForTesting - public static void resetForTest() { - sAsyncTaskExecutor = null; - } -} diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java deleted file mode 100644 index 67b72a5a3..000000000 --- a/src/com/android/dialer/calllog/CallLogFragment.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.app.Activity; -import android.app.Fragment; -import android.app.KeyguardManager; -import android.content.ContentResolver; -import android.content.Context; -import android.content.pm.PackageManager; -import android.database.ContentObserver; -import android.database.Cursor; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract; -import android.support.annotation.Nullable; -import android.support.v13.app.FragmentCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.R; -import com.android.dialer.list.ListsFragment; -import com.android.dialer.util.EmptyLoader; -import com.android.dialer.voicemail.VoicemailPlaybackPresenter; -import com.android.dialer.widget.EmptyContentView; -import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; -import com.android.dialerbind.ObjectFactory; - -import static android.Manifest.permission.READ_CALL_LOG; - -/** - * Displays a list of call log entries. To filter for a particular kind of call - * (all, missed or voicemails), specify it in the constructor. - */ -public class CallLogFragment extends Fragment implements CallLogQueryHandler.Listener, - CallLogAdapter.CallFetcher, OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - private static final String TAG = "CallLogFragment"; - - /** - * ID of the empty loader to defer other fragments. - */ - private static final int EMPTY_LOADER_ID = 0; - - private static final String KEY_FILTER_TYPE = "filter_type"; - private static final String KEY_LOG_LIMIT = "log_limit"; - private static final String KEY_DATE_LIMIT = "date_limit"; - private static final String KEY_IS_CALL_LOG_ACTIVITY = "is_call_log_activity"; - - // No limit specified for the number of logs to show; use the CallLogQueryHandler's default. - private static final int NO_LOG_LIMIT = -1; - // No date-based filtering. - private static final int NO_DATE_LIMIT = 0; - - private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1; - - private static final int EVENT_UPDATE_DISPLAY = 1; - - private static final long MILLIS_IN_MINUTE = 60 * 1000; - - private RecyclerView mRecyclerView; - private LinearLayoutManager mLayoutManager; - private CallLogAdapter mAdapter; - private CallLogQueryHandler mCallLogQueryHandler; - private boolean mScrollToTop; - - - private EmptyContentView mEmptyListView; - private KeyguardManager mKeyguardManager; - - private boolean mEmptyLoaderRunning; - private boolean mCallLogFetched; - private boolean mVoicemailStatusFetched; - - private final Handler mDisplayUpdateHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_UPDATE_DISPLAY: - refreshData(); - rescheduleDisplayUpdate(); - break; - } - } - }; - - private final Handler mHandler = new Handler(); - - protected class CustomContentObserver extends ContentObserver { - public CustomContentObserver() { - super(mHandler); - } - @Override - public void onChange(boolean selfChange) { - mRefreshDataRequired = true; - } - } - - // See issue 6363009 - private final ContentObserver mCallLogObserver = new CustomContentObserver(); - private final ContentObserver mContactsObserver = new CustomContentObserver(); - private boolean mRefreshDataRequired = true; - - private boolean mHasReadCallLogPermission = false; - - // Exactly same variable is in Fragment as a package private. - private boolean mMenuVisible = true; - - // Default to all calls. - private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL; - - // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler} - // will be used. - private int mLogLimit = NO_LOG_LIMIT; - - // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after - // the date filter are included. If zero, no date-based filtering occurs. - private long mDateLimit = NO_DATE_LIMIT; - - /* - * True if this instance of the CallLogFragment shown in the CallLogActivity. - */ - private boolean mIsCallLogActivity = false; - - public interface HostInterface { - public void showDialpad(); - } - - public CallLogFragment() { - this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT); - } - - public CallLogFragment(int filterType) { - this(filterType, NO_LOG_LIMIT); - } - - public CallLogFragment(int filterType, boolean isCallLogActivity) { - this(filterType, NO_LOG_LIMIT); - mIsCallLogActivity = isCallLogActivity; - } - - public CallLogFragment(int filterType, int logLimit) { - this(filterType, logLimit, NO_DATE_LIMIT); - } - - /** - * Creates a call log fragment, filtering to include only calls of the desired type, occurring - * after the specified date. - * @param filterType type of calls to include. - * @param dateLimit limits results to calls occurring on or after the specified date. - */ - public CallLogFragment(int filterType, long dateLimit) { - this(filterType, NO_LOG_LIMIT, dateLimit); - } - - /** - * Creates a call log fragment, filtering to include only calls of the desired type, occurring - * after the specified date. Also provides a means to limit the number of results returned. - * @param filterType type of calls to include. - * @param logLimit limits the number of results to return. - * @param dateLimit limits results to calls occurring on or after the specified date. - */ - public CallLogFragment(int filterType, int logLimit, long dateLimit) { - mCallTypeFilter = filterType; - mLogLimit = logLimit; - mDateLimit = dateLimit; - } - - @Override - public void onCreate(Bundle state) { - super.onCreate(state); - if (state != null) { - mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter); - mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit); - mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit); - mIsCallLogActivity = state.getBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity); - } - - final Activity activity = getActivity(); - final ContentResolver resolver = activity.getContentResolver(); - String currentCountryIso = GeoUtil.getCurrentCountryIso(activity); - mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit); - mKeyguardManager = - (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); - resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver); - resolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, - mContactsObserver); - setHasOptionsMenu(true); - } - - /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */ - @Override - public boolean onCallsFetched(Cursor cursor) { - if (getActivity() == null || getActivity().isFinishing()) { - // Return false; we did not take ownership of the cursor - return false; - } - mAdapter.invalidatePositions(); - mAdapter.setLoading(false); - mAdapter.changeCursor(cursor); - // This will update the state of the "Clear call log" menu item. - getActivity().invalidateOptionsMenu(); - - boolean showListView = cursor != null && cursor.getCount() > 0; - mRecyclerView.setVisibility(showListView ? View.VISIBLE : View.GONE); - mEmptyListView.setVisibility(!showListView ? View.VISIBLE : View.GONE); - - if (mScrollToTop) { - // The smooth-scroll animation happens over a fixed time period. - // As a result, if it scrolls through a large portion of the list, - // each frame will jump so far from the previous one that the user - // will not experience the illusion of downward motion. Instead, - // if we're not already near the top of the list, we instantly jump - // near the top, and animate from there. - if (mLayoutManager.findFirstVisibleItemPosition() > 5) { - // TODO: Jump to near the top, then begin smooth scroll. - mRecyclerView.smoothScrollToPosition(0); - } - // Workaround for framework issue: the smooth-scroll doesn't - // occur if setSelection() is called immediately before. - mHandler.post(new Runnable() { - @Override - public void run() { - if (getActivity() == null || getActivity().isFinishing()) { - return; - } - mRecyclerView.smoothScrollToPosition(0); - } - }); - - mScrollToTop = false; - } - mCallLogFetched = true; - destroyEmptyLoaderIfAllDataFetched(); - return true; - } - - /** - * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider. - */ - @Override - public void onVoicemailStatusFetched(Cursor statusCursor) { - Activity activity = getActivity(); - if (activity == null || activity.isFinishing()) { - return; - } - - mVoicemailStatusFetched = true; - destroyEmptyLoaderIfAllDataFetched(); - } - - private void destroyEmptyLoaderIfAllDataFetched() { - if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) { - mEmptyLoaderRunning = false; - getLoaderManager().destroyLoader(EMPTY_LOADER_ID); - } - } - - @Override - public void onVoicemailUnreadCountFetched(Cursor cursor) {} - - @Override - public void onMissedCallsUnreadCountFetched(Cursor cursor) {} - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { - View view = inflater.inflate(R.layout.call_log_fragment, container, false); - setupView(view, null); - return view; - } - - protected void setupView( - View view, @Nullable VoicemailPlaybackPresenter voicemailPlaybackPresenter) { - mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); - mRecyclerView.setHasFixedSize(true); - mLayoutManager = new LinearLayoutManager(getActivity()); - mRecyclerView.setLayoutManager(mLayoutManager); - mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view); - mEmptyListView.setImage(R.drawable.empty_call_log); - mEmptyListView.setActionClickedListener(this); - - int activityType = mIsCallLogActivity ? CallLogAdapter.ACTIVITY_TYPE_CALL_LOG : - CallLogAdapter.ACTIVITY_TYPE_DIALTACTS; - String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); - mAdapter = ObjectFactory.newCallLogAdapter( - getActivity(), - this, - new ContactInfoHelper(getActivity(), currentCountryIso), - voicemailPlaybackPresenter, - activityType); - mRecyclerView.setAdapter(mAdapter); - fetchCalls(); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - updateEmptyMessage(mCallTypeFilter); - mAdapter.onRestoreInstanceState(savedInstanceState); - } - - @Override - public void onStart() { - // Start the empty loader now to defer other fragments. We destroy it when both calllog - // and the voicemail status are fetched. - getLoaderManager().initLoader(EMPTY_LOADER_ID, null, - new EmptyLoader.Callback(getActivity())); - mEmptyLoaderRunning = true; - super.onStart(); - } - - @Override - public void onResume() { - super.onResume(); - final boolean hasReadCallLogPermission = - PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG); - if (!mHasReadCallLogPermission && hasReadCallLogPermission) { - // We didn't have the permission before, and now we do. Force a refresh of the call log. - // Note that this code path always happens on a fresh start, but mRefreshDataRequired - // is already true in that case anyway. - mRefreshDataRequired = true; - updateEmptyMessage(mCallTypeFilter); - } - - mHasReadCallLogPermission = hasReadCallLogPermission; - refreshData(); - mAdapter.onResume(); - - rescheduleDisplayUpdate(); - } - - @Override - public void onPause() { - cancelDisplayUpdate(); - mAdapter.onPause(); - super.onPause(); - } - - @Override - public void onStop() { - updateOnTransition(); - - super.onStop(); - } - - @Override - public void onDestroy() { - mAdapter.changeCursor(null); - - getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver); - getActivity().getContentResolver().unregisterContentObserver(mContactsObserver); - super.onDestroy(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter); - outState.putInt(KEY_LOG_LIMIT, mLogLimit); - outState.putLong(KEY_DATE_LIMIT, mDateLimit); - outState.putBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity); - - mAdapter.onSaveInstanceState(outState); - } - - @Override - public void fetchCalls() { - mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit); - if (!mIsCallLogActivity) { - ((ListsFragment) getParentFragment()).updateTabUnreadCounts(); - } - } - - private void updateEmptyMessage(int filterType) { - final Context context = getActivity(); - if (context == null) { - return; - } - - if (!PermissionsUtil.hasPermission(context, READ_CALL_LOG)) { - mEmptyListView.setDescription(R.string.permission_no_calllog); - mEmptyListView.setActionLabel(R.string.permission_single_turn_on); - return; - } - - final int messageId; - switch (filterType) { - case Calls.MISSED_TYPE: - messageId = R.string.call_log_missed_empty; - break; - case Calls.VOICEMAIL_TYPE: - messageId = R.string.call_log_voicemail_empty; - break; - case CallLogQueryHandler.CALL_TYPE_ALL: - messageId = R.string.call_log_all_empty; - break; - default: - throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: " - + filterType); - } - mEmptyListView.setDescription(messageId); - if (mIsCallLogActivity) { - mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL); - } else if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) { - mEmptyListView.setActionLabel(R.string.call_log_all_empty_action); - } - } - - CallLogAdapter getAdapter() { - return mAdapter; - } - - @Override - public void setMenuVisibility(boolean menuVisible) { - super.setMenuVisibility(menuVisible); - if (mMenuVisible != menuVisible) { - mMenuVisible = menuVisible; - if (!menuVisible) { - updateOnTransition(); - } else if (isResumed()) { - refreshData(); - } - } - } - - /** Requests updates to the data to be shown. */ - private void refreshData() { - // Prevent unnecessary refresh. - if (mRefreshDataRequired) { - // Mark all entries in the contact info cache as out of date, so they will be looked up - // again once being shown. - mAdapter.invalidateCache(); - mAdapter.setLoading(true); - - fetchCalls(); - mCallLogQueryHandler.fetchVoicemailStatus(); - mCallLogQueryHandler.fetchMissedCallsUnreadCount(); - updateOnTransition(); - mRefreshDataRequired = false; - } else { - // Refresh the display of the existing data to update the timestamp text descriptions. - mAdapter.notifyDataSetChanged(); - } - } - - /** - * Updates the voicemail notification state. - * - * TODO: Move to CallLogActivity - */ - private void updateOnTransition() { - // We don't want to update any call data when keyguard is on because the user has likely not - // seen the new calls yet. - // This might be called before onCreate() and thus we need to check null explicitly. - if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode() - && mCallTypeFilter == Calls.VOICEMAIL_TYPE) { - CallLogNotificationsHelper.updateVoicemailNotifications(getActivity()); - } - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) { - FragmentCompat.requestPermissions(this, new String[] {READ_CALL_LOG}, - READ_CALL_LOG_PERMISSION_REQUEST_CODE); - } else if (!mIsCallLogActivity) { - // Show dialpad if we are not in the call log activity. - ((HostInterface) activity).showDialpad(); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == READ_CALL_LOG_PERMISSION_REQUEST_CODE) { - if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) { - // Force a refresh of the data since we were missing the permission before this. - mRefreshDataRequired = true; - } - } - } - - /** - * Schedules an update to the relative call times (X mins ago). - */ - private void rescheduleDisplayUpdate() { - if (!mDisplayUpdateHandler.hasMessages(EVENT_UPDATE_DISPLAY)) { - long time = System.currentTimeMillis(); - // This value allows us to change the display relatively close to when the time changes - // from one minute to the next. - long millisUtilNextMinute = MILLIS_IN_MINUTE - (time % MILLIS_IN_MINUTE); - mDisplayUpdateHandler.sendEmptyMessageDelayed( - EVENT_UPDATE_DISPLAY, millisUtilNextMinute); - } - } - - /** - * Cancels any pending update requests to update the relative call times (X mins ago). - */ - private void cancelDisplayUpdate() { - mDisplayUpdateHandler.removeMessages(EVENT_UPDATE_DISPLAY); - } -} diff --git a/src/com/android/dialer/calllog/CallLogGroupBuilder.java b/src/com/android/dialer/calllog/CallLogGroupBuilder.java deleted file mode 100644 index aa45029c0..000000000 --- a/src/com/android/dialer/calllog/CallLogGroupBuilder.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import com.google.common.annotations.VisibleForTesting; - -import android.database.Cursor; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.text.format.Time; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.util.DateUtils; -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.dialer.util.AppCompatConstants; - -/** - * Groups together calls in the call log. The primary grouping attempts to group together calls - * to and from the same number into a single row on the call log. - * A secondary grouping assigns calls, grouped via the primary grouping, to "day groups". The day - * groups provide a means of identifying the calls which occurred "Today", "Yesterday", "Last week", - * or "Other". - * <p> - * This class is meant to be used in conjunction with {@link GroupingListAdapter}. - */ -public class CallLogGroupBuilder { - public interface GroupCreator { - - /** - * Defines the interface for adding a group to the call log. - * The primary group for a call log groups the calls together based on the number which was - * dialed. - * @param cursorPosition The starting position of the group in the cursor. - * @param size The size of the group. - */ - public void addGroup(int cursorPosition, int size); - - /** - * Defines the interface for tracking the day group each call belongs to. Calls in a call - * group are assigned the same day group as the first call in the group. The day group - * assigns calls to the buckets: Today, Yesterday, Last week, and Other - * - * @param rowId The row Id of the current call. - * @param dayGroup The day group the call belongs in. - */ - public void setDayGroup(long rowId, int dayGroup); - - /** - * Defines the interface for clearing the day groupings information on rebind/regroup. - */ - public void clearDayGroups(); - } - - /** - * Day grouping for call log entries used to represent no associated day group. Used primarily - * when retrieving the previous day group, but there is no previous day group (i.e. we are at - * the start of the list). - */ - public static final int DAY_GROUP_NONE = -1; - - /** Day grouping for calls which occurred today. */ - public static final int DAY_GROUP_TODAY = 0; - - /** Day grouping for calls which occurred yesterday. */ - public static final int DAY_GROUP_YESTERDAY = 1; - - /** Day grouping for calls which occurred before last week. */ - public static final int DAY_GROUP_OTHER = 2; - - /** Instance of the time object used for time calculations. */ - private static final Time TIME = new Time(); - - /** The object on which the groups are created. */ - private final GroupCreator mGroupCreator; - - public CallLogGroupBuilder(GroupCreator groupCreator) { - mGroupCreator = groupCreator; - } - - /** - * Finds all groups of adjacent entries in the call log which should be grouped together and - * calls {@link GroupCreator#addGroup(int, int)} on {@link #mGroupCreator} for each of - * them. - * <p> - * For entries that are not grouped with others, we do not need to create a group of size one. - * <p> - * It assumes that the cursor will not change during its execution. - * - * @see GroupingListAdapter#addGroups(Cursor) - */ - public void addGroups(Cursor cursor) { - final int count = cursor.getCount(); - if (count == 0) { - return; - } - - // Clear any previous day grouping information. - mGroupCreator.clearDayGroups(); - - // Get current system time, used for calculating which day group calls belong to. - long currentTime = System.currentTimeMillis(); - cursor.moveToFirst(); - - // Determine the day group for the first call in the cursor. - final long firstDate = cursor.getLong(CallLogQuery.DATE); - final long firstRowId = cursor.getLong(CallLogQuery.ID); - int groupDayGroup = getDayGroup(firstDate, currentTime); - mGroupCreator.setDayGroup(firstRowId, groupDayGroup); - - // Instantiate the group values to those of the first call in the cursor. - String groupNumber = cursor.getString(CallLogQuery.NUMBER); - String groupPostDialDigits = CompatUtils.isNCompatible() - ? cursor.getString(CallLogQuery.POST_DIAL_DIGITS) : ""; - String groupViaNumbers = CompatUtils.isNCompatible() - ? cursor.getString(CallLogQuery.VIA_NUMBER) : ""; - int groupCallType = cursor.getInt(CallLogQuery.CALL_TYPE); - String groupAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME); - String groupAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID); - int groupSize = 1; - - String number; - String numberPostDialDigits; - String numberViaNumbers; - int callType; - String accountComponentName; - String accountId; - - while (cursor.moveToNext()) { - // Obtain the values for the current call to group. - number = cursor.getString(CallLogQuery.NUMBER); - numberPostDialDigits = CompatUtils.isNCompatible() - ? cursor.getString(CallLogQuery.POST_DIAL_DIGITS) : ""; - numberViaNumbers = CompatUtils.isNCompatible() - ? cursor.getString(CallLogQuery.VIA_NUMBER) : ""; - callType = cursor.getInt(CallLogQuery.CALL_TYPE); - accountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME); - accountId = cursor.getString(CallLogQuery.ACCOUNT_ID); - - final boolean isSameNumber = equalNumbers(groupNumber, number); - final boolean isSamePostDialDigits = groupPostDialDigits.equals(numberPostDialDigits); - final boolean isSameViaNumbers = groupViaNumbers.equals(numberViaNumbers); - final boolean isSameAccount = isSameAccount( - groupAccountComponentName, accountComponentName, groupAccountId, accountId); - - // Group with the same number and account. Never group voicemails. Only group blocked - // calls with other blocked calls. - if (isSameNumber && isSameAccount && isSamePostDialDigits && isSameViaNumbers - && areBothNotVoicemail(callType, groupCallType) - && (areBothNotBlocked(callType, groupCallType) - || areBothBlocked(callType, groupCallType))) { - // Increment the size of the group to include the current call, but do not create - // the group until finding a call that does not match. - groupSize++; - } else { - // The call group has changed. Determine the day group for the new call group. - final long date = cursor.getLong(CallLogQuery.DATE); - groupDayGroup = getDayGroup(date, currentTime); - - // Create a group for the previous group of calls, which does not include the - // current call. - mGroupCreator.addGroup(cursor.getPosition() - groupSize, groupSize); - - // Start a new group; it will include at least the current call. - groupSize = 1; - - // Update the group values to those of the current call. - groupNumber = number; - groupPostDialDigits = numberPostDialDigits; - groupViaNumbers = numberViaNumbers; - groupCallType = callType; - groupAccountComponentName = accountComponentName; - groupAccountId = accountId; - } - - // Save the day group associated with the current call. - final long currentCallId = cursor.getLong(CallLogQuery.ID); - mGroupCreator.setDayGroup(currentCallId, groupDayGroup); - } - - // Create a group for the last set of calls. - mGroupCreator.addGroup(count - groupSize, groupSize); - } - - /** - * Group cursor entries by date, with only one entry per group. This is used for listing - * voicemails in the archive tab. - */ - public void addVoicemailGroups(Cursor cursor) { - if (cursor.getCount() == 0) { - return; - } - - // Clear any previous day grouping information. - mGroupCreator.clearDayGroups(); - - // Get current system time, used for calculating which day group calls belong to. - long currentTime = System.currentTimeMillis(); - - // Reset cursor to start before the first row - cursor.moveToPosition(-1); - - // Create an individual group for each voicemail - while (cursor.moveToNext()) { - mGroupCreator.addGroup(cursor.getPosition(), 1); - mGroupCreator.setDayGroup(cursor.getLong(CallLogQuery.ID), - getDayGroup(cursor.getLong(CallLogQuery.DATE), currentTime)); - - } - } - - @VisibleForTesting - boolean equalNumbers(String number1, String number2) { - if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) { - return compareSipAddresses(number1, number2); - } else { - return PhoneNumberUtils.compare(number1, number2); - } - } - - private boolean isSameAccount(String name1, String name2, String id1, String id2) { - return TextUtils.equals(name1, name2) && TextUtils.equals(id1, id2); - } - - @VisibleForTesting - boolean compareSipAddresses(String number1, String number2) { - if (number1 == null || number2 == null) return number1 == number2; - - int index1 = number1.indexOf('@'); - final String userinfo1; - final String rest1; - if (index1 != -1) { - userinfo1 = number1.substring(0, index1); - rest1 = number1.substring(index1); - } else { - userinfo1 = number1; - rest1 = ""; - } - - int index2 = number2.indexOf('@'); - final String userinfo2; - final String rest2; - if (index2 != -1) { - userinfo2 = number2.substring(0, index2); - rest2 = number2.substring(index2); - } else { - userinfo2 = number2; - rest2 = ""; - } - - return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2); - } - - /** - * Given a call date and the current date, determine which date group the call belongs in. - * - * @param date The call date. - * @param now The current date. - * @return The date group the call belongs in. - */ - private int getDayGroup(long date, long now) { - int days = DateUtils.getDayDifference(TIME, date, now); - - if (days == 0) { - return DAY_GROUP_TODAY; - } else if (days == 1) { - return DAY_GROUP_YESTERDAY; - } else { - return DAY_GROUP_OTHER; - } - } - - private boolean areBothNotVoicemail(int callType, int groupCallType) { - return callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE - && groupCallType != AppCompatConstants.CALLS_VOICEMAIL_TYPE; - } - - private boolean areBothNotBlocked(int callType, int groupCallType) { - return callType != AppCompatConstants.CALLS_BLOCKED_TYPE - && groupCallType != AppCompatConstants.CALLS_BLOCKED_TYPE; - } - - private boolean areBothBlocked(int callType, int groupCallType) { - return callType == AppCompatConstants.CALLS_BLOCKED_TYPE - && groupCallType == AppCompatConstants.CALLS_BLOCKED_TYPE; - } -} diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java deleted file mode 100644 index 07e2bb425..000000000 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.res.Resources; -import android.provider.CallLog.Calls; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.util.Log; - -import com.android.dialer.PhoneCallDetails; -import com.android.dialer.util.AppCompatConstants; -import com.android.dialer.R; -import com.android.dialer.calllog.calllogcache.CallLogCache; - -/** - * Helper class to fill in the views of a call log entry. - */ -/* package */class CallLogListItemHelper { - private static final String TAG = "CallLogListItemHelper"; - - /** Helper for populating the details of a phone call. */ - private final PhoneCallDetailsHelper mPhoneCallDetailsHelper; - /** Resources to look up strings. */ - private final Resources mResources; - private final CallLogCache mCallLogCache; - - /** - * Creates a new helper instance. - * - * @param phoneCallDetailsHelper used to set the details of a phone call - * @param resources The object from which resources can be retrieved - * @param callLogCache A cache for values retrieved from telecom/telephony - */ - public CallLogListItemHelper( - PhoneCallDetailsHelper phoneCallDetailsHelper, - Resources resources, - CallLogCache callLogCache) { - mPhoneCallDetailsHelper = phoneCallDetailsHelper; - mResources = resources; - mCallLogCache = callLogCache; - } - - /** - * Sets the name, label, and number for a contact. - * - * @param views the views to populate - * @param details the details of a phone call needed to fill in the data - */ - public void setPhoneCallDetails( - CallLogListItemViewHolder views, - PhoneCallDetails details) { - mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details); - - // Set the accessibility text for the contact badge - views.quickContactView.setContentDescription(getContactBadgeDescription(details)); - - // Set the primary action accessibility description - views.primaryActionView.setContentDescription(getCallDescription(details)); - - // Cache name or number of caller. Used when setting the content descriptions of buttons - // when the actions ViewStub is inflated. - views.nameOrNumber = getNameOrNumber(details); - - // The call type or Location associated with the call. Use when setting text for a - // voicemail log's call button - views.callTypeOrLocation = mPhoneCallDetailsHelper.getCallTypeOrLocation(details); - - // Cache country iso. Used for number filtering. - views.countryIso = details.countryIso; - } - - /** - * Sets the accessibility descriptions for the action buttons in the action button ViewStub. - * - * @param views The views associated with the current call log entry. - */ - public void setActionContentDescriptions(CallLogListItemViewHolder views) { - if (views.nameOrNumber == null) { - Log.e(TAG, "setActionContentDescriptions; name or number is null."); - } - - // Calling expandTemplate with a null parameter will cause a NullPointerException. - // Although we don't expect a null name or number, it is best to protect against it. - CharSequence nameOrNumber = views.nameOrNumber == null ? "" : views.nameOrNumber; - - views.videoCallButtonView.setContentDescription( - TextUtils.expandTemplate( - mResources.getString(R.string.description_video_call_action), - nameOrNumber)); - - views.createNewContactButtonView.setContentDescription( - TextUtils.expandTemplate( - mResources.getString(R.string.description_create_new_contact_action), - nameOrNumber)); - - views.addToExistingContactButtonView.setContentDescription( - TextUtils.expandTemplate( - mResources.getString(R.string.description_add_to_existing_contact_action), - nameOrNumber)); - - views.detailsButtonView.setContentDescription( - TextUtils.expandTemplate( - mResources.getString(R.string.description_details_action), nameOrNumber)); - } - - /** - * Returns the accessibility description for the contact badge for a call log entry. - * - * @param details Details of call. - * @return Accessibility description. - */ - private CharSequence getContactBadgeDescription(PhoneCallDetails details) { - return mResources.getString(R.string.description_contact_details, getNameOrNumber(details)); - } - - /** - * Returns the accessibility description of the "return call/call" action for a call log - * entry. - * Accessibility text is a combination of: - * {Voicemail Prefix}. {Number of Calls}. {Caller information} {Phone Account}. - * If most recent call is a voicemail, {Voicemail Prefix} is "New Voicemail.", otherwise "". - * - * If more than one call for the caller, {Number of Calls} is: - * "{number of calls} calls.", otherwise "". - * - * The {Caller Information} references the most recent call associated with the caller. - * For incoming calls: - * If missed call: Missed call from {Name/Number} {Call Type} {Call Time}. - * If answered call: Answered call from {Name/Number} {Call Type} {Call Time}. - * - * For outgoing calls: - * If outgoing: Call to {Name/Number] {Call Type} {Call Time}. - * - * Where: - * {Name/Number} is the name or number of the caller (as shown in call log). - * {Call type} is the contact phone number type (eg mobile) or location. - * {Call Time} is the time since the last call for the contact occurred. - * - * The {Phone Account} refers to the account/SIM through which the call was placed or received - * in multi-SIM devices. - * - * Examples: - * 3 calls. New Voicemail. Missed call from Joe Smith mobile 2 hours ago on SIM 1. - * - * 2 calls. Answered call from John Doe mobile 1 hour ago. - * - * @param context The application context. - * @param details Details of call. - * @return Return call action description. - */ - public CharSequence getCallDescription(PhoneCallDetails details) { - int lastCallType = getLastCallType(details.callTypes); - - // Get the name or number of the caller. - final CharSequence nameOrNumber = getNameOrNumber(details); - - // Get the call type or location of the caller; null if not applicable - final CharSequence typeOrLocation = mPhoneCallDetailsHelper.getCallTypeOrLocation(details); - - // Get the time/date of the call - final CharSequence timeOfCall = mPhoneCallDetailsHelper.getCallDate(details); - - SpannableStringBuilder callDescription = new SpannableStringBuilder(); - - // Add number of calls if more than one. - if (details.callTypes.length > 1) { - callDescription.append(mResources.getString(R.string.description_num_calls, - details.callTypes.length)); - } - - // If call had video capabilities, add the "Video Call" string. - if ((details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) { - callDescription.append(mResources.getString(R.string.description_video_call)); - } - - String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle); - CharSequence onAccountLabel = PhoneCallDetails.createAccountLabelDescription(mResources, - details.viaNumber, accountLabel); - - int stringID = getCallDescriptionStringID(details.callTypes, details.isRead); - callDescription.append( - TextUtils.expandTemplate( - mResources.getString(stringID), - nameOrNumber, - typeOrLocation == null ? "" : typeOrLocation, - timeOfCall, - onAccountLabel)); - - return callDescription; - } - - /** - * Determine the appropriate string ID to describe a call for accessibility purposes. - * - * @param callTypes The type of call corresponding to this entry or multiple if this entry - * represents multiple calls grouped together. - * @param isRead If the entry is a voicemail, {@code true} if the voicemail is read. - * @return String resource ID to use. - */ - public int getCallDescriptionStringID(int[] callTypes, boolean isRead) { - int lastCallType = getLastCallType(callTypes); - int stringID; - - if (lastCallType == AppCompatConstants.CALLS_MISSED_TYPE) { - //Message: Missed call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>, - //<PhoneAccount>. - stringID = R.string.description_incoming_missed_call; - } else if (lastCallType == AppCompatConstants.CALLS_INCOMING_TYPE) { - //Message: Answered call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>, - //<PhoneAccount>. - stringID = R.string.description_incoming_answered_call; - } else if (lastCallType == AppCompatConstants.CALLS_VOICEMAIL_TYPE) { - //Message: (Unread) [V/v]oicemail from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>, - //<PhoneAccount>. - stringID = isRead ? R.string.description_read_voicemail - : R.string.description_unread_voicemail; - } else { - //Message: Call to <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>, <PhoneAccount>. - stringID = R.string.description_outgoing_call; - } - return stringID; - } - - /** - * Determine the call type for the most recent call. - * @param callTypes Call types to check. - * @return Call type. - */ - private int getLastCallType(int[] callTypes) { - if (callTypes.length > 0) { - return callTypes[0]; - } else { - return Calls.MISSED_TYPE; - } - } - - /** - * Return the name or number of the caller specified by the details. - * @param details Call details - * @return the name (if known) of the caller, otherwise the formatted number. - */ - private CharSequence getNameOrNumber(PhoneCallDetails details) { - final CharSequence recipient; - if (!TextUtils.isEmpty(details.getPreferredName())) { - recipient = details.getPreferredName(); - } else { - recipient = details.displayNumber + details.postDialDigits; - } - return recipient; - } -} diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java deleted file mode 100644 index baf2e1ab5..000000000 --- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java +++ /dev/null @@ -1,732 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; -import android.telecom.PhoneAccountHandle; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import android.view.ContextMenu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewStub; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.QuickContactBadge; -import android.widget.TextView; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.ClipboardUtils; -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.PhoneNumberUtilsCompat; -import com.android.contacts.common.dialog.CallSubjectDialog; -import com.android.contacts.common.testing.NeededForTesting; -import com.android.contacts.common.util.UriUtils; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.R; -import com.android.dialer.calllog.calllogcache.CallLogCache; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.filterednumber.BlockNumberDialogFragment; -import com.android.dialer.filterednumber.FilteredNumbersUtil; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.service.ExtendedBlockingButtonRenderer; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.PhoneNumberUtil; -import com.android.dialer.voicemail.VoicemailPlaybackLayout; -import com.android.dialer.voicemail.VoicemailPlaybackPresenter; -import com.android.dialerbind.ObjectFactory; -import com.google.common.collect.Lists; - -import java.util.List; - -/** - * This is an object containing references to views contained by the call log list item. This - * improves performance by reducing the frequency with which we need to find views by IDs. - * - * This object also contains UI logic pertaining to the view, to isolate it from the CallLogAdapter. - */ -public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener, MenuItem.OnMenuItemClickListener, - View.OnCreateContextMenuListener { - - /** The root view of the call log list item */ - public final View rootView; - /** The quick contact badge for the contact. */ - public final QuickContactBadge quickContactView; - /** The primary action view of the entry. */ - public final View primaryActionView; - /** The details of the phone call. */ - public final PhoneCallDetailsViews phoneCallDetailsViews; - /** The text of the header for a day grouping. */ - public final TextView dayGroupHeader; - /** The view containing the details for the call log row, including the action buttons. */ - public final CardView callLogEntryView; - /** The actionable view which places a call to the number corresponding to the call log row. */ - public final ImageView primaryActionButtonView; - - /** The view containing call log item actions. Null until the ViewStub is inflated. */ - public View actionsView; - /** The button views below are assigned only when the action section is expanded. */ - public VoicemailPlaybackLayout voicemailPlaybackView; - public View callButtonView; - public View videoCallButtonView; - public View createNewContactButtonView; - public View addToExistingContactButtonView; - public View sendMessageView; - public View detailsButtonView; - public View callWithNoteButtonView; - public ImageView workIconView; - - /** - * The row Id for the first call associated with the call log entry. Used as a key for the - * map used to track which call log entries have the action button section expanded. - */ - public long rowId; - - /** - * The call Ids for the calls represented by the current call log entry. Used when the user - * deletes a call log entry. - */ - public long[] callIds; - - /** - * The callable phone number for the current call log entry. Cached here as the call back - * intent is set only when the actions ViewStub is inflated. - */ - public String number; - - /** - * The post-dial numbers that are dialed following the phone number. - */ - public String postDialDigits; - - /** - * The formatted phone number to display. - */ - public String displayNumber; - - /** - * The phone number presentation for the current call log entry. Cached here as the call back - * intent is set only when the actions ViewStub is inflated. - */ - public int numberPresentation; - - /** - * The type of the phone number (e.g. main, work, etc). - */ - public String numberType; - - /** - * The country iso for the call. Cached here as the call back - * intent is set only when the actions ViewStub is inflated. - */ - public String countryIso; - - /** - * The type of call for the current call log entry. Cached here as the call back - * intent is set only when the actions ViewStub is inflated. - */ - public int callType; - - /** - * ID for blocked numbers database. - * Set when context menu is created, if the number is blocked. - */ - public Integer blockId; - - /** - * The account for the current call log entry. Cached here as the call back - * intent is set only when the actions ViewStub is inflated. - */ - public PhoneAccountHandle accountHandle; - - /** - * If the call has an associated voicemail message, the URI of the voicemail message for - * playback. Cached here as the voicemail intent is only set when the actions ViewStub is - * inflated. - */ - public String voicemailUri; - - /** - * The name or number associated with the call. Cached here for use when setting content - * descriptions on buttons in the actions ViewStub when it is inflated. - */ - public CharSequence nameOrNumber; - - /** - * The call type or Location associated with the call. Cached here for use when setting text - * for a voicemail log's call button - */ - public CharSequence callTypeOrLocation; - - /** - * Whether this row is for a business or not. - */ - public boolean isBusiness; - - /** - * The contact info for the contact displayed in this list item. - */ - public ContactInfo info; - - /** - * Whether the current log entry is a blocked number or not. Used in updatePhoto() - */ - public boolean isBlocked; - - /** - * Whether this is the archive tab or not. - */ - public final boolean isArchiveTab; - - private final Context mContext; - private final CallLogCache mCallLogCache; - private final CallLogListItemHelper mCallLogListItemHelper; - private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; - private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - - private final BlockNumberDialogFragment.Callback mFilteredNumberDialogCallback; - - private final int mPhotoSize; - private ViewStub mExtendedBlockingViewStub; - private final ExtendedBlockingButtonRenderer mExtendedBlockingButtonRenderer; - - private View.OnClickListener mExpandCollapseListener; - private boolean mVoicemailPrimaryActionButtonClicked; - - private CallLogListItemViewHolder( - Context context, - ExtendedBlockingButtonRenderer.Listener eventListener, - View.OnClickListener expandCollapseListener, - CallLogCache callLogCache, - CallLogListItemHelper callLogListItemHelper, - VoicemailPlaybackPresenter voicemailPlaybackPresenter, - FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, - BlockNumberDialogFragment.Callback filteredNumberDialogCallback, - View rootView, - QuickContactBadge quickContactView, - View primaryActionView, - PhoneCallDetailsViews phoneCallDetailsViews, - CardView callLogEntryView, - TextView dayGroupHeader, - ImageView primaryActionButtonView, - boolean isArchiveTab) { - super(rootView); - - mContext = context; - mExpandCollapseListener = expandCollapseListener; - mCallLogCache = callLogCache; - mCallLogListItemHelper = callLogListItemHelper; - mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; - mFilteredNumberAsyncQueryHandler = filteredNumberAsyncQueryHandler; - mFilteredNumberDialogCallback = filteredNumberDialogCallback; - - this.rootView = rootView; - this.quickContactView = quickContactView; - this.primaryActionView = primaryActionView; - this.phoneCallDetailsViews = phoneCallDetailsViews; - this.callLogEntryView = callLogEntryView; - this.dayGroupHeader = dayGroupHeader; - this.primaryActionButtonView = primaryActionButtonView; - this.workIconView = (ImageView) rootView.findViewById(R.id.work_profile_icon); - this.isArchiveTab = isArchiveTab; - Resources resources = mContext.getResources(); - mPhotoSize = mContext.getResources().getDimensionPixelSize(R.dimen.contact_photo_size); - - // Set text height to false on the TextViews so they don't have extra padding. - phoneCallDetailsViews.nameView.setElegantTextHeight(false); - phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false); - - quickContactView.setOverlay(null); - if (CompatUtils.hasPrioritizedMimeType()) { - quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); - } - primaryActionButtonView.setOnClickListener(this); - primaryActionView.setOnClickListener(mExpandCollapseListener); - primaryActionView.setOnCreateContextMenuListener(this); - mExtendedBlockingButtonRenderer = - ObjectFactory.newExtendedBlockingButtonRenderer(mContext, eventListener); - } - - public static CallLogListItemViewHolder create( - View view, - Context context, - ExtendedBlockingButtonRenderer.Listener eventListener, - View.OnClickListener expandCollapseListener, - CallLogCache callLogCache, - CallLogListItemHelper callLogListItemHelper, - VoicemailPlaybackPresenter voicemailPlaybackPresenter, - FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, - BlockNumberDialogFragment.Callback filteredNumberDialogCallback, - boolean isArchiveTab) { - - return new CallLogListItemViewHolder( - context, - eventListener, - expandCollapseListener, - callLogCache, - callLogListItemHelper, - voicemailPlaybackPresenter, - filteredNumberAsyncQueryHandler, - filteredNumberDialogCallback, - view, - (QuickContactBadge) view.findViewById(R.id.quick_contact_photo), - view.findViewById(R.id.primary_action_view), - PhoneCallDetailsViews.fromView(view), - (CardView) view.findViewById(R.id.call_log_row), - (TextView) view.findViewById(R.id.call_log_day_group_label), - (ImageView) view.findViewById(R.id.primary_action_button), - isArchiveTab); - } - - @Override - public void onCreateContextMenu( - final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - if (TextUtils.isEmpty(number)) { - return; - } - - if (callType == CallLog.Calls.VOICEMAIL_TYPE) { - menu.setHeaderTitle(mContext.getResources().getText(R.string.voicemail)); - } else { - menu.setHeaderTitle(PhoneNumberUtilsCompat.createTtsSpannable( - BidiFormatter.getInstance().unicodeWrap(number, TextDirectionHeuristics.LTR))); - } - - menu.add(ContextMenu.NONE, R.id.context_menu_copy_to_clipboard, ContextMenu.NONE, - R.string.action_copy_number_text) - .setOnMenuItemClickListener(this); - - // The edit number before call does not show up if any of the conditions apply: - // 1) Number cannot be called - // 2) Number is the voicemail number - // 3) Number is a SIP address - - if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) - && !mCallLogCache.isVoicemailNumber(accountHandle, number) - && !PhoneNumberUtil.isSipNumber(number)) { - menu.add(ContextMenu.NONE, R.id.context_menu_edit_before_call, ContextMenu.NONE, - R.string.action_edit_number_before_call) - .setOnMenuItemClickListener(this); - } - - if (callType == CallLog.Calls.VOICEMAIL_TYPE - && phoneCallDetailsViews.voicemailTranscriptionView.length() > 0) { - menu.add(ContextMenu.NONE, R.id.context_menu_copy_transcript_to_clipboard, - ContextMenu.NONE, R.string.copy_transcript_text) - .setOnMenuItemClickListener(this); - } - - if (FilteredNumberCompat.canAttemptBlockOperations(mContext) - && FilteredNumbersUtil.canBlockNumber(mContext, number, countryIso)) { - mFilteredNumberAsyncQueryHandler.isBlockedNumber( - new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - blockId = id; - int blockTitleId = blockId == null ? R.string.action_block_number - : R.string.action_unblock_number; - final MenuItem blockItem = menu.add( - ContextMenu.NONE, - R.id.context_menu_block_number, - ContextMenu.NONE, - blockTitleId); - blockItem.setOnMenuItemClickListener( - CallLogListItemViewHolder.this); - } - }, number, countryIso); - } - - Logger.logScreenView(ScreenEvent.CALL_LOG_CONTEXT_MENU, (Activity) mContext); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - int resId = item.getItemId(); - if (resId == R.id.context_menu_block_number) { - FilteredNumberCompat - .showBlockNumberDialogFlow(mContext.getContentResolver(), blockId, number, - countryIso, displayNumber, R.id.floating_action_button_container, - ((Activity) mContext).getFragmentManager(), - mFilteredNumberDialogCallback); - return true; - } else if (resId == R.id.context_menu_copy_to_clipboard) { - ClipboardUtils.copyText(mContext, null, number, true); - return true; - } else if (resId == R.id.context_menu_copy_transcript_to_clipboard) { - ClipboardUtils.copyText(mContext, null, - phoneCallDetailsViews.voicemailTranscriptionView.getText(), true); - return true; - } else if (resId == R.id.context_menu_edit_before_call) { - final Intent intent = new Intent( - Intent.ACTION_DIAL, CallUtil.getCallUri(number)); - intent.setClass(mContext, DialtactsActivity.class); - DialerUtils.startActivityWithErrorToast(mContext, intent); - return true; - } - return false; - } - - /** - * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not - * inflated during initial binding, so click handlers, tags and accessibility text must be set - * here, if necessary. - */ - public void inflateActionViewStub() { - ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub); - if (stub != null) { - actionsView = stub.inflate(); - - voicemailPlaybackView = (VoicemailPlaybackLayout) actionsView - .findViewById(R.id.voicemail_playback_layout); - if (isArchiveTab) { - voicemailPlaybackView.hideArchiveButton(); - } - - - callButtonView = actionsView.findViewById(R.id.call_action); - callButtonView.setOnClickListener(this); - - videoCallButtonView = actionsView.findViewById(R.id.video_call_action); - videoCallButtonView.setOnClickListener(this); - - createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action); - createNewContactButtonView.setOnClickListener(this); - - addToExistingContactButtonView = - actionsView.findViewById(R.id.add_to_existing_contact_action); - addToExistingContactButtonView.setOnClickListener(this); - - sendMessageView = actionsView.findViewById(R.id.send_message_action); - sendMessageView.setOnClickListener(this); - - detailsButtonView = actionsView.findViewById(R.id.details_action); - detailsButtonView.setOnClickListener(this); - - callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action); - callWithNoteButtonView.setOnClickListener(this); - - mExtendedBlockingViewStub = - (ViewStub) actionsView.findViewById(R.id.extended_blocking_actions_container); - } - - bindActionButtons(); - } - - private void updatePrimaryActionButton(boolean isExpanded) { - if (!TextUtils.isEmpty(voicemailUri)) { - // Treat as voicemail list item; show play button if not expanded. - if (!isExpanded) { - primaryActionButtonView.setImageResource(R.drawable.ic_play_arrow_24dp); - primaryActionButtonView.setContentDescription(TextUtils.expandTemplate( - mContext.getString(R.string.description_voicemail_action), - nameOrNumber)); - primaryActionButtonView.setVisibility(View.VISIBLE); - } else { - primaryActionButtonView.setVisibility(View.GONE); - } - } else { - // Treat as normal list item; show call button, if possible. - if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation)) { - boolean isVoicemailNumber = - mCallLogCache.isVoicemailNumber(accountHandle, number); - if (isVoicemailNumber) { - // Call to generic voicemail number, in case there are multiple accounts. - primaryActionButtonView.setTag( - IntentProvider.getReturnVoicemailCallIntentProvider()); - } else { - primaryActionButtonView.setTag( - IntentProvider.getReturnCallIntentProvider(number + postDialDigits)); - } - - primaryActionButtonView.setContentDescription(TextUtils.expandTemplate( - mContext.getString(R.string.description_call_action), - nameOrNumber)); - primaryActionButtonView.setImageResource(R.drawable.ic_call_24dp); - primaryActionButtonView.setVisibility(View.VISIBLE); - } else { - primaryActionButtonView.setTag(null); - primaryActionButtonView.setVisibility(View.GONE); - } - } - } - - /** - * Binds text titles, click handlers and intents to the voicemail, details and callback action - * buttons. - */ - private void bindActionButtons() { - boolean canPlaceCallToNumber = PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation); - - if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) { - callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); - ((TextView) callButtonView.findViewById(R.id.call_action_text)) - .setText(TextUtils.expandTemplate( - mContext.getString(R.string.call_log_action_call), - nameOrNumber)); - TextView callTypeOrLocationView = ((TextView) callButtonView.findViewById( - R.id.call_type_or_location_text)); - if (callType == Calls.VOICEMAIL_TYPE && !TextUtils.isEmpty(callTypeOrLocation)) { - callTypeOrLocationView.setText(callTypeOrLocation); - callTypeOrLocationView.setVisibility(View.VISIBLE); - } else { - callTypeOrLocationView.setVisibility(View.GONE); - } - callButtonView.setVisibility(View.VISIBLE); - } else { - callButtonView.setVisibility(View.GONE); - } - - // If one of the calls had video capabilities, show the video call button. - if (mCallLogCache.isVideoEnabled() && canPlaceCallToNumber && - phoneCallDetailsViews.callTypeIcons.isVideoShown()) { - videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); - videoCallButtonView.setVisibility(View.VISIBLE); - } else { - videoCallButtonView.setVisibility(View.GONE); - } - - // For voicemail calls, show the voicemail playback layout; hide otherwise. - if (callType == Calls.VOICEMAIL_TYPE && mVoicemailPlaybackPresenter != null - && !TextUtils.isEmpty(voicemailUri)) { - voicemailPlaybackView.setVisibility(View.VISIBLE); - - Uri uri = Uri.parse(voicemailUri); - mVoicemailPlaybackPresenter.setPlaybackView( - voicemailPlaybackView, uri, mVoicemailPrimaryActionButtonClicked); - mVoicemailPrimaryActionButtonClicked = false; - // Only mark voicemail as read when not in archive tab - if (!isArchiveTab) { - CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); - } - } else { - voicemailPlaybackView.setVisibility(View.GONE); - } - - if (callType == Calls.VOICEMAIL_TYPE) { - detailsButtonView.setVisibility(View.GONE); - } else { - detailsButtonView.setVisibility(View.VISIBLE); - detailsButtonView.setTag( - IntentProvider.getCallDetailIntentProvider(rowId, callIds, null)); - } - - if (info != null && UriUtils.isEncodedContactUri(info.lookupUri)) { - createNewContactButtonView.setTag(IntentProvider.getAddContactIntentProvider( - info.lookupUri, info.name, info.number, info.type, true /* isNewContact */)); - createNewContactButtonView.setVisibility(View.VISIBLE); - - addToExistingContactButtonView.setTag(IntentProvider.getAddContactIntentProvider( - info.lookupUri, info.name, info.number, info.type, false /* isNewContact */)); - addToExistingContactButtonView.setVisibility(View.VISIBLE); - } else { - createNewContactButtonView.setVisibility(View.GONE); - addToExistingContactButtonView.setVisibility(View.GONE); - } - - if (canPlaceCallToNumber) { - sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number)); - sendMessageView.setVisibility(View.VISIBLE); - } else { - sendMessageView.setVisibility(View.GONE); - } - - mCallLogListItemHelper.setActionContentDescriptions(this); - - boolean supportsCallSubject = - mCallLogCache.doesAccountSupportCallSubject(accountHandle); - boolean isVoicemailNumber = - mCallLogCache.isVoicemailNumber(accountHandle, number); - callWithNoteButtonView.setVisibility( - supportsCallSubject && !isVoicemailNumber ? View.VISIBLE : View.GONE); - - if(mExtendedBlockingButtonRenderer != null){ - List<View> completeLogListItems = Lists.newArrayList( - createNewContactButtonView, - addToExistingContactButtonView, - sendMessageView, - callButtonView, - callWithNoteButtonView, - detailsButtonView, - voicemailPlaybackView); - - List<View> blockedNumberVisibleViews = Lists.newArrayList(detailsButtonView); - List<View> extendedBlockingVisibleViews = Lists.newArrayList(detailsButtonView); - - ExtendedBlockingButtonRenderer.ViewHolderInfo viewHolderInfo = - new ExtendedBlockingButtonRenderer.ViewHolderInfo( - completeLogListItems, - extendedBlockingVisibleViews, - blockedNumberVisibleViews, - number, - countryIso, - nameOrNumber.toString(), - displayNumber); - mExtendedBlockingButtonRenderer.setViewHolderInfo(viewHolderInfo); - - mExtendedBlockingButtonRenderer.render(mExtendedBlockingViewStub); - } - } - - /** - * Show or hide the action views, such as voicemail, details, and add contact. - * - * If the action views have never been shown yet for this view, inflate the view stub. - */ - public void showActions(boolean show) { - showOrHideVoicemailTranscriptionView(show); - - if (show) { - // Inflate the view stub if necessary, and wire up the event handlers. - inflateActionViewStub(); - - actionsView.setVisibility(View.VISIBLE); - actionsView.setAlpha(1.0f); - } else { - // When recycling a view, it is possible the actionsView ViewStub was previously - // inflated so we should hide it in this case. - if (actionsView != null) { - actionsView.setVisibility(View.GONE); - } - } - - updatePrimaryActionButton(show); - } - - public void showOrHideVoicemailTranscriptionView(boolean isExpanded) { - if (callType != Calls.VOICEMAIL_TYPE) { - return; - } - - final TextView view = phoneCallDetailsViews.voicemailTranscriptionView; - if (!isExpanded || TextUtils.isEmpty(view.getText())) { - view.setVisibility(View.GONE); - return; - } - view.setVisibility(View.VISIBLE); - } - - public void updatePhoto() { - quickContactView.assignContactUri(info.lookupUri); - - final boolean isVoicemail = mCallLogCache.isVoicemailNumber(accountHandle, number); - int contactType = ContactPhotoManager.TYPE_DEFAULT; - if (isVoicemail) { - contactType = ContactPhotoManager.TYPE_VOICEMAIL; - } else if (isBusiness) { - contactType = ContactPhotoManager.TYPE_BUSINESS; - } - - final String lookupKey = info.lookupUri != null - ? UriUtils.getLookupKeyFromUri(info.lookupUri) : null; - final String displayName = TextUtils.isEmpty(info.name) ? displayNumber : info.name; - final DefaultImageRequest request = new DefaultImageRequest( - displayName, lookupKey, contactType, true /* isCircular */); - - if (info.photoId == 0 && info.photoUri != null) { - ContactPhotoManager.getInstance(mContext).loadPhoto(quickContactView, info.photoUri, - mPhotoSize, false /* darkTheme */, true /* isCircular */, request); - } else { - ContactPhotoManager.getInstance(mContext).loadThumbnail(quickContactView, info.photoId, - false /* darkTheme */, true /* isCircular */, request); - } - - if (mExtendedBlockingButtonRenderer != null) { - mExtendedBlockingButtonRenderer.updatePhotoAndLabelIfNecessary( - number, - countryIso, - quickContactView, - phoneCallDetailsViews.callLocationAndDate); - } - } - - @Override - public void onClick(View view) { - if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) { - mVoicemailPrimaryActionButtonClicked = true; - mExpandCollapseListener.onClick(primaryActionView); - } else if (view.getId() == R.id.call_with_note_action) { - CallSubjectDialog.start( - (Activity) mContext, - info.photoId, - info.photoUri, - info.lookupUri, - (String) nameOrNumber /* top line of contact view in call subject dialog */, - isBusiness, - number, - TextUtils.isEmpty(info.name) ? null : displayNumber, /* second line of contact - view in dialog. */ - numberType, /* phone number type (e.g. mobile) in second line of contact view */ - accountHandle); - } else { - final IntentProvider intentProvider = (IntentProvider) view.getTag(); - if (intentProvider != null) { - final Intent intent = intentProvider.getIntent(mContext); - // See IntentProvider.getCallDetailIntentProvider() for why this may be null. - if (intent != null) { - DialerUtils.startActivityWithErrorToast(mContext, intent); - } - } - } - } - - @NeededForTesting - public static CallLogListItemViewHolder createForTest(Context context) { - Resources resources = context.getResources(); - CallLogCache callLogCache = - CallLogCache.getCallLogCache(context); - PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper( - context, resources, callLogCache); - - CallLogListItemViewHolder viewHolder = new CallLogListItemViewHolder( - context, - null, - null /* expandCollapseListener */, - callLogCache, - new CallLogListItemHelper(phoneCallDetailsHelper, resources, callLogCache), - null /* voicemailPlaybackPresenter */, - null /* filteredNumberAsyncQueryHandler */, - null /* filteredNumberDialogCallback */, - new View(context), - new QuickContactBadge(context), - new View(context), - PhoneCallDetailsViews.createForTest(context), - new CardView(context), - new TextView(context), - new ImageView(context), - false); - viewHolder.detailsButtonView = new TextView(context); - viewHolder.actionsView = new View(context); - viewHolder.voicemailPlaybackView = new VoicemailPlaybackLayout(context); - viewHolder.workIconView = new ImageButton(context); - return viewHolder; - } -}
\ No newline at end of file diff --git a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java deleted file mode 100644 index 9a5028460..000000000 --- a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import com.google.common.base.Strings; - -import android.Manifest; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract.PhoneLookup; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.R; -import com.android.dialer.util.TelecomUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * Helper class operating on call log notifications. - */ -public class CallLogNotificationsHelper { - private static final String TAG = "CallLogNotifHelper"; - private static CallLogNotificationsHelper sInstance; - - /** Returns the singleton instance of the {@link CallLogNotificationsHelper}. */ - public static CallLogNotificationsHelper getInstance(Context context) { - if (sInstance == null) { - ContentResolver contentResolver = context.getContentResolver(); - String countryIso = GeoUtil.getCurrentCountryIso(context); - sInstance = new CallLogNotificationsHelper(context, - createNewCallsQuery(context, contentResolver), - createNameLookupQuery(context, contentResolver), - new ContactInfoHelper(context, countryIso), - countryIso); - } - return sInstance; - } - - private final Context mContext; - private final NewCallsQuery mNewCallsQuery; - private final NameLookupQuery mNameLookupQuery; - private final ContactInfoHelper mContactInfoHelper; - private final String mCurrentCountryIso; - - CallLogNotificationsHelper(Context context, NewCallsQuery newCallsQuery, - NameLookupQuery nameLookupQuery, ContactInfoHelper contactInfoHelper, - String countryIso) { - mContext = context; - mNewCallsQuery = newCallsQuery; - mNameLookupQuery = nameLookupQuery; - mContactInfoHelper = contactInfoHelper; - mCurrentCountryIso = countryIso; - } - - /** - * Get all voicemails with the "new" flag set to 1. - * - * @return A list of NewCall objects where each object represents a new voicemail. - */ - @Nullable - public List<NewCall> getNewVoicemails() { - return mNewCallsQuery.query(Calls.VOICEMAIL_TYPE); - } - - /** - * Get all missed calls with the "new" flag set to 1. - * - * @return A list of NewCall objects where each object represents a new missed call. - */ - @Nullable - public List<NewCall> getNewMissedCalls() { - return mNewCallsQuery.query(Calls.MISSED_TYPE); - } - - /** - * Given a number and number information (presentation and country ISO), get the best name - * for display. If the name is empty but we have a special presentation, display that. - * Otherwise attempt to look it up in the database or the cache. - * If that fails, fall back to displaying the number. - */ - public String getName(@Nullable String number, int numberPresentation, - @Nullable String countryIso) { - return getContactInfo(number, numberPresentation, countryIso).name; - } - - /** - * Given a number and number information (presentation and country ISO), get - * {@link ContactInfo}. If the name is empty but we have a special presentation, display that. - * Otherwise attempt to look it up in the cache. - * If that fails, fall back to displaying the number. - */ - public ContactInfo getContactInfo(@Nullable String number, int numberPresentation, - @Nullable String countryIso) { - if (countryIso == null) { - countryIso = mCurrentCountryIso; - } - - number = Strings.nullToEmpty(number); - ContactInfo contactInfo = new ContactInfo(); - contactInfo.number = number; - contactInfo.formattedNumber = PhoneNumberUtils.formatNumber(number, countryIso); - // contactInfo.normalizedNumber is not PhoneNumberUtils.normalizeNumber. Read ContactInfo. - contactInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); - - // 1. Special number representation. - contactInfo.name = PhoneNumberDisplayUtil.getDisplayName( - mContext, - number, - numberPresentation, - false).toString(); - if (!TextUtils.isEmpty(contactInfo.name)) { - return contactInfo; - } - - // 2. Look it up in the cache. - ContactInfo cachedContactInfo = mContactInfoHelper.lookupNumber(number, countryIso); - - if (cachedContactInfo != null && !TextUtils.isEmpty(cachedContactInfo.name)) { - return cachedContactInfo; - } - - if (!TextUtils.isEmpty(contactInfo.formattedNumber)) { - // 3. If we cannot lookup the contact, use the formatted number instead. - contactInfo.name = contactInfo.formattedNumber; - } else if (!TextUtils.isEmpty(number)) { - // 4. If number can't be formatted, use number. - contactInfo.name = number; - } else { - // 5. Otherwise, it's unknown number. - contactInfo.name = mContext.getResources().getString(R.string.unknown); - } - return contactInfo; - } - - /** Removes the missed call notifications. */ - public static void removeMissedCallNotifications(Context context) { - TelecomUtil.cancelMissedCallsNotification(context); - } - - /** Update the voice mail notifications. */ - public static void updateVoicemailNotifications(Context context) { - CallLogNotificationsService.updateVoicemailNotifications(context, null); - } - - /** Information about a new voicemail. */ - public static final class NewCall { - public final Uri callsUri; - public final Uri voicemailUri; - public final String number; - public final int numberPresentation; - public final String accountComponentName; - public final String accountId; - public final String transcription; - public final String countryIso; - public final long dateMs; - - public NewCall( - Uri callsUri, - Uri voicemailUri, - String number, - int numberPresentation, - String accountComponentName, - String accountId, - String transcription, - String countryIso, - long dateMs) { - this.callsUri = callsUri; - this.voicemailUri = voicemailUri; - this.number = number; - this.numberPresentation = numberPresentation; - this.accountComponentName = accountComponentName; - this.accountId = accountId; - this.transcription = transcription; - this.countryIso = countryIso; - this.dateMs = dateMs; - } - } - - /** Allows determining the new calls for which a notification should be generated. */ - public interface NewCallsQuery { - /** - * Returns the new calls of a certain type for which a notification should be generated. - */ - @Nullable - public List<NewCall> query(int type); - } - - /** Create a new instance of {@link NewCallsQuery}. */ - public static NewCallsQuery createNewCallsQuery(Context context, - ContentResolver contentResolver) { - - return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver); - } - - /** - * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to - * notify about in the call log. - */ - private static final class DefaultNewCallsQuery implements NewCallsQuery { - private static final String[] PROJECTION = { - Calls._ID, - Calls.NUMBER, - Calls.VOICEMAIL_URI, - Calls.NUMBER_PRESENTATION, - Calls.PHONE_ACCOUNT_COMPONENT_NAME, - Calls.PHONE_ACCOUNT_ID, - Calls.TRANSCRIPTION, - Calls.COUNTRY_ISO, - Calls.DATE - }; - private static final int ID_COLUMN_INDEX = 0; - private static final int NUMBER_COLUMN_INDEX = 1; - private static final int VOICEMAIL_URI_COLUMN_INDEX = 2; - private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 3; - private static final int PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX = 4; - private static final int PHONE_ACCOUNT_ID_COLUMN_INDEX = 5; - private static final int TRANSCRIPTION_COLUMN_INDEX = 6; - private static final int COUNTRY_ISO_COLUMN_INDEX = 7; - private static final int DATE_COLUMN_INDEX = 8; - - private final ContentResolver mContentResolver; - private final Context mContext; - - private DefaultNewCallsQuery(Context context, ContentResolver contentResolver) { - mContext = context; - mContentResolver = contentResolver; - } - - @Override - @Nullable - public List<NewCall> query(int type) { - if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) { - Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup."); - return null; - } - final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE); - final String[] selectionArgs = new String[]{ Integer.toString(type) }; - try (Cursor cursor = mContentResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, - PROJECTION, selection, selectionArgs, Calls.DEFAULT_SORT_ORDER)) { - if (cursor == null) { - return null; - } - List<NewCall> newCalls = new ArrayList<>(); - while (cursor.moveToNext()) { - newCalls.add(createNewCallsFromCursor(cursor)); - } - return newCalls; - } catch (RuntimeException e) { - Log.w(TAG, "Exception when querying Contacts Provider for calls lookup"); - return null; - } - } - - /** Returns an instance of {@link NewCall} created by using the values of the cursor. */ - private NewCall createNewCallsFromCursor(Cursor cursor) { - String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX); - Uri callsUri = ContentUris.withAppendedId( - Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX)); - Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString); - return new NewCall( - callsUri, - voicemailUri, - cursor.getString(NUMBER_COLUMN_INDEX), - cursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX), - cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX), - cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX), - cursor.getString(TRANSCRIPTION_COLUMN_INDEX), - cursor.getString(COUNTRY_ISO_COLUMN_INDEX), - cursor.getLong(DATE_COLUMN_INDEX)); - } - } - - /** Allows determining the name associated with a given phone number. */ - public interface NameLookupQuery { - /** - * Returns the name associated with the given number in the contacts database, or null if - * the number does not correspond to any of the contacts. - * <p> - * If there are multiple contacts with the same phone number, it will return the name of one - * of the matching contacts. - */ - @Nullable - public String query(@Nullable String number); - } - - /** Create a new instance of {@link NameLookupQuery}. */ - public static NameLookupQuery createNameLookupQuery(Context context, - ContentResolver contentResolver) { - return new DefaultNameLookupQuery(context.getApplicationContext(), contentResolver); - } - - /** - * Default implementation of {@link NameLookupQuery} that looks up the name of a contact in the - * contacts database. - */ - private static final class DefaultNameLookupQuery implements NameLookupQuery { - private static final String[] PROJECTION = { PhoneLookup.DISPLAY_NAME }; - private static final int DISPLAY_NAME_COLUMN_INDEX = 0; - - private final ContentResolver mContentResolver; - private final Context mContext; - - private DefaultNameLookupQuery(Context context, ContentResolver contentResolver) { - mContext = context; - mContentResolver = contentResolver; - } - - @Override - @Nullable - public String query(@Nullable String number) { - if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CONTACTS)) { - Log.w(TAG, "No READ_CONTACTS permission, returning null for name lookup."); - return null; - } - try (Cursor cursor = mContentResolver.query( - Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)), - PROJECTION, null, null, null)) { - if (cursor == null || !cursor.moveToFirst()) { - return null; - } - return cursor.getString(DISPLAY_NAME_COLUMN_INDEX); - } catch (RuntimeException e) { - Log.w(TAG, "Exception when querying Contacts Provider for name lookup"); - return null; - } - } - } -} diff --git a/src/com/android/dialer/calllog/CallLogNotificationsService.java b/src/com/android/dialer/calllog/CallLogNotificationsService.java deleted file mode 100644 index 4ff9576ca..000000000 --- a/src/com/android/dialer/calllog/CallLogNotificationsService.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.util.Log; - -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.util.TelecomUtil; - -/** - * Provides operations for managing call-related notifications. - * <p> - * It handles the following actions: - * <ul> - * <li>Updating voicemail notifications</li> - * <li>Marking new voicemails as old</li> - * <li>Updating missed call notifications</li> - * <li>Marking new missed calls as old</li> - * <li>Calling back from a missed call</li> - * <li>Sending an SMS from a missed call</li> - * </ul> - */ -public class CallLogNotificationsService extends IntentService { - private static final String TAG = "CallLogNotificationsService"; - - /** Action to mark all the new voicemails as old. */ - public static final String ACTION_MARK_NEW_VOICEMAILS_AS_OLD = - "com.android.dialer.calllog.ACTION_MARK_NEW_VOICEMAILS_AS_OLD"; - - /** - * Action to update voicemail notifications. - * <p> - * May include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}. - */ - public static final String ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS = - "com.android.dialer.calllog.UPDATE_VOICEMAIL_NOTIFICATIONS"; - - /** - * Extra to included with {@link #ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS} to identify the new - * voicemail that triggered an update. - * <p> - * It must be a {@link Uri}. - */ - public static final String EXTRA_NEW_VOICEMAIL_URI = "NEW_VOICEMAIL_URI"; - - /** - * Action to update the missed call notifications. - * <p> - * Includes optional extras {@link #EXTRA_MISSED_CALL_NUMBER} and - * {@link #EXTRA_MISSED_CALL_COUNT}. - */ - public static final String ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS = - "com.android.dialer.calllog.UPDATE_MISSED_CALL_NOTIFICATIONS"; - - /** Action to mark all the new missed calls as old. */ - public static final String ACTION_MARK_NEW_MISSED_CALLS_AS_OLD = - "com.android.dialer.calllog.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD"; - - /** Action to call back a missed call. */ - public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION = - "com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION"; - - public static final String ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION = - "com.android.dialer.calllog.SEND_SMS_FROM_MISSED_CALL_NOTIFICATION"; - - /** - * Extra to be included with {@link #ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS}, - * {@link #ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION} and - * {@link #ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION} to identify the number to display, - * call or text back. - * <p> - * It must be a {@link String}. - */ - public static final String EXTRA_MISSED_CALL_NUMBER = "MISSED_CALL_NUMBER"; - - /** - * Extra to be included with {@link #ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS} to represent the - * number of missed calls. - * <p> - * It must be a {@link Integer} - */ - public static final String EXTRA_MISSED_CALL_COUNT = - "MISSED_CALL_COUNT"; - - public static final int UNKNOWN_MISSED_CALL_COUNT = -1; - - private VoicemailQueryHandler mVoicemailQueryHandler; - - public CallLogNotificationsService() { - super("CallLogNotificationsService"); - } - - @Override - protected void onHandleIntent(Intent intent) { - if (intent == null) { - Log.d(TAG, "onHandleIntent: could not handle null intent"); - return; - } - - if (!PermissionsUtil.hasPermission(this, android.Manifest.permission.READ_CALL_LOG)) { - return; - } - - String action = intent.getAction(); - switch (action) { - case ACTION_MARK_NEW_VOICEMAILS_AS_OLD: - if (mVoicemailQueryHandler == null) { - mVoicemailQueryHandler = new VoicemailQueryHandler(this, getContentResolver()); - } - mVoicemailQueryHandler.markNewVoicemailsAsOld(); - break; - case ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS: - Uri voicemailUri = (Uri) intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI); - DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri); - break; - case ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS: - int count = intent.getIntExtra(EXTRA_MISSED_CALL_COUNT, - UNKNOWN_MISSED_CALL_COUNT); - String number = intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER); - MissedCallNotifier.getInstance(this).updateMissedCallNotification(count, number); - break; - case ACTION_MARK_NEW_MISSED_CALLS_AS_OLD: - CallLogNotificationsHelper.removeMissedCallNotifications(this); - break; - case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION: - MissedCallNotifier.getInstance(this).callBackFromMissedCall( - intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER)); - break; - case ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION: - MissedCallNotifier.getInstance(this).sendSmsFromMissedCall( - intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER)); - break; - default: - Log.d(TAG, "onHandleIntent: could not handle: " + intent); - break; - } - } - - /** - * Updates notifications for any new voicemails. - * - * @param context a valid context. - * @param voicemailUri The uri pointing to the voicemail to update the notification for. If - * {@code null}, then notifications for all new voicemails will be updated. - */ - public static void updateVoicemailNotifications(Context context, Uri voicemailUri) { - if (TelecomUtil.hasReadWriteVoicemailPermissions(context)) { - Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); - serviceIntent.setAction( - CallLogNotificationsService.ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS); - // If voicemailUri is null, then notifications for all voicemails will be updated. - if (voicemailUri != null) { - serviceIntent.putExtra( - CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, voicemailUri); - } - context.startService(serviceIntent); - } - } - - /** - * Updates notifications for any new missed calls. - * - * @param context A valid context. - * @param count The number of new missed calls. - * @param number The phone number of the newest missed call. - */ - public static void updateMissedCallNotifications(Context context, int count, - String number) { - Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); - serviceIntent.setAction( - CallLogNotificationsService.ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS); - serviceIntent.putExtra(EXTRA_MISSED_CALL_COUNT, count); - serviceIntent.putExtra(EXTRA_MISSED_CALL_NUMBER, number); - context.startService(serviceIntent); - } -} diff --git a/src/com/android/dialer/calllog/CallLogQuery.java b/src/com/android/dialer/calllog/CallLogQuery.java deleted file mode 100644 index e1a41199a..000000000 --- a/src/com/android/dialer/calllog/CallLogQuery.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import com.google.common.collect.Lists; - -import android.provider.CallLog.Calls; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.dialer.compat.CallsSdkCompat; -import com.android.dialer.compat.DialerCompatUtils; - -import java.util.List; - -/** - * The query for the call log table. - */ -public final class CallLogQuery { - - private static final String[] _PROJECTION_INTERNAL = new String[] { - Calls._ID, // 0 - Calls.NUMBER, // 1 - Calls.DATE, // 2 - Calls.DURATION, // 3 - Calls.TYPE, // 4 - Calls.COUNTRY_ISO, // 5 - Calls.VOICEMAIL_URI, // 6 - Calls.GEOCODED_LOCATION, // 7 - Calls.CACHED_NAME, // 8 - Calls.CACHED_NUMBER_TYPE, // 9 - Calls.CACHED_NUMBER_LABEL, // 10 - Calls.CACHED_LOOKUP_URI, // 11 - Calls.CACHED_MATCHED_NUMBER, // 12 - Calls.CACHED_NORMALIZED_NUMBER, // 13 - Calls.CACHED_PHOTO_ID, // 14 - Calls.CACHED_FORMATTED_NUMBER, // 15 - Calls.IS_READ, // 16 - Calls.NUMBER_PRESENTATION, // 17 - Calls.PHONE_ACCOUNT_COMPONENT_NAME, // 18 - Calls.PHONE_ACCOUNT_ID, // 19 - Calls.FEATURES, // 20 - Calls.DATA_USAGE, // 21 - Calls.TRANSCRIPTION, // 22 - }; - - public static final int ID = 0; - public static final int NUMBER = 1; - public static final int DATE = 2; - public static final int DURATION = 3; - public static final int CALL_TYPE = 4; - public static final int COUNTRY_ISO = 5; - public static final int VOICEMAIL_URI = 6; - public static final int GEOCODED_LOCATION = 7; - public static final int CACHED_NAME = 8; - public static final int CACHED_NUMBER_TYPE = 9; - public static final int CACHED_NUMBER_LABEL = 10; - public static final int CACHED_LOOKUP_URI = 11; - public static final int CACHED_MATCHED_NUMBER = 12; - public static final int CACHED_NORMALIZED_NUMBER = 13; - public static final int CACHED_PHOTO_ID = 14; - public static final int CACHED_FORMATTED_NUMBER = 15; - public static final int IS_READ = 16; - public static final int NUMBER_PRESENTATION = 17; - public static final int ACCOUNT_COMPONENT_NAME = 18; - public static final int ACCOUNT_ID = 19; - public static final int FEATURES = 20; - public static final int DATA_USAGE = 21; - public static final int TRANSCRIPTION = 22; - - // Indices for columns that may not be available, depending on the Sdk Version - /** - * Only available in versions >= M - * Call {@link DialerCompatUtils#isCallsCachedPhotoUriCompatible()} prior to use - */ - public static int CACHED_PHOTO_URI = -1; - - /** - * Only available in versions > M - * Call {@link CompatUtils#isNCompatible()} prior to use - */ - public static int POST_DIAL_DIGITS = -1; - public static int VIA_NUMBER = -1; - - public static final String[] _PROJECTION; - - static { - List<String> projectionList = Lists.newArrayList(_PROJECTION_INTERNAL); - if (DialerCompatUtils.isCallsCachedPhotoUriCompatible()) { - projectionList.add(Calls.CACHED_PHOTO_URI); - CACHED_PHOTO_URI = projectionList.size() - 1; - } - if (CompatUtils.isNCompatible()) { - projectionList.add(CallsSdkCompat.POST_DIAL_DIGITS); - POST_DIAL_DIGITS = projectionList.size() - 1; - projectionList.add(CallsSdkCompat.VIA_NUMBER); - VIA_NUMBER = projectionList.size() - 1; - } - _PROJECTION = projectionList.toArray(new String[projectionList.size()]); - } - -} diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java deleted file mode 100644 index cf86bad7f..000000000 --- a/src/com/android/dialer/calllog/CallLogQueryHandler.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabaseCorruptException; -import android.database.sqlite.SQLiteDiskIOException; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteFullException; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.CallLog.Calls; -import android.provider.VoicemailContract.Status; -import android.provider.VoicemailContract.Voicemails; -import android.util.Log; - -import com.android.contacts.common.compat.SdkVersionOverride; -import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.database.VoicemailArchiveContract; -import com.android.dialer.util.AppCompatConstants; -import com.android.dialer.util.TelecomUtil; -import com.android.dialer.voicemail.VoicemailStatusHelperImpl; - -import com.google.common.collect.Lists; - -import java.lang.ref.WeakReference; -import java.util.List; - -/** Handles asynchronous queries to the call log. */ -public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { - private static final String TAG = "CallLogQueryHandler"; - private static final int NUM_LOGS_TO_DISPLAY = 1000; - - /** The token for the query to fetch the old entries from the call log. */ - private static final int QUERY_CALLLOG_TOKEN = 54; - /** The token for the query to mark all missed calls as old after seeing the call log. */ - private static final int UPDATE_MARK_AS_OLD_TOKEN = 55; - /** The token for the query to mark all missed calls as read after seeing the call log. */ - private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 56; - /** The token for the query to fetch voicemail status messages. */ - private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 57; - /** The token for the query to fetch the number of unread voicemails. */ - private static final int QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN = 58; - /** The token for the query to fetch the number of missed calls. */ - private static final int QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN = 59; - /** The oken for the query to fetch the archived voicemails. */ - private static final int QUERY_VOICEMAIL_ARCHIVE = 60; - - private final int mLogLimit; - - /** - * Call type similar to Calls.INCOMING_TYPE used to specify all types instead of one particular - * type. Exception: excludes Calls.VOICEMAIL_TYPE. - */ - public static final int CALL_TYPE_ALL = -1; - - private final WeakReference<Listener> mListener; - - private final Context mContext; - - /** - * Simple handler that wraps background calls to catch - * {@link SQLiteException}, such as when the disk is full. - */ - protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler { - public CatchingWorkerHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - try { - // Perform same query while catching any exceptions - super.handleMessage(msg); - } catch (SQLiteDiskIOException e) { - Log.w(TAG, "Exception on background worker thread", e); - } catch (SQLiteFullException e) { - Log.w(TAG, "Exception on background worker thread", e); - } catch (SQLiteDatabaseCorruptException e) { - Log.w(TAG, "Exception on background worker thread", e); - } catch (IllegalArgumentException e) { - Log.w(TAG, "ContactsProvider not present on device", e); - } catch (SecurityException e) { - // Shouldn't happen if we are protecting the entry points correctly, - // but just in case. - Log.w(TAG, "No permission to access ContactsProvider.", e); - } - } - } - - @Override - protected Handler createHandler(Looper looper) { - // Provide our special handler that catches exceptions - return new CatchingWorkerHandler(looper); - } - - public CallLogQueryHandler(Context context, ContentResolver contentResolver, - Listener listener) { - this(context, contentResolver, listener, -1); - } - - public CallLogQueryHandler(Context context, ContentResolver contentResolver, Listener listener, - int limit) { - super(contentResolver); - mContext = context.getApplicationContext(); - mListener = new WeakReference<Listener>(listener); - mLogLimit = limit; - } - - /** - * Fetch all the voicemails in the voicemail archive. - */ - public void fetchVoicemailArchive() { - startQuery(QUERY_VOICEMAIL_ARCHIVE, null, - VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, - null, VoicemailArchiveContract.VoicemailArchive.ARCHIVED + " = 1", null, - VoicemailArchiveContract.VoicemailArchive.DATE + " DESC"); - } - - - /** - * Fetches the list of calls from the call log for a given type. - * This call ignores the new or old state. - * <p> - * It will asynchronously update the content of the list view when the fetch completes. - */ - public void fetchCalls(int callType, long newerThan) { - cancelFetch(); - if (PermissionsUtil.hasPhonePermissions(mContext)) { - fetchCalls(QUERY_CALLLOG_TOKEN, callType, false /* newOnly */, newerThan); - } else { - updateAdapterData(null); - } - } - - public void fetchCalls(int callType) { - fetchCalls(callType, 0); - } - - public void fetchVoicemailStatus() { - if (TelecomUtil.hasReadWriteVoicemailPermissions(mContext)) { - startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null, Status.CONTENT_URI, - VoicemailStatusHelperImpl.PROJECTION, null, null, null); - } - } - - public void fetchVoicemailUnreadCount() { - if (TelecomUtil.hasReadWriteVoicemailPermissions(mContext)) { - // Only count voicemails that have not been read and have not been deleted. - startQuery(QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN, null, Voicemails.CONTENT_URI, - new String[] { Voicemails._ID }, - Voicemails.IS_READ + "=0" + " AND " + Voicemails.DELETED + "=0", null, null); - } - } - - /** Fetches the list of calls in the call log. */ - private void fetchCalls(int token, int callType, boolean newOnly, long newerThan) { - StringBuilder where = new StringBuilder(); - List<String> selectionArgs = Lists.newArrayList(); - - // Always hide blocked calls. - where.append("(").append(Calls.TYPE).append(" != ?)"); - selectionArgs.add(Integer.toString(AppCompatConstants.CALLS_BLOCKED_TYPE)); - - // Ignore voicemails marked as deleted - if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) - >= Build.VERSION_CODES.M) { - where.append(" AND (").append(Voicemails.DELETED).append(" = 0)"); - } - - if (newOnly) { - where.append(" AND (").append(Calls.NEW).append(" = 1)"); - } - - if (callType > CALL_TYPE_ALL) { - where.append(" AND (").append(Calls.TYPE).append(" = ?)"); - selectionArgs.add(Integer.toString(callType)); - } else { - where.append(" AND NOT "); - where.append("(" + Calls.TYPE + " = " + AppCompatConstants.CALLS_VOICEMAIL_TYPE + ")"); - } - - if (newerThan > 0) { - where.append(" AND (").append(Calls.DATE).append(" > ?)"); - selectionArgs.add(Long.toString(newerThan)); - } - - final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit; - final String selection = where.length() > 0 ? where.toString() : null; - Uri uri = TelecomUtil.getCallLogUri(mContext).buildUpon() - .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit)) - .build(); - startQuery(token, null, uri, CallLogQuery._PROJECTION, selection, selectionArgs.toArray( - new String[selectionArgs.size()]), Calls.DEFAULT_SORT_ORDER); - } - - /** Cancel any pending fetch request. */ - private void cancelFetch() { - cancelOperation(QUERY_CALLLOG_TOKEN); - } - - /** Updates all new calls to mark them as old. */ - public void markNewCallsAsOld() { - if (!PermissionsUtil.hasPhonePermissions(mContext)) { - return; - } - // Mark all "new" calls as not new anymore. - StringBuilder where = new StringBuilder(); - where.append(Calls.NEW); - where.append(" = 1"); - - ContentValues values = new ContentValues(1); - values.put(Calls.NEW, "0"); - - startUpdate(UPDATE_MARK_AS_OLD_TOKEN, null, TelecomUtil.getCallLogUri(mContext), - values, where.toString(), null); - } - - /** Updates all missed calls to mark them as read. */ - public void markMissedCallsAsRead() { - if (!PermissionsUtil.hasPhonePermissions(mContext)) { - return; - } - - ContentValues values = new ContentValues(1); - values.put(Calls.IS_READ, "1"); - - startUpdate(UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN, null, Calls.CONTENT_URI, values, - getUnreadMissedCallsQuery(), null); - } - - /** Fetch all missed calls received since last time the tab was opened. */ - public void fetchMissedCallsUnreadCount() { - if (!PermissionsUtil.hasPhonePermissions(mContext)) { - return; - } - - startQuery(QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN, null, Calls.CONTENT_URI, - new String[]{Calls._ID}, getUnreadMissedCallsQuery(), null, null); - } - - - @Override - protected synchronized void onNotNullableQueryComplete(int token, Object cookie, - Cursor cursor) { - if (cursor == null) { - return; - } - try { - if (token == QUERY_CALLLOG_TOKEN || token == QUERY_VOICEMAIL_ARCHIVE) { - if (updateAdapterData(cursor)) { - cursor = null; - } - } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) { - updateVoicemailStatus(cursor); - } else if (token == QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN) { - updateVoicemailUnreadCount(cursor); - } else if (token == QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN) { - updateMissedCallsUnreadCount(cursor); - } else { - Log.w(TAG, "Unknown query completed: ignoring: " + token); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - /** - * Updates the adapter in the call log fragment to show the new cursor data. - * Returns true if the listener took ownership of the cursor. - */ - private boolean updateAdapterData(Cursor cursor) { - final Listener listener = mListener.get(); - if (listener != null) { - return listener.onCallsFetched(cursor); - } - return false; - - } - - /** - * @return Query string to get all unread missed calls. - */ - private String getUnreadMissedCallsQuery() { - StringBuilder where = new StringBuilder(); - where.append(Calls.IS_READ).append(" = 0 OR ").append(Calls.IS_READ).append(" IS NULL"); - where.append(" AND "); - where.append(Calls.TYPE).append(" = ").append(Calls.MISSED_TYPE); - return where.toString(); - } - - private void updateVoicemailStatus(Cursor statusCursor) { - final Listener listener = mListener.get(); - if (listener != null) { - listener.onVoicemailStatusFetched(statusCursor); - } - } - - private void updateVoicemailUnreadCount(Cursor statusCursor) { - final Listener listener = mListener.get(); - if (listener != null) { - listener.onVoicemailUnreadCountFetched(statusCursor); - } - } - - private void updateMissedCallsUnreadCount(Cursor statusCursor) { - final Listener listener = mListener.get(); - if (listener != null) { - listener.onMissedCallsUnreadCountFetched(statusCursor); - } - } - - /** Listener to completion of various queries. */ - public interface Listener { - /** Called when {@link CallLogQueryHandler#fetchVoicemailStatus()} completes. */ - void onVoicemailStatusFetched(Cursor statusCursor); - - /** Called when {@link CallLogQueryHandler#fetchVoicemailUnreadCount()} completes. */ - void onVoicemailUnreadCountFetched(Cursor cursor); - - /** Called when {@link CallLogQueryHandler#fetchMissedCallsUnreadCount()} completes. */ - void onMissedCallsUnreadCountFetched(Cursor cursor); - - /** - * Called when {@link CallLogQueryHandler#fetchCalls(int)} complete. - * Returns true if takes ownership of cursor. - */ - boolean onCallsFetched(Cursor combinedCursor); - } -} diff --git a/src/com/android/dialer/calllog/CallLogReceiver.java b/src/com/android/dialer/calllog/CallLogReceiver.java deleted file mode 100644 index fef76086c..000000000 --- a/src/com/android/dialer/calllog/CallLogReceiver.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.provider.VoicemailContract; -import android.util.Log; - -/** - * Receiver for call log events. - * <p> - * It is currently used to handle {@link VoicemailContract#ACTION_NEW_VOICEMAIL} and - * {@link Intent#ACTION_BOOT_COMPLETED}. - */ -public class CallLogReceiver extends BroadcastReceiver { - private static final String TAG = "CallLogReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) { - CallLogNotificationsService.updateVoicemailNotifications(context, intent.getData()); - } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - CallLogNotificationsService.updateVoicemailNotifications(context, null); - } else { - Log.w(TAG, "onReceive: could not handle: " + intent); - } - } -} diff --git a/src/com/android/dialer/calllog/CallTypeHelper.java b/src/com/android/dialer/calllog/CallTypeHelper.java deleted file mode 100644 index acc114c5c..000000000 --- a/src/com/android/dialer/calllog/CallTypeHelper.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.res.Resources; - -import com.android.dialer.R; -import com.android.dialer.util.AppCompatConstants; - -/** - * Helper class to perform operations related to call types. - */ -public class CallTypeHelper { - /** Name used to identify incoming calls. */ - private final CharSequence mIncomingName; - /** Name used to identify outgoing calls. */ - private final CharSequence mOutgoingName; - /** Name used to identify missed calls. */ - private final CharSequence mMissedName; - /** Name used to identify incoming video calls. */ - private final CharSequence mIncomingVideoName; - /** Name used to identify outgoing video calls. */ - private final CharSequence mOutgoingVideoName; - /** Name used to identify missed video calls. */ - private final CharSequence mMissedVideoName; - /** Name used to identify voicemail calls. */ - private final CharSequence mVoicemailName; - /** Name used to identify rejected calls. */ - private final CharSequence mRejectedName; - /** Name used to identify blocked calls. */ - private final CharSequence mBlockedName; - /** Color used to identify new missed calls. */ - private final int mNewMissedColor; - /** Color used to identify new voicemail calls. */ - private final int mNewVoicemailColor; - - public CallTypeHelper(Resources resources) { - // Cache these values so that we do not need to look them up each time. - mIncomingName = resources.getString(R.string.type_incoming); - mOutgoingName = resources.getString(R.string.type_outgoing); - mMissedName = resources.getString(R.string.type_missed); - mIncomingVideoName = resources.getString(R.string.type_incoming_video); - mOutgoingVideoName = resources.getString(R.string.type_outgoing_video); - mMissedVideoName = resources.getString(R.string.type_missed_video); - mVoicemailName = resources.getString(R.string.type_voicemail); - mRejectedName = resources.getString(R.string.type_rejected); - mBlockedName = resources.getString(R.string.type_blocked); - mNewMissedColor = resources.getColor(R.color.call_log_missed_call_highlight_color); - mNewVoicemailColor = resources.getColor(R.color.call_log_voicemail_highlight_color); - } - - /** Returns the text used to represent the given call type. */ - public CharSequence getCallTypeText(int callType, boolean isVideoCall) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - if (isVideoCall) { - return mIncomingVideoName; - } else { - return mIncomingName; - } - - case AppCompatConstants.CALLS_OUTGOING_TYPE: - if (isVideoCall) { - return mOutgoingVideoName; - } else { - return mOutgoingName; - } - - case AppCompatConstants.CALLS_MISSED_TYPE: - if (isVideoCall) { - return mMissedVideoName; - } else { - return mMissedName; - } - - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return mVoicemailName; - - case AppCompatConstants.CALLS_REJECTED_TYPE: - return mRejectedName; - - case AppCompatConstants.CALLS_BLOCKED_TYPE: - return mBlockedName; - - default: - return mMissedName; - } - } - - /** Returns the color used to highlight the given call type, null if not highlight is needed. */ - public Integer getHighlightedColor(int callType) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - // New incoming calls are not highlighted. - return null; - - case AppCompatConstants.CALLS_OUTGOING_TYPE: - // New outgoing calls are not highlighted. - return null; - - case AppCompatConstants.CALLS_MISSED_TYPE: - return mNewMissedColor; - - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return mNewVoicemailColor; - - default: - // Don't highlight calls of unknown types. They are treated as missed calls by - // the rest of the UI, but since they will never be marked as read by - // {@link CallLogQueryHandler}, just don't ever highlight them anyway. - return null; - } - } - - public static boolean isMissedCallType(int callType) { - return (callType != AppCompatConstants.CALLS_INCOMING_TYPE - && callType != AppCompatConstants.CALLS_OUTGOING_TYPE - && callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE); - } -} diff --git a/src/com/android/dialer/calllog/CallTypeIconsView.java b/src/com/android/dialer/calllog/CallTypeIconsView.java deleted file mode 100644 index 14748433c..000000000 --- a/src/com/android/dialer/calllog/CallTypeIconsView.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; - -import com.android.contacts.common.testing.NeededForTesting; -import com.android.contacts.common.util.BitmapUtil; -import com.android.dialer.R; -import com.android.dialer.util.AppCompatConstants; -import com.google.common.collect.Lists; - -import java.util.List; - -/** - * View that draws one or more symbols for different types of calls (missed calls, outgoing etc). - * The symbols are set up horizontally. As this view doesn't create subviews, it is better suited - * for ListView-recycling that a regular LinearLayout using ImageViews. - */ -public class CallTypeIconsView extends View { - private List<Integer> mCallTypes = Lists.newArrayListWithCapacity(3); - private boolean mShowVideo = false; - private int mWidth; - private int mHeight; - - private static Resources sResources; - - public CallTypeIconsView(Context context) { - this(context, null); - } - - public CallTypeIconsView(Context context, AttributeSet attrs) { - super(context, attrs); - if (sResources == null) { - sResources = new Resources(context); - } - } - - public void clear() { - mCallTypes.clear(); - mWidth = 0; - mHeight = 0; - invalidate(); - } - - public void add(int callType) { - mCallTypes.add(callType); - - final Drawable drawable = getCallTypeDrawable(callType); - mWidth += drawable.getIntrinsicWidth() + sResources.iconMargin; - mHeight = Math.max(mHeight, drawable.getIntrinsicHeight()); - invalidate(); - } - - /** - * Determines whether the video call icon will be shown. - * - * @param showVideo True where the video icon should be shown. - */ - public void setShowVideo(boolean showVideo) { - mShowVideo = showVideo; - if (showVideo) { - mWidth += sResources.videoCall.getIntrinsicWidth(); - mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight()); - invalidate(); - } - } - - /** - * Determines if the video icon should be shown. - * - * @return True if the video icon should be shown. - */ - public boolean isVideoShown() { - return mShowVideo; - } - - @NeededForTesting - public int getCount() { - return mCallTypes.size(); - } - - @NeededForTesting - public int getCallType(int index) { - return mCallTypes.get(index); - } - - private Drawable getCallTypeDrawable(int callType) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - return sResources.incoming; - case AppCompatConstants.CALLS_OUTGOING_TYPE: - return sResources.outgoing; - case AppCompatConstants.CALLS_MISSED_TYPE: - return sResources.missed; - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return sResources.voicemail; - case AppCompatConstants.CALLS_BLOCKED_TYPE: - return sResources.blocked; - default: - // It is possible for users to end up with calls with unknown call types in their - // call history, possibly due to 3rd party call log implementations (e.g. to - // distinguish between rejected and missed calls). Instead of crashing, just - // assume that all unknown call types are missed calls. - return sResources.missed; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mWidth, mHeight); - } - - @Override - protected void onDraw(Canvas canvas) { - int left = 0; - for (Integer callType : mCallTypes) { - final Drawable drawable = getCallTypeDrawable(callType); - final int right = left + drawable.getIntrinsicWidth(); - drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight()); - drawable.draw(canvas); - left = right + sResources.iconMargin; - } - - // If showing the video call icon, draw it scaled appropriately. - if (mShowVideo) { - final Drawable drawable = sResources.videoCall; - final int right = left + sResources.videoCall.getIntrinsicWidth(); - drawable.setBounds(left, 0, right, sResources.videoCall.getIntrinsicHeight()); - drawable.draw(canvas); - } - } - - private static class Resources { - - // Drawable representing an incoming answered call. - public final Drawable incoming; - - // Drawable respresenting an outgoing call. - public final Drawable outgoing; - - // Drawable representing an incoming missed call. - public final Drawable missed; - - // Drawable representing a voicemail. - public final Drawable voicemail; - - // Drawable representing a blocked call. - public final Drawable blocked; - - // Drawable repesenting a video call. - public final Drawable videoCall; - - /** - * The margin to use for icons. - */ - public final int iconMargin; - - /** - * Configures the call icon drawables. - * A single white call arrow which points down and left is used as a basis for all of the - * call arrow icons, applying rotation and colors as needed. - * - * @param context The current context. - */ - public Resources(Context context) { - final android.content.res.Resources r = context.getResources(); - - incoming = r.getDrawable(R.drawable.ic_call_arrow); - incoming.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); - - // Create a rotated instance of the call arrow for outgoing calls. - outgoing = BitmapUtil.getRotatedDrawable(r, R.drawable.ic_call_arrow, 180f); - outgoing.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); - - // Need to make a copy of the arrow drawable, otherwise the same instance colored - // above will be recolored here. - missed = r.getDrawable(R.drawable.ic_call_arrow).mutate(); - missed.setColorFilter(r.getColor(R.color.missed_call), PorterDuff.Mode.MULTIPLY); - - voicemail = r.getDrawable(R.drawable.ic_call_voicemail_holo_dark); - - blocked = getScaledBitmap(context, R.drawable.ic_block_24dp); - blocked.setColorFilter(r.getColor(R.color.blocked_call), PorterDuff.Mode.MULTIPLY); - - videoCall = getScaledBitmap(context, R.drawable.ic_videocam_24dp); - videoCall.setColorFilter(r.getColor(R.color.dialtacts_secondary_text_color), - PorterDuff.Mode.MULTIPLY); - - iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin); - } - - // Gets the icon, scaled to the height of the call type icons. This helps display all the - // icons to be the same height, while preserving their width aspect ratio. - private Drawable getScaledBitmap(Context context, int resourceId) { - Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resourceId); - int scaledHeight = - context.getResources().getDimensionPixelSize(R.dimen.call_type_icon_size); - int scaledWidth = (int) ((float) icon.getWidth() - * ((float) scaledHeight / (float) icon.getHeight())); - Bitmap scaledIcon = Bitmap.createScaledBitmap(icon, scaledWidth, scaledHeight, false); - return new BitmapDrawable(context.getResources(), scaledIcon); - } - } -} diff --git a/src/com/android/dialer/calllog/ClearCallLogDialog.java b/src/com/android/dialer/calllog/ClearCallLogDialog.java deleted file mode 100644 index bef5010ec..000000000 --- a/src/com/android/dialer/calllog/ClearCallLogDialog.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentManager; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.CallLog.Calls; - -import com.android.dialer.R; -import com.android.dialer.service.CachedNumberLookupService; -import com.android.dialerbind.ObjectFactory; - -/** - * Dialog that clears the call log after confirming with the user - */ -public class ClearCallLogDialog extends DialogFragment { - private static final CachedNumberLookupService mCachedNumberLookupService = - ObjectFactory.newCachedNumberLookupService(); - - /** Preferred way to show this dialog */ - public static void show(FragmentManager fragmentManager) { - ClearCallLogDialog dialog = new ClearCallLogDialog(); - dialog.show(fragmentManager, "deleteCallLog"); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final ContentResolver resolver = getActivity().getContentResolver(); - final Context context = getActivity().getApplicationContext(); - final OnClickListener okListener = new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final ProgressDialog progressDialog = ProgressDialog.show(getActivity(), - getString(R.string.clearCallLogProgress_title), - "", true, false); - progressDialog.setOwnerActivity(getActivity()); - final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - resolver.delete(Calls.CONTENT_URI, null, null); - if (mCachedNumberLookupService != null) { - mCachedNumberLookupService.clearAllCacheEntries(context); - } - return null; - } - @Override - protected void onPostExecute(Void result) { - final Activity activity = progressDialog.getOwnerActivity(); - - if (activity == null || activity.isDestroyed() || activity.isFinishing()) { - return; - } - - if (progressDialog != null && progressDialog.isShowing()) { - progressDialog.dismiss(); - } - } - }; - // TODO: Once we have the API, we should configure this ProgressDialog - // to only show up after a certain time (e.g. 150ms) - progressDialog.show(); - task.execute(); - } - }; - return new AlertDialog.Builder(getActivity()) - .setTitle(R.string.clearCallLogConfirmation_title) - .setIconAttribute(android.R.attr.alertDialogIcon) - .setMessage(R.string.clearCallLogConfirmation) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, okListener) - .setCancelable(true) - .create(); - } -} diff --git a/src/com/android/dialer/calllog/ContactInfo.java b/src/com/android/dialer/calllog/ContactInfo.java deleted file mode 100644 index 8fe4964bc..000000000 --- a/src/com/android/dialer/calllog/ContactInfo.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.net.Uri; -import android.text.TextUtils; - -import com.android.contacts.common.ContactsUtils.UserType; -import com.android.contacts.common.util.UriUtils; -import com.google.common.base.Objects; - -/** - * Information for a contact as needed by the Call Log. - */ -public class ContactInfo { - public Uri lookupUri; - - /** - * Contact lookup key. Note this may be a lookup key for a corp contact, in which case - * "lookup by lookup key" doesn't work on the personal profile. - */ - public String lookupKey; - public String name; - public String nameAlternative; - public int type; - public String label; - public String number; - public String formattedNumber; - /* - * ContactInfo.normalizedNumber is a column value returned by PhoneLookup query. By definition, - * it's E164 representation. - * http://developer.android.com/reference/android/provider/ContactsContract.PhoneLookupColumns. - * html#NORMALIZED_NUMBER. - * - * The fallback value, when PhoneLookup fails or else, should be either null or - * PhoneNumberUtils.formatNumberToE164. - */ - public String normalizedNumber; - /** The photo for the contact, if available. */ - public long photoId; - /** The high-res photo for the contact, if available. */ - public Uri photoUri; - public boolean isBadData; - public String objectId; - public @UserType long userType; - - public static ContactInfo EMPTY = new ContactInfo(); - - public int sourceType = 0; - - @Override - public int hashCode() { - // Uses only name and contactUri to determine hashcode. - // This should be sufficient to have a reasonable distribution of hash codes. - // Moreover, there should be no two people with the same lookupUri. - final int prime = 31; - int result = 1; - result = prime * result + ((lookupUri == null) ? 0 : lookupUri.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - ContactInfo other = (ContactInfo) obj; - if (!UriUtils.areEqual(lookupUri, other.lookupUri)) return false; - if (!TextUtils.equals(name, other.name)) return false; - if (!TextUtils.equals(nameAlternative, other.nameAlternative)) return false; - if (type != other.type) return false; - if (!TextUtils.equals(label, other.label)) return false; - if (!TextUtils.equals(number, other.number)) return false; - if (!TextUtils.equals(formattedNumber, other.formattedNumber)) return false; - if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false; - if (photoId != other.photoId) return false; - if (!UriUtils.areEqual(photoUri, other.photoUri)) return false; - if (!TextUtils.equals(objectId, other.objectId)) return false; - if (userType != other.userType) return false; - return true; - } - - @Override - public String toString() { - return Objects.toStringHelper(this).add("lookupUri", lookupUri).add("name", name) - .add("nameAlternative", nameAlternative) - .add("type", type).add("label", label) - .add("number", number).add("formattedNumber",formattedNumber) - .add("normalizedNumber", normalizedNumber).add("photoId", photoId) - .add("photoUri", photoUri).add("objectId", objectId) - .add("userType",userType).toString(); - } -} diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java deleted file mode 100644 index b0ef0abf4..000000000 --- a/src/com/android/dialer/calllog/ContactInfoHelper.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.dialer.calllog; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteFullException; -import android.net.Uri; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.DisplayNameSources; -import android.provider.ContactsContract.PhoneLookup; -import android.support.annotation.Nullable; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.ContactsUtils.UserType; -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.util.Constants; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.contacts.common.util.UriUtils; -import com.android.dialer.compat.DialerCompatUtils; -import com.android.dialer.service.CachedNumberLookupService; -import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo; -import com.android.dialer.util.TelecomUtil; -import com.android.dialerbind.ObjectFactory; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Utility class to look up the contact information for a given number. - */ -public class ContactInfoHelper { - private static final String TAG = ContactInfoHelper.class.getSimpleName(); - - private final Context mContext; - private final String mCurrentCountryIso; - - private static final CachedNumberLookupService mCachedNumberLookupService = - ObjectFactory.newCachedNumberLookupService(); - - public ContactInfoHelper(Context context, String currentCountryIso) { - mContext = context; - mCurrentCountryIso = currentCountryIso; - } - - /** - * Returns the contact information for the given number. - * <p> - * If the number does not match any contact, returns a contact info containing only the number - * and the formatted number. - * <p> - * If an error occurs during the lookup, it returns null. - * - * @param number the number to look up - * @param countryIso the country associated with this number - */ - @Nullable - public ContactInfo lookupNumber(String number, String countryIso) { - if (TextUtils.isEmpty(number)) { - return null; - } - - ContactInfo info; - - if (PhoneNumberHelper.isUriNumber(number)) { - // The number is a SIP address.. - info = lookupContactFromUri(getContactInfoLookupUri(number), true); - if (info == null || info == ContactInfo.EMPTY) { - // If lookup failed, check if the "username" of the SIP address is a phone number. - String username = PhoneNumberHelper.getUsernameFromUriNumber(number); - if (PhoneNumberUtils.isGlobalPhoneNumber(username)) { - info = queryContactInfoForPhoneNumber(username, countryIso, true); - } - } - } else { - // Look for a contact that has the given phone number. - info = queryContactInfoForPhoneNumber(number, countryIso, false); - } - - final ContactInfo updatedInfo; - if (info == null) { - // The lookup failed. - updatedInfo = null; - } else { - // If we did not find a matching contact, generate an empty contact info for the number. - if (info == ContactInfo.EMPTY) { - // Did not find a matching contact. - updatedInfo = new ContactInfo(); - updatedInfo.number = number; - updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso); - updatedInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164( - number, countryIso); - updatedInfo.lookupUri = createTemporaryContactUri(updatedInfo.formattedNumber); - } else { - updatedInfo = info; - } - } - return updatedInfo; - } - - /** - * Creates a JSON-encoded lookup uri for a unknown number without an associated contact - * - * @param number - Unknown phone number - * @return JSON-encoded URI that can be used to perform a lookup when clicking on the quick - * contact card. - */ - private static Uri createTemporaryContactUri(String number) { - try { - final JSONObject contactRows = new JSONObject().put(Phone.CONTENT_ITEM_TYPE, - new JSONObject().put(Phone.NUMBER, number).put(Phone.TYPE, Phone.TYPE_CUSTOM)); - - final String jsonString = new JSONObject().put(Contacts.DISPLAY_NAME, number) - .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.PHONE) - .put(Contacts.CONTENT_ITEM_TYPE, contactRows).toString(); - - return Contacts.CONTENT_LOOKUP_URI - .buildUpon() - .appendPath(Constants.LOOKUP_URI_ENCODED) - .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, - String.valueOf(Long.MAX_VALUE)) - .encodedFragment(jsonString) - .build(); - } catch (JSONException e) { - return null; - } - } - - /** - * Looks up a contact using the given URI. - * <p> - * It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is - * found, or the {@link ContactInfo} for the given contact. - * <p> - * The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned - * value. - */ - ContactInfo lookupContactFromUri(Uri uri, boolean isSip) { - if (uri == null) { - return null; - } - if (!PermissionsUtil.hasContactsPermissions(mContext)) { - return ContactInfo.EMPTY; - } - - Cursor phoneLookupCursor = null; - try { - String[] projection = PhoneQuery.getPhoneLookupProjection(uri); - phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, - null); - } catch (NullPointerException e) { - // Trap NPE from pre-N CP2 - return null; - } - if (phoneLookupCursor == null) { - return null; - } - - try { - if (!phoneLookupCursor.moveToFirst()) { - return ContactInfo.EMPTY; - } - String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY); - ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey); - contactInfo.nameAlternative = lookUpDisplayNameAlternative(mContext, lookupKey, - contactInfo.userType); - return contactInfo; - } finally { - phoneLookupCursor.close(); - } - } - - private ContactInfo createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey) { - ContactInfo info = new ContactInfo(); - info.lookupKey = lookupKey; - info.lookupUri = Contacts.getLookupUri(phoneLookupCursor.getLong(PhoneQuery.PERSON_ID), - lookupKey); - info.name = phoneLookupCursor.getString(PhoneQuery.NAME); - info.type = phoneLookupCursor.getInt(PhoneQuery.PHONE_TYPE); - info.label = phoneLookupCursor.getString(PhoneQuery.LABEL); - info.number = phoneLookupCursor.getString(PhoneQuery.MATCHED_NUMBER); - info.normalizedNumber = phoneLookupCursor.getString(PhoneQuery.NORMALIZED_NUMBER); - info.photoId = phoneLookupCursor.getLong(PhoneQuery.PHOTO_ID); - info.photoUri = UriUtils.parseUriOrNull(phoneLookupCursor.getString(PhoneQuery.PHOTO_URI)); - info.formattedNumber = null; - info.userType = ContactsUtils.determineUserType(null, - phoneLookupCursor.getLong(PhoneQuery.PERSON_ID)); - - return info; - } - - public static String lookUpDisplayNameAlternative(Context context, String lookupKey, - @UserType long userType) { - // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed. - if (lookupKey == null || userType == ContactsUtils.USER_TYPE_WORK) { - return null; - } - final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); - Cursor cursor = null; - try { - cursor = context.getContentResolver().query(uri, - PhoneQuery.DISPLAY_NAME_ALTERNATIVE_PROJECTION, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - return cursor.getString(PhoneQuery.NAME_ALTERNATIVE); - } - } catch (IllegalArgumentException e) { - // Avoid dialer crash when lookup key is not valid - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return null; - } - - /** - * Determines the contact information for the given phone number. - * <p> - * It returns the contact info if found. - * <p> - * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}. - * <p> - * If the lookup fails for some other reason, it returns null. - */ - private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso, - boolean isSip) { - if (TextUtils.isEmpty(number)) { - return null; - } - - ContactInfo info = lookupContactFromUri(getContactInfoLookupUri(number), isSip); - if (info != null && info != ContactInfo.EMPTY) { - info.formattedNumber = formatPhoneNumber(number, null, countryIso); - } else if (mCachedNumberLookupService != null) { - CachedContactInfo cacheInfo = - mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number); - if (cacheInfo != null) { - info = cacheInfo.getContactInfo().isBadData ? null : cacheInfo.getContactInfo(); - } else { - info = null; - } - } - return info; - } - - /** - * Format the given phone number - * - * @param number the number to be formatted. - * @param normalizedNumber the normalized number of the given number. - * @param countryIso the ISO 3166-1 two letters country code, the country's convention will be - * used to format the number if the normalized phone is null. - * - * @return the formatted number, or the given number if it was formatted. - */ - private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) { - if (TextUtils.isEmpty(number)) { - return ""; - } - // If "number" is really a SIP address, don't try to do any formatting at all. - if (PhoneNumberHelper.isUriNumber(number)) { - return number; - } - if (TextUtils.isEmpty(countryIso)) { - countryIso = mCurrentCountryIso; - } - return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso); - } - - /** - * Stores differences between the updated contact info and the current call log contact info. - * - * @param number The number of the contact. - * @param countryIso The country associated with this number. - * @param updatedInfo The updated contact info. - * @param callLogInfo The call log entry's current contact info. - */ - public void updateCallLogContactInfo(String number, String countryIso, ContactInfo updatedInfo, - ContactInfo callLogInfo) { - if (!PermissionsUtil.hasPermission(mContext, android.Manifest.permission.WRITE_CALL_LOG)) { - return; - } - - final ContentValues values = new ContentValues(); - boolean needsUpdate = false; - - if (callLogInfo != null) { - if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) { - values.put(Calls.CACHED_NAME, updatedInfo.name); - needsUpdate = true; - } - - if (updatedInfo.type != callLogInfo.type) { - values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type); - needsUpdate = true; - } - - if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) { - values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label); - needsUpdate = true; - } - - if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) { - values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri)); - needsUpdate = true; - } - - // Only replace the normalized number if the new updated normalized number isn't empty. - if (!TextUtils.isEmpty(updatedInfo.normalizedNumber) && - !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) { - values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber); - needsUpdate = true; - } - - if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) { - values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number); - needsUpdate = true; - } - - if (updatedInfo.photoId != callLogInfo.photoId) { - values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId); - needsUpdate = true; - } - - final Uri updatedPhotoUriContactsOnly = - UriUtils.nullForNonContactsUri(updatedInfo.photoUri); - if (DialerCompatUtils.isCallsCachedPhotoUriCompatible() && - !UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) { - values.put(Calls.CACHED_PHOTO_URI, - UriUtils.uriToString(updatedPhotoUriContactsOnly)); - needsUpdate = true; - } - - if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) { - values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber); - needsUpdate = true; - } - } else { - // No previous values, store all of them. - values.put(Calls.CACHED_NAME, updatedInfo.name); - values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type); - values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label); - values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri)); - values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number); - values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber); - values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId); - if (DialerCompatUtils.isCallsCachedPhotoUriCompatible()) { - values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString( - UriUtils.nullForNonContactsUri(updatedInfo.photoUri))); - } - values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber); - needsUpdate = true; - } - - if (!needsUpdate) { - return; - } - - try { - if (countryIso == null) { - mContext.getContentResolver().update( - TelecomUtil.getCallLogUri(mContext), - values, - Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL", - new String[]{ number }); - } else { - mContext.getContentResolver().update( - TelecomUtil.getCallLogUri(mContext), - values, - Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?", - new String[]{ number, countryIso }); - } - } catch (SQLiteFullException e) { - Log.e(TAG, "Unable to update contact info in call log db", e); - } - } - - public static Uri getContactInfoLookupUri(String number) { - return getContactInfoLookupUri(number, -1); - } - - public static Uri getContactInfoLookupUri(String number, long directoryId) { - // Get URI for the number in the PhoneLookup table, with a parameter to indicate whether - // the number is a SIP number. - Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI; - if (!ContactsUtils.FLAG_N_FEATURE) { - if (directoryId != -1) { - // ENTERPRISE_CONTENT_FILTER_URI in M doesn't support directory lookup - uri = PhoneLookup.CONTENT_FILTER_URI; - } else { - // b/25900607 in M. PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, encodes twice. - number = Uri.encode(number); - } - } - Uri.Builder builder = uri.buildUpon() - .appendPath(number) - .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, - String.valueOf(PhoneNumberHelper.isUriNumber(number))); - if (directoryId != -1) { - builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, - String.valueOf(directoryId)); - } - return builder.build(); - } - - /** - * Returns the contact information stored in an entry of the call log. - * - * @param c A cursor pointing to an entry in the call log. - */ - public static ContactInfo getContactInfo(Cursor c) { - ContactInfo info = new ContactInfo(); - info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI)); - info.name = c.getString(CallLogQuery.CACHED_NAME); - info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE); - info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL); - String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER); - String postDialDigits = CompatUtils.isNCompatible() - ? c.getString(CallLogQuery.POST_DIAL_DIGITS) : ""; - info.number = (matchedNumber == null) ? - c.getString(CallLogQuery.NUMBER) + postDialDigits : matchedNumber; - - info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER); - info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID); - info.photoUri = DialerCompatUtils.isCallsCachedPhotoUriCompatible() ? - UriUtils.nullForNonContactsUri( - UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_PHOTO_URI))) - : null; - info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER); - - return info; - } - - /** - * Given a contact's sourceType, return true if the contact is a business - * - * @param sourceType sourceType of the contact. This is usually populated by - * {@link #mCachedNumberLookupService}. - */ - public boolean isBusiness(int sourceType) { - return mCachedNumberLookupService != null - && mCachedNumberLookupService.isBusiness(sourceType); - } - - /** - * This function looks at a contact's source and determines if the user can - * mark caller ids from this source as invalid. - * - * @param sourceType The source type to be checked - * @param objectId The ID of the Contact object. - * @return true if contacts from this source can be marked with an invalid caller id - */ - public boolean canReportAsInvalid(int sourceType, String objectId) { - return mCachedNumberLookupService != null - && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId); - } -} diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java deleted file mode 100644 index de6fc6a3d..000000000 --- a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog; - -import com.google.common.collect.Maps; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.support.v4.util.Pair; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.compat.TelephonyManagerCompat; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.R; -import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall; -import com.android.dialer.filterednumber.FilteredNumbersUtil; -import com.android.dialer.list.ListsFragment; -import com.android.dialer.util.TelecomUtil; - -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Shows a voicemail notification in the status bar. - */ -public class DefaultVoicemailNotifier { - public static final String TAG = "VoicemailNotifier"; - - /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; - /** The identifier of the notification of new voicemails. */ - private static final int NOTIFICATION_ID = 1; - - /** The singleton instance of {@link DefaultVoicemailNotifier}. */ - private static DefaultVoicemailNotifier sInstance; - - private final Context mContext; - - /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */ - public static DefaultVoicemailNotifier getInstance(Context context) { - if (sInstance == null) { - ContentResolver contentResolver = context.getContentResolver(); - sInstance = new DefaultVoicemailNotifier(context); - } - return sInstance; - } - - private DefaultVoicemailNotifier(Context context) { - mContext = context; - } - - /** - * Updates the notification and notifies of the call with the given URI. - * - * Clears the notification if there are no new voicemails, and notifies if the given URI - * corresponds to a new voicemail. - * - * It is not safe to call this method from the main thread. - */ - public void updateNotification(Uri newCallUri) { - // Lookup the list of new voicemails to include in the notification. - // TODO: Move this into a service, to avoid holding the receiver up. - final List<NewCall> newCalls = - CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails(); - - if (newCalls == null) { - // Query failed, just return. - return; - } - - if (newCalls.isEmpty()) { - // No voicemails to notify about: clear the notification. - getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - return; - } - - Resources resources = mContext.getResources(); - - // This represents a list of names to include in the notification. - String callers = null; - - // Maps each number into a name: if a number is in the map, it has already left a more - // recent voicemail. - final Map<String, String> names = Maps.newHashMap(); - - // Determine the call corresponding to the new voicemail we have to notify about. - NewCall callToNotify = null; - - // Iterate over the new voicemails to determine all the information above. - Iterator<NewCall> itr = newCalls.iterator(); - while (itr.hasNext()) { - NewCall newCall = itr.next(); - - // Skip notifying for numbers which are blocked. - if (FilteredNumbersUtil.shouldBlockVoicemail( - mContext, newCall.number, newCall.countryIso, newCall.dateMs)) { - itr.remove(); - - // Delete the voicemail. - mContext.getContentResolver().delete(newCall.voicemailUri, null, null); - continue; - } - - // Check if we already know the name associated with this number. - String name = names.get(newCall.number); - if (name == null) { - name = CallLogNotificationsHelper.getInstance(mContext).getName(newCall.number, - newCall.numberPresentation, newCall.countryIso); - names.put(newCall.number, name); - // This is a new caller. Add it to the back of the list of callers. - if (TextUtils.isEmpty(callers)) { - callers = name; - } else { - callers = resources.getString( - R.string.notification_voicemail_callers_list, callers, name); - } - } - // Check if this is the new call we need to notify about. - if (newCallUri != null && newCall.voicemailUri != null && - ContentUris.parseId(newCallUri) == ContentUris.parseId(newCall.voicemailUri)) { - callToNotify = newCall; - } - } - - // All the potential new voicemails have been removed, e.g. if they were spam. - if (newCalls.isEmpty()) { - return; - } - - // If there is only one voicemail, set its transcription as the "long text". - String transcription = null; - if (newCalls.size() == 1) { - transcription = newCalls.get(0).transcription; - } - - if (newCallUri != null && callToNotify == null) { - Log.e(TAG, "The new call could not be found in the call log: " + newCallUri); - } - - // Determine the title of the notification and the icon for it. - final String title = resources.getQuantityString( - R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()); - // TODO: Use the photo of contact if all calls are from the same person. - final int icon = android.R.drawable.stat_notify_voicemail; - - Pair<Uri, Integer> info = getNotificationInfo(callToNotify); - - Notification.Builder notificationBuilder = new Notification.Builder(mContext) - .setSmallIcon(icon) - .setContentTitle(title) - .setContentText(callers) - .setStyle(new Notification.BigTextStyle().bigText(transcription)) - .setColor(resources.getColor(R.color.dialer_theme_color)) - .setSound(info.first) - .setDefaults(info.second) - .setDeleteIntent(createMarkNewVoicemailsAsOldIntent()) - .setAutoCancel(true); - - // Determine the intent to fire when the notification is clicked on. - final Intent contentIntent; - // Open the call log. - contentIntent = new Intent(mContext, DialtactsActivity.class); - contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_VOICEMAIL); - notificationBuilder.setContentIntent(PendingIntent.getActivity( - mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - - // The text to show in the ticker, describing the new event. - if (callToNotify != null) { - CharSequence msg = ContactDisplayUtils.getTtsSpannedPhoneNumber( - resources, - R.string.notification_new_voicemail_ticker, - names.get(callToNotify.number)); - notificationBuilder.setTicker(msg); - } - Log.i(TAG, "Creating voicemail notification"); - getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, - notificationBuilder.build()); - } - - /** - * Determines which ringtone Uri and Notification defaults to use when updating the notification - * for the given call. - */ - private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) { - Log.v(TAG, "getNotificationInfo"); - if (callToNotify == null) { - Log.i(TAG, "callToNotify == null"); - return new Pair<>(null, 0); - } - PhoneAccountHandle accountHandle = null; - if (callToNotify.accountComponentName == null || callToNotify.accountId == null) { - Log.v(TAG, "accountComponentName == null || callToNotify.accountId == null"); - accountHandle = TelecomUtil - .getDefaultOutgoingPhoneAccount(mContext, PhoneAccount.SCHEME_TEL); - if (accountHandle == null) { - Log.i(TAG, "No default phone account found, using default notification ringtone"); - return new Pair<>(null, Notification.DEFAULT_ALL); - } - - } else { - accountHandle = new PhoneAccountHandle( - ComponentName.unflattenFromString(callToNotify.accountComponentName), - callToNotify.accountId); - } - if (accountHandle.getComponentName() != null) { - Log.v(TAG, "PhoneAccountHandle.ComponentInfo:" + accountHandle.getComponentName()); - } else { - Log.i(TAG, "PhoneAccountHandle.ComponentInfo: null"); - } - return new Pair<>( - TelephonyManagerCompat.getVoicemailRingtoneUri( - getTelephonyManager(), accountHandle), - getNotificationDefaults(accountHandle)); - } - - private int getNotificationDefaults(PhoneAccountHandle accountHandle) { - if (ContactsUtils.FLAG_N_FEATURE) { - return TelephonyManagerCompat.isVoicemailVibrationEnabled(getTelephonyManager(), - accountHandle) ? Notification.DEFAULT_VIBRATE : 0; - } - return Notification.DEFAULT_ALL; - } - - /** Creates a pending intent that marks all new voicemails as old. */ - private PendingIntent createMarkNewVoicemailsAsOldIntent() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); - intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); - } - - private NotificationManager getNotificationManager() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - } - - private TelephonyManager getTelephonyManager() { - return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - } - -} diff --git a/src/com/android/dialer/calllog/GroupingListAdapter.java b/src/com/android/dialer/calllog/GroupingListAdapter.java deleted file mode 100644 index 0d06298e7..000000000 --- a/src/com/android/dialer/calllog/GroupingListAdapter.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.Context; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.os.Handler; -import android.support.v7.widget.RecyclerView; -import android.util.SparseIntArray; - -/** - * Maintains a list that groups items into groups of consecutive elements which are disjoint, - * that is, an item can only belong to one group. This is leveraged for grouping calls in the - * call log received from or made to the same phone number. - * - * There are two integers stored as metadata for every list item in the adapter. - */ -abstract class GroupingListAdapter extends RecyclerView.Adapter { - - private Context mContext; - private Cursor mCursor; - - /** - * SparseIntArray, which maps the cursor position of the first element of a group to the size - * of the group. The index of a key in this map corresponds to the list position of that group. - */ - private SparseIntArray mGroupMetadata; - private int mItemCount; - - protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) { - @Override - public boolean deliverSelfNotifications() { - return true; - } - - @Override - public void onChange(boolean selfChange) { - onContentChanged(); - } - }; - - protected DataSetObserver mDataSetObserver = new DataSetObserver() { - @Override - public void onChanged() { - notifyDataSetChanged(); - } - }; - - public GroupingListAdapter(Context context) { - mContext = context; - reset(); - } - - /** - * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for - * each of them. - */ - protected abstract void addGroups(Cursor cursor); - - protected abstract void addVoicemailGroups(Cursor cursor); - - protected abstract void onContentChanged(); - - public void changeCursor(Cursor cursor) { - changeCursor(cursor, false); - } - - public void changeCursorVoicemail(Cursor cursor) { - changeCursor(cursor, true); - } - - public void changeCursor(Cursor cursor, boolean voicemail) { - if (cursor == mCursor) { - return; - } - - if (mCursor != null) { - mCursor.unregisterContentObserver(mChangeObserver); - mCursor.unregisterDataSetObserver(mDataSetObserver); - mCursor.close(); - } - - // Reset whenever the cursor is changed. - reset(); - mCursor = cursor; - - if (cursor != null) { - if (voicemail) { - addVoicemailGroups(mCursor); - } else { - addGroups(mCursor); - } - - // Calculate the item count by subtracting group child counts from the cursor count. - mItemCount = mGroupMetadata.size(); - - cursor.registerContentObserver(mChangeObserver); - cursor.registerDataSetObserver(mDataSetObserver); - notifyDataSetChanged(); - } - } - - /** - * Records information about grouping in the list. - * Should be called by the overridden {@link #addGroups} method. - */ - public void addGroup(int cursorPosition, int groupSize) { - int lastIndex = mGroupMetadata.size() - 1; - if (lastIndex < 0 || cursorPosition <= mGroupMetadata.keyAt(lastIndex)) { - mGroupMetadata.put(cursorPosition, groupSize); - } else { - // Optimization to avoid binary search if adding groups in ascending cursor position. - mGroupMetadata.append(cursorPosition, groupSize); - } - } - - @Override - public int getItemCount() { - return mItemCount; - } - - /** - * Given the position of a list item, returns the size of the group of items corresponding to - * that position. - */ - public int getGroupSize(int listPosition) { - if (listPosition < 0 || listPosition >= mGroupMetadata.size()) { - return 0; - } - - return mGroupMetadata.valueAt(listPosition); - } - - /** - * Given the position of a list item, returns the the first item in the group of items - * corresponding to that position. - */ - public Object getItem(int listPosition) { - if (mCursor == null || listPosition < 0 || listPosition >= mGroupMetadata.size()) { - return null; - } - - int cursorPosition = mGroupMetadata.keyAt(listPosition); - if (mCursor.moveToPosition(cursorPosition)) { - return mCursor; - } else { - return null; - } - } - - private void reset() { - mItemCount = 0; - mGroupMetadata = new SparseIntArray(); - } -} diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java deleted file mode 100644 index 773436be4..000000000 --- a/src/com/android/dialer/calllog/IntentProvider.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.ContentValues; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.provider.ContactsContract; -import android.telecom.PhoneAccountHandle; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.model.Contact; -import com.android.contacts.common.model.ContactLoader; -import com.android.dialer.CallDetailActivity; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.util.IntentUtil.CallIntentBuilder; -import com.android.dialer.util.TelecomUtil; -import com.android.incallui.Call.LogState; - -import java.util.ArrayList; - -/** - * Used to create an intent to attach to an action in the call log. - * <p> - * The intent is constructed lazily with the given information. - */ -public abstract class IntentProvider { - - private static final String TAG = IntentProvider.class.getSimpleName(); - - public abstract Intent getIntent(Context context); - - public static IntentProvider getReturnCallIntentProvider(final String number) { - return getReturnCallIntentProvider(number, null); - } - - public static IntentProvider getReturnCallIntentProvider(final String number, - final PhoneAccountHandle accountHandle) { - return new IntentProvider() { - @Override - public Intent getIntent(Context context) { - return new CallIntentBuilder(number) - .setPhoneAccountHandle(accountHandle) - .setCallInitiationType(LogState.INITIATION_CALL_LOG) - .build(); - } - }; - } - - public static IntentProvider getReturnVideoCallIntentProvider(final String number) { - return getReturnVideoCallIntentProvider(number, null); - } - - public static IntentProvider getReturnVideoCallIntentProvider(final String number, - final PhoneAccountHandle accountHandle) { - return new IntentProvider() { - @Override - public Intent getIntent(Context context) { - return new CallIntentBuilder(number) - .setPhoneAccountHandle(accountHandle) - .setCallInitiationType(LogState.INITIATION_CALL_LOG) - .setIsVideoCall(true) - .build(); - } - }; - } - - public static IntentProvider getReturnVoicemailCallIntentProvider() { - return new IntentProvider() { - @Override - public Intent getIntent(Context context) { - return new CallIntentBuilder(CallUtil.getVoicemailUri()) - .setCallInitiationType(LogState.INITIATION_CALL_LOG) - .build(); - } - }; - } - - public static IntentProvider getSendSmsIntentProvider(final String number) { - return new IntentProvider() { - @Override - public Intent getIntent(Context context) { - return IntentUtil.getSendSmsIntent(number); - } - }; - } - - /** - * Retrieves the call details intent provider for an entry in the call log. - * - * @param id The call ID of the first call in the call group. - * @param extraIds The call ID of the other calls grouped together with the call. - * @param voicemailUri If call log entry is for a voicemail, the voicemail URI. - * @return The call details intent provider. - */ - public static IntentProvider getCallDetailIntentProvider( - final long id, final long[] extraIds, final String voicemailUri) { - return new IntentProvider() { - @Override - public Intent getIntent(Context context) { - Intent intent = new Intent(context, CallDetailActivity.class); - // Check if the first item is a voicemail. - if (voicemailUri != null) { - intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, - Uri.parse(voicemailUri)); - } - - if (extraIds != null && extraIds.length > 0) { - intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, extraIds); - } else { - // If there is a single item, use the direct URI for it. - intent.setData(ContentUris.withAppendedId(TelecomUtil.getCallLogUri(context), - id)); - } - return intent; - } - }; - } - - /** - * Retrieves an add contact intent for the given contact and phone call details. - */ - public static IntentProvider getAddContactIntentProvider( - final Uri lookupUri, - final CharSequence name, - final CharSequence number, - final int numberType, - final boolean isNewContact) { - return new IntentProvider() { - @Override - public Intent getIntent(Context context) { - Contact contactToSave = null; - - if (lookupUri != null) { - contactToSave = ContactLoader.parseEncodedContactEntity(lookupUri); - } - - if (contactToSave != null) { - // Populate the intent with contact information stored in the lookup URI. - // Note: This code mirrors code in Contacts/QuickContactsActivity. - final Intent intent; - if (isNewContact) { - intent = IntentUtil.getNewContactIntent(); - } else { - intent = IntentUtil.getAddToExistingContactIntent(); - } - - ArrayList<ContentValues> values = contactToSave.getContentValues(); - // Only pre-fill the name field if the provided display name is an nickname - // or better (e.g. structured name, nickname) - if (contactToSave.getDisplayNameSource() - >= ContactsContract.DisplayNameSources.NICKNAME) { - intent.putExtra(ContactsContract.Intents.Insert.NAME, - contactToSave.getDisplayName()); - } else if (contactToSave.getDisplayNameSource() - == ContactsContract.DisplayNameSources.ORGANIZATION) { - // This is probably an organization. Instead of copying the organization - // name into a name entry, copy it into the organization entry. This - // way we will still consider the contact an organization. - final ContentValues organization = new ContentValues(); - organization.put(ContactsContract.CommonDataKinds.Organization.COMPANY, - contactToSave.getDisplayName()); - organization.put(ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); - values.add(organization); - } - - // Last time used and times used are aggregated values from the usage stat - // table. They need to be removed from data values so the SQL table can insert - // properly - for (ContentValues value : values) { - value.remove(ContactsContract.Data.LAST_TIME_USED); - value.remove(ContactsContract.Data.TIMES_USED); - } - - intent.putExtra(ContactsContract.Intents.Insert.DATA, values); - - return intent; - } else { - // If no lookup uri is provided, rely on the available phone number and name. - if (isNewContact) { - return IntentUtil.getNewContactIntent(name, number, numberType); - } else { - return IntentUtil.getAddToExistingContactIntent(name, number, numberType); - } - } - } - }; - } -} diff --git a/src/com/android/dialer/calllog/MissedCallNotificationReceiver.java b/src/com/android/dialer/calllog/MissedCallNotificationReceiver.java deleted file mode 100644 index 86d6cb9fb..000000000 --- a/src/com/android/dialer/calllog/MissedCallNotificationReceiver.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.dialer.calllog; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.telecom.TelecomManager; -import android.util.Log; - -import com.android.dialer.calllog.CallLogNotificationsService; - -/** - * Receives broadcasts that should trigger a refresh of the missed call notification. This includes - * both an explicit broadcast from Telecom and a reboot. - */ -public class MissedCallNotificationReceiver extends BroadcastReceiver { - //TODO: Use compat class for these methods. - public static final String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = - "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"; - - public static final String EXTRA_NOTIFICATION_COUNT = - "android.telecom.extra.NOTIFICATION_COUNT"; - - public static final String EXTRA_NOTIFICATION_PHONE_NUMBER = - "android.telecom.extra.NOTIFICATION_PHONE_NUMBER"; - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (!ACTION_SHOW_MISSED_CALLS_NOTIFICATION.equals(action)) { - return; - } - - int count = intent.getIntExtra(EXTRA_NOTIFICATION_COUNT, - CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT); - String number = intent.getStringExtra(EXTRA_NOTIFICATION_PHONE_NUMBER); - CallLogNotificationsService.updateMissedCallNotifications(context, count, number); - } -} diff --git a/src/com/android/dialer/calllog/MissedCallNotifier.java b/src/com/android/dialer/calllog/MissedCallNotifier.java deleted file mode 100644 index 732f65665..000000000 --- a/src/com/android/dialer/calllog/MissedCallNotifier.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.dialer.calllog; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.AsyncTask; -import android.provider.CallLog.Calls; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.R; -import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall; -import com.android.dialer.contactinfo.ContactPhotoLoader; -import com.android.dialer.compat.UserManagerCompat; -import com.android.dialer.list.ListsFragment; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.util.IntentUtil.CallIntentBuilder; - -import java.util.List; - -/** - * Creates a notification for calls that the user missed (neither answered nor rejected). - * - */ -public class MissedCallNotifier { - public static final String TAG = "MissedCallNotifier"; - - /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "MissedCallNotifier"; - /** The identifier of the notification of new missed calls. */ - private static final int NOTIFICATION_ID = 1; - /** Preference file key for number of missed calls. */ - private static final String MISSED_CALL_COUNT = "missed_call_count"; - - private static MissedCallNotifier sInstance; - private Context mContext; - - /** Returns the singleton instance of the {@link MissedCallNotifier}. */ - public static MissedCallNotifier getInstance(Context context) { - if (sInstance == null) { - sInstance = new MissedCallNotifier(context); - } - return sInstance; - } - - private MissedCallNotifier(Context context) { - mContext = context; - } - - public void updateMissedCallNotification(int count, String number) { - final int titleResId; - final String expandedText; // The text in the notification's line 1 and 2. - - final List<NewCall> newCalls = - CallLogNotificationsHelper.getInstance(mContext).getNewMissedCalls(); - - if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { - if (newCalls == null) { - // If the intent did not contain a count, and we are unable to get a count from the - // call log, then no notification can be shown. - return; - } - count = newCalls.size(); - } - - if (count == 0) { - // No voicemails to notify about: clear the notification. - clearMissedCalls(); - return; - } - - // The call log has been updated, use that information preferentially. - boolean useCallLog = newCalls != null && newCalls.size() == count; - NewCall newestCall = useCallLog ? newCalls.get(0) : null; - long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis(); - - Notification.Builder builder = new Notification.Builder(mContext); - // Display the first line of the notification: - // 1 missed call: <caller name || handle> - // More than 1 missed call: <number of calls> + "missed calls" - if (count == 1) { - //TODO: look up caller ID that is not in contacts. - ContactInfo contactInfo = CallLogNotificationsHelper.getInstance(mContext) - .getContactInfo(useCallLog ? newestCall.number : number, - useCallLog ? newestCall.numberPresentation - : Calls.PRESENTATION_ALLOWED, - useCallLog ? newestCall.countryIso : null); - - titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK - ? R.string.notification_missedWorkCallTitle - : R.string.notification_missedCallTitle; - - expandedText = contactInfo.name; - ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo); - Bitmap photoIcon = loader.loadPhotoIcon(); - if (photoIcon != null) { - builder.setLargeIcon(photoIcon); - } - } else { - titleResId = R.string.notification_missedCallsTitle; - expandedText = - mContext.getString(R.string.notification_missedCallsMsg, count); - } - - // Create a public viewable version of the notification, suitable for display when sensitive - // notification content is hidden. - Notification.Builder publicBuilder = new Notification.Builder(mContext); - publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - // Show "Phone" for notification title. - .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) - // Notification details shows that there are missed call(s), but does not reveal - // the missed caller information. - .setContentText(mContext.getText(titleResId)) - .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setDeleteIntent(createClearMissedCallsPendingIntent()); - - // Create the notification suitable for display when sensitive information is showing. - builder.setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - .setContentTitle(mContext.getText(titleResId)) - .setContentText(expandedText) - .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setDefaults(Notification.DEFAULT_VIBRATE) - .setDeleteIntent(createClearMissedCallsPendingIntent()) - // Include a public version of the notification to be shown when the missed call - // notification is shown on the user's lock screen and they have chosen to hide - // sensitive notification information. - .setPublicVersion(publicBuilder.build()); - - // Add additional actions when there is only 1 missed call and the user isn't locked - if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) { - if (!TextUtils.isEmpty(number) - && !TextUtils.equals( - number, mContext.getString(R.string.handle_restricted))) { - builder.addAction(R.drawable.ic_phone_24dp, - mContext.getString(R.string.notification_missedCall_call_back), - createCallBackPendingIntent(number)); - - if (!PhoneNumberHelper.isUriNumber(number)) { - builder.addAction(R.drawable.ic_message_24dp, - mContext.getString(R.string.notification_missedCall_message), - createSendSmsFromNotificationPendingIntent(number)); - } - } - } - - Notification notification = builder.build(); - configureLedOnNotification(notification); - - Log.i(TAG, "Adding missed call notification."); - getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); - } - - private void clearMissedCalls() { - AsyncTask.execute(new Runnable() { - @Override - public void run() { - // Call log is only accessible when unlocked. If that's the case, clear the list of - // new missed calls from the call log. - if (UserManagerCompat.isUserUnlocked(mContext)) { - ContentValues values = new ContentValues(); - values.put(Calls.NEW, 0); - values.put(Calls.IS_READ, 1); - StringBuilder where = new StringBuilder(); - where.append(Calls.NEW); - where.append(" = 1 AND "); - where.append(Calls.TYPE); - where.append(" = ?"); - try { - mContext.getContentResolver().update(Calls.CONTENT_URI, values, - where.toString(), new String[]{Integer.toString(Calls. - MISSED_TYPE)}); - } catch (IllegalArgumentException e) { - Log.w(TAG, "ContactsProvider update command failed", e); - } - } - getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - }); - } - - /** - * Trigger an intent to make a call from a missed call number. - */ - public void callBackFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); - DialerUtils.startActivityWithErrorToast( - mContext, - new CallIntentBuilder(number) - .build() - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } - - /** - * Trigger an intent to send an sms from a missed call number. - */ - public void sendSmsFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); - DialerUtils.startActivityWithErrorToast( - mContext, - IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } - - /** - * Creates a new pending intent that sends the user to the call log. - * - * @return The pending intent. - */ - private PendingIntent createCallLogPendingIntent() { - Intent contentIntent = new Intent(mContext, DialtactsActivity.class); - contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_HISTORY); - return PendingIntent.getActivity( - mContext, 0, contentIntent,PendingIntent.FLAG_UPDATE_CURRENT); - } - - /** Creates a pending intent that marks all new missed calls as old. */ - private PendingIntent createClearMissedCallsPendingIntent() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); - intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); - } - - private PendingIntent createCallBackPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); - intent.setAction( - CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION); - intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); - // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new - // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - private PendingIntent createSendSmsFromNotificationPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); - intent.setAction( - CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION); - intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); - // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new - // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - /** - * Configures a notification to emit the blinky notification light. - */ - private void configureLedOnNotification(Notification notification) { - notification.flags |= Notification.FLAG_SHOW_LIGHTS; - notification.defaults |= Notification.DEFAULT_LIGHTS; - } - - /** - * Closes open system dialogs and the notification shade. - */ - private void closeSystemDialogs(Context context) { - context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - private NotificationManager getNotificationMgr() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - } -} diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java deleted file mode 100644 index b3ce18b3c..000000000 --- a/src/com/android/dialer/calllog/PhoneAccountUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog; - -import android.content.ComponentName; -import android.content.Context; -import android.support.annotation.Nullable; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.dialer.util.TelecomUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * Methods to help extract {@code PhoneAccount} information from database and Telecomm sources. - */ -public class PhoneAccountUtils { - /** - * Return a list of phone accounts that are subscription/SIM accounts. - */ - public static List<PhoneAccountHandle> getSubscriptionPhoneAccounts(Context context) { - List<PhoneAccountHandle> subscriptionAccountHandles = new ArrayList<PhoneAccountHandle>(); - final List<PhoneAccountHandle> accountHandles = - TelecomUtil.getCallCapablePhoneAccounts(context); - for (PhoneAccountHandle accountHandle : accountHandles) { - PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { - subscriptionAccountHandles.add(accountHandle); - } - } - return subscriptionAccountHandles; - } - - /** - * Compose PhoneAccount object from component name and account id. - */ - @Nullable - public static PhoneAccountHandle getAccount(@Nullable String componentString, - @Nullable String accountId) { - if (TextUtils.isEmpty(componentString) || TextUtils.isEmpty(accountId)) { - return null; - } - final ComponentName componentName = ComponentName.unflattenFromString(componentString); - if (componentName == null) { - return null; - } - return new PhoneAccountHandle(componentName, accountId); - } - - /** - * Extract account label from PhoneAccount object. - */ - @Nullable - public static String getAccountLabel(Context context, - @Nullable PhoneAccountHandle accountHandle) { - PhoneAccount account = getAccountOrNull(context, accountHandle); - if (account != null && account.getLabel() != null) { - return account.getLabel().toString(); - } - return null; - } - - /** - * Extract account color from PhoneAccount object. - */ - public static int getAccountColor(Context context, @Nullable PhoneAccountHandle accountHandle) { - final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - - // For single-sim devices the PhoneAccount will be NO_HIGHLIGHT_COLOR by default, so it is - // safe to always use the account highlight color. - return account == null ? PhoneAccount.NO_HIGHLIGHT_COLOR : account.getHighlightColor(); - } - - /** - * Determine whether a phone account supports call subjects. - * - * @return {@code true} if call subjects are supported, {@code false} otherwise. - */ - public static boolean getAccountSupportsCallSubject(Context context, - @Nullable PhoneAccountHandle accountHandle) { - final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - - return account == null ? false : - account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); - } - - /** - * Retrieve the account metadata, but if the account does not exist or the device has only a - * single registered and enabled account, return null. - */ - @Nullable - private static PhoneAccount getAccountOrNull(Context context, - @Nullable PhoneAccountHandle accountHandle) { - if (TelecomUtil.getCallCapablePhoneAccounts(context).size() <= 1) { - return null; - } - return TelecomUtil.getPhoneAccount(context, accountHandle); - } -} diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java deleted file mode 100644 index 4f1c45503..000000000 --- a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import com.google.common.base.MoreObjects; -import com.google.common.collect.Lists; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Typeface; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v4.content.ContextCompat; -import android.telecom.PhoneAccount; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.TextView; - -import com.android.contacts.common.testing.NeededForTesting; -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.dialer.PhoneCallDetails; -import com.android.dialer.R; -import com.android.dialer.calllog.calllogcache.CallLogCache; -import com.android.dialer.util.DialerUtils; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.concurrent.TimeUnit; - -/** - * Helper class to fill in the views in {@link PhoneCallDetailsViews}. - */ -public class PhoneCallDetailsHelper { - - /** The maximum number of icons will be shown to represent the call types in a group. */ - private static final int MAX_CALL_TYPE_ICONS = 3; - - private final Context mContext; - private final Resources mResources; - /** The injected current time in milliseconds since the epoch. Used only by tests. */ - private Long mCurrentTimeMillisForTest; - - private CharSequence mPhoneTypeLabelForTest; - - private final CallLogCache mCallLogCache; - - /** Calendar used to construct dates */ - private final Calendar mCalendar; - - /** - * List of items to be concatenated together for accessibility descriptions - */ - private ArrayList<CharSequence> mDescriptionItems = Lists.newArrayList(); - - /** - * Creates a new instance of the helper. - * <p> - * Generally you should have a single instance of this helper in any context. - * - * @param resources used to look up strings - */ - public PhoneCallDetailsHelper( - Context context, - Resources resources, - CallLogCache callLogCache) { - mContext = context; - mResources = resources; - mCallLogCache = callLogCache; - mCalendar = Calendar.getInstance(); - } - - /** Fills the call details views with content. */ - public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details) { - // Display up to a given number of icons. - views.callTypeIcons.clear(); - int count = details.callTypes.length; - boolean isVoicemail = false; - for (int index = 0; index < count && index < MAX_CALL_TYPE_ICONS; ++index) { - views.callTypeIcons.add(details.callTypes[index]); - if (index == 0) { - isVoicemail = details.callTypes[index] == Calls.VOICEMAIL_TYPE; - } - } - - // Show the video icon if the call had video enabled. - views.callTypeIcons.setShowVideo( - (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO); - views.callTypeIcons.requestLayout(); - views.callTypeIcons.setVisibility(View.VISIBLE); - - // Show the total call count only if there are more than the maximum number of icons. - final Integer callCount; - if (count > MAX_CALL_TYPE_ICONS) { - callCount = count; - } else { - callCount = null; - } - - // Set the call count, location, date and if voicemail, set the duration. - setDetailText(views, callCount, details); - - // Set the account label if it exists. - String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle); - if (!TextUtils.isEmpty(details.viaNumber)) { - if (!TextUtils.isEmpty(accountLabel)) { - accountLabel = mResources.getString(R.string.call_log_via_number_phone_account, - accountLabel, details.viaNumber); - } else { - accountLabel = mResources.getString(R.string.call_log_via_number, - details.viaNumber); - } - } - if (!TextUtils.isEmpty(accountLabel)) { - views.callAccountLabel.setVisibility(View.VISIBLE); - views.callAccountLabel.setText(accountLabel); - int color = mCallLogCache.getAccountColor(details.accountHandle); - if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { - int defaultColor = R.color.dialtacts_secondary_text_color; - views.callAccountLabel.setTextColor(mContext.getResources().getColor(defaultColor)); - } else { - views.callAccountLabel.setTextColor(color); - } - } else { - views.callAccountLabel.setVisibility(View.GONE); - } - - final CharSequence nameText; - final CharSequence displayNumber = details.displayNumber; - if (TextUtils.isEmpty(details.getPreferredName())) { - nameText = displayNumber; - // We have a real phone number as "nameView" so make it always LTR - views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR); - } else { - nameText = details.getPreferredName(); - } - - views.nameView.setText(nameText); - - if (isVoicemail) { - views.voicemailTranscriptionView.setText(TextUtils.isEmpty(details.transcription) ? null - : details.transcription); - } - - // Bold if not read - Typeface typeface = details.isRead ? Typeface.SANS_SERIF : Typeface.DEFAULT_BOLD; - views.nameView.setTypeface(typeface); - views.voicemailTranscriptionView.setTypeface(typeface); - views.callLocationAndDate.setTypeface(typeface); - views.callLocationAndDate.setTextColor(ContextCompat.getColor(mContext, details.isRead ? - R.color.call_log_detail_color : R.color.call_log_unread_text_color)); - } - - /** - * Builds a string containing the call location and date. For voicemail logs only the call date - * is returned because location information is displayed in the call action button - * - * @param details The call details. - * @return The call location and date string. - */ - private CharSequence getCallLocationAndDate(PhoneCallDetails details) { - mDescriptionItems.clear(); - - if (details.callTypes[0] != Calls.VOICEMAIL_TYPE) { - // Get type of call (ie mobile, home, etc) if known, or the caller's location. - CharSequence callTypeOrLocation = getCallTypeOrLocation(details); - - // Only add the call type or location if its not empty. It will be empty for unknown - // callers. - if (!TextUtils.isEmpty(callTypeOrLocation)) { - mDescriptionItems.add(callTypeOrLocation); - } - } - - // The date of this call - mDescriptionItems.add(getCallDate(details)); - - // Create a comma separated list from the call type or location, and call date. - return DialerUtils.join(mResources, mDescriptionItems); - } - - /** - * For a call, if there is an associated contact for the caller, return the known call type - * (e.g. mobile, home, work). If there is no associated contact, attempt to use the caller's - * location if known. - * - * @param details Call details to use. - * @return Type of call (mobile/home) if known, or the location of the caller (if known). - */ - public CharSequence getCallTypeOrLocation(PhoneCallDetails details) { - CharSequence numberFormattedLabel = null; - // Only show a label if the number is shown and it is not a SIP address. - if (!TextUtils.isEmpty(details.number) - && !PhoneNumberHelper.isUriNumber(details.number.toString()) - && !mCallLogCache.isVoicemailNumber(details.accountHandle, details.number)) { - - if (TextUtils.isEmpty(details.namePrimary) && !TextUtils.isEmpty(details.geocode)) { - numberFormattedLabel = details.geocode; - } else if (!(details.numberType == Phone.TYPE_CUSTOM - && TextUtils.isEmpty(details.numberLabel))) { - // Get type label only if it will not be "Custom" because of an empty number label. - numberFormattedLabel = MoreObjects.firstNonNull(mPhoneTypeLabelForTest, - Phone.getTypeLabel(mResources, details.numberType, details.numberLabel)); - } - } - - if (!TextUtils.isEmpty(details.namePrimary) && TextUtils.isEmpty(numberFormattedLabel)) { - numberFormattedLabel = details.displayNumber; - } - return numberFormattedLabel; - } - - @NeededForTesting - public void setPhoneTypeLabelForTest(CharSequence phoneTypeLabel) { - this.mPhoneTypeLabelForTest = phoneTypeLabel; - } - - /** - * Get the call date/time of the call. For the call log this is relative to the current time. - * e.g. 3 minutes ago. For voicemail, see {@link #getGranularDateTime(PhoneCallDetails)} - * - * @param details Call details to use. - * @return String representing when the call occurred. - */ - public CharSequence getCallDate(PhoneCallDetails details) { - if (details.callTypes[0] == Calls.VOICEMAIL_TYPE) { - return getGranularDateTime(details); - } - - return DateUtils.getRelativeTimeSpanString(details.date, getCurrentTimeMillis(), - DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); - } - - /** - * Get the granular version of the call date/time of the call. The result is always in the form - * 'DATE at TIME'. The date value changes based on when the call was created. - * - * If created today, DATE is 'Today' - * If created this year, DATE is 'MMM dd' - * Otherwise, DATE is 'MMM dd, yyyy' - * - * TIME is the localized time format, e.g. 'hh:mm a' or 'HH:mm' - * - * @param details Call details to use - * @return String representing when the call occurred - */ - public CharSequence getGranularDateTime(PhoneCallDetails details) { - return mResources.getString(R.string.voicemailCallLogDateTimeFormat, - getGranularDate(details.date), - DateUtils.formatDateTime(mContext, details.date, DateUtils.FORMAT_SHOW_TIME)); - } - - /** - * Get the granular version of the call date. See {@link #getGranularDateTime(PhoneCallDetails)} - */ - private String getGranularDate(long date) { - if (DateUtils.isToday(date)) { - return mResources.getString(R.string.voicemailCallLogToday); - } - return DateUtils.formatDateTime(mContext, date, DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_ABBREV_MONTH - | (shouldShowYear(date) ? DateUtils.FORMAT_SHOW_YEAR : DateUtils.FORMAT_NO_YEAR)); - } - - /** - * Determines whether the year should be shown for the given date - * - * @return {@code true} if date is within the current year, {@code false} otherwise - */ - private boolean shouldShowYear(long date) { - mCalendar.setTimeInMillis(getCurrentTimeMillis()); - int currentYear = mCalendar.get(Calendar.YEAR); - mCalendar.setTimeInMillis(date); - return currentYear != mCalendar.get(Calendar.YEAR); - } - - /** Sets the text of the header view for the details page of a phone call. */ - @NeededForTesting - public void setCallDetailsHeader(TextView nameView, PhoneCallDetails details) { - final CharSequence nameText; - if (!TextUtils.isEmpty(details.namePrimary)) { - nameText = details.namePrimary; - } else if (!TextUtils.isEmpty(details.displayNumber)) { - nameText = details.displayNumber; - } else { - nameText = mResources.getString(R.string.unknown); - } - - nameView.setText(nameText); - } - - @NeededForTesting - public void setCurrentTimeForTest(long currentTimeMillis) { - mCurrentTimeMillisForTest = currentTimeMillis; - } - - /** - * Returns the current time in milliseconds since the epoch. - * <p> - * It can be injected in tests using {@link #setCurrentTimeForTest(long)}. - */ - private long getCurrentTimeMillis() { - if (mCurrentTimeMillisForTest == null) { - return System.currentTimeMillis(); - } else { - return mCurrentTimeMillisForTest; - } - } - - /** Sets the call count, date, and if it is a voicemail, sets the duration. */ - private void setDetailText(PhoneCallDetailsViews views, Integer callCount, - PhoneCallDetails details) { - // Combine the count (if present) and the date. - CharSequence dateText = getCallLocationAndDate(details); - final CharSequence text; - if (callCount != null) { - text = mResources.getString( - R.string.call_log_item_count_and_date, callCount.intValue(), dateText); - } else { - text = dateText; - } - - if (details.callTypes[0] == Calls.VOICEMAIL_TYPE && details.duration > 0) { - views.callLocationAndDate.setText(mResources.getString( - R.string.voicemailCallLogDateTimeFormatWithDuration, text, - getVoicemailDuration(details))); - } else { - views.callLocationAndDate.setText(text); - } - - } - - private String getVoicemailDuration(PhoneCallDetails details) { - long minutes = TimeUnit.SECONDS.toMinutes(details.duration); - long seconds = details.duration - TimeUnit.MINUTES.toSeconds(minutes); - if (minutes > 99) { - minutes = 99; - } - return mResources.getString(R.string.voicemailDurationFormat, minutes, seconds); - } -} diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsViews.java b/src/com/android/dialer/calllog/PhoneCallDetailsViews.java deleted file mode 100644 index 94f4411b0..000000000 --- a/src/com/android/dialer/calllog/PhoneCallDetailsViews.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.Context; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.dialer.R; - -/** - * Encapsulates the views that are used to display the details of a phone call in the call log. - */ -public final class PhoneCallDetailsViews { - public final TextView nameView; - public final View callTypeView; - public final CallTypeIconsView callTypeIcons; - public final TextView callLocationAndDate; - public final TextView voicemailTranscriptionView; - public final TextView callAccountLabel; - - private PhoneCallDetailsViews(TextView nameView, View callTypeView, - CallTypeIconsView callTypeIcons, TextView callLocationAndDate, - TextView voicemailTranscriptionView, TextView callAccountLabel) { - this.nameView = nameView; - this.callTypeView = callTypeView; - this.callTypeIcons = callTypeIcons; - this.callLocationAndDate = callLocationAndDate; - this.voicemailTranscriptionView = voicemailTranscriptionView; - this.callAccountLabel = callAccountLabel; - } - - /** - * Create a new instance by extracting the elements from the given view. - * <p> - * The view should contain three text views with identifiers {@code R.id.name}, - * {@code R.id.date}, and {@code R.id.number}, and a linear layout with identifier - * {@code R.id.call_types}. - */ - public static PhoneCallDetailsViews fromView(View view) { - return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name), - view.findViewById(R.id.call_type), - (CallTypeIconsView) view.findViewById(R.id.call_type_icons), - (TextView) view.findViewById(R.id.call_location_and_date), - (TextView) view.findViewById(R.id.voicemail_transcription), - (TextView) view.findViewById(R.id.call_account_label)); - } - - public static PhoneCallDetailsViews createForTest(Context context) { - return new PhoneCallDetailsViews( - new TextView(context), - new View(context), - new CallTypeIconsView(context), - new TextView(context), - new TextView(context), - new TextView(context)); - } -} diff --git a/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java b/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java deleted file mode 100644 index 5b1fc9e3a..000000000 --- a/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.content.Context; -import android.provider.CallLog.Calls; -import android.text.TextUtils; - -import com.android.dialer.R; -import com.android.dialer.util.PhoneNumberUtil; - -/** - * Helper for formatting and managing the display of phone numbers. - */ -public class PhoneNumberDisplayUtil { - - /** - * Returns the string to display for the given phone number if there is no matching contact. - */ - /* package */ static CharSequence getDisplayName( - Context context, - CharSequence number, - int presentation, - boolean isVoicemail) { - if (presentation == Calls.PRESENTATION_UNKNOWN) { - return context.getResources().getString(R.string.unknown); - } - if (presentation == Calls.PRESENTATION_RESTRICTED) { - return context.getResources().getString(R.string.private_num); - } - if (presentation == Calls.PRESENTATION_PAYPHONE) { - return context.getResources().getString(R.string.payphone); - } - if (isVoicemail) { - return context.getResources().getString(R.string.voicemail); - } - if (PhoneNumberUtil.isLegacyUnknownNumbers(number)) { - return context.getResources().getString(R.string.unknown); - } - return ""; - } - - /** - * Returns the string to display for the given phone number. - * - * @param number the number to display - * @param formattedNumber the formatted number if available, may be null - */ - public static CharSequence getDisplayNumber( - Context context, - CharSequence number, - int presentation, - CharSequence formattedNumber, - CharSequence postDialDigits, - boolean isVoicemail) { - final CharSequence displayName = getDisplayName(context, number, presentation, isVoicemail); - if (!TextUtils.isEmpty(displayName)) { - return displayName; - } - - if (!TextUtils.isEmpty(formattedNumber)) { - return formattedNumber; - } else if (!TextUtils.isEmpty(number)) { - return number.toString() + postDialDigits; - } else { - return context.getResources().getString(R.string.unknown); - } - } -} diff --git a/src/com/android/dialer/calllog/PhoneQuery.java b/src/com/android/dialer/calllog/PhoneQuery.java deleted file mode 100644 index f1f14c66e..000000000 --- a/src/com/android/dialer/calllog/PhoneQuery.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.PhoneLookup; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.PhoneLookupSdkCompat; -import com.android.contacts.common.ContactsUtils; - -/** - * The queries to look up the {@link ContactInfo} for a given number in the Call Log. - */ -final class PhoneQuery { - - /** - * Projection to look up the ContactInfo. Does not include DISPLAY_NAME_ALTERNATIVE as that - * column isn't available in ContactsCommon.PhoneLookup. - * We should always use this projection starting from NYC onward. - */ - private static final String[] PHONE_LOOKUP_PROJECTION = new String[] { - PhoneLookupSdkCompat.CONTACT_ID, - PhoneLookup.DISPLAY_NAME, - PhoneLookup.TYPE, - PhoneLookup.LABEL, - PhoneLookup.NUMBER, - PhoneLookup.NORMALIZED_NUMBER, - PhoneLookup.PHOTO_ID, - PhoneLookup.LOOKUP_KEY, - PhoneLookup.PHOTO_URI - }; - - /** - * Similar to {@link PHONE_LOOKUP_PROJECTION}. In pre-N, contact id is stored in - * {@link PhoneLookup#_ID} in non-sip query. - */ - private static final String[] BACKWARD_COMPATIBLE_NON_SIP_PHONE_LOOKUP_PROJECTION = - new String[] { - PhoneLookup._ID, - PhoneLookup.DISPLAY_NAME, - PhoneLookup.TYPE, - PhoneLookup.LABEL, - PhoneLookup.NUMBER, - PhoneLookup.NORMALIZED_NUMBER, - PhoneLookup.PHOTO_ID, - PhoneLookup.LOOKUP_KEY, - PhoneLookup.PHOTO_URI - }; - - public static String[] getPhoneLookupProjection(Uri phoneLookupUri) { - if (CompatUtils.isNCompatible()) { - return PHONE_LOOKUP_PROJECTION; - } - // Pre-N - boolean isSip = phoneLookupUri.getBooleanQueryParameter( - ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, false); - return (isSip) ? PHONE_LOOKUP_PROJECTION - : BACKWARD_COMPATIBLE_NON_SIP_PHONE_LOOKUP_PROJECTION; - } - - public static final int PERSON_ID = 0; - public static final int NAME = 1; - public static final int PHONE_TYPE = 2; - public static final int LABEL = 3; - public static final int MATCHED_NUMBER = 4; - public static final int NORMALIZED_NUMBER = 5; - public static final int PHOTO_ID = 6; - public static final int LOOKUP_KEY = 7; - public static final int PHOTO_URI = 8; - - /** - * Projection to look up a contact's DISPLAY_NAME_ALTERNATIVE - */ - public static final String[] DISPLAY_NAME_ALTERNATIVE_PROJECTION = new String[] { - Contacts.DISPLAY_NAME_ALTERNATIVE, - }; - - public static final int NAME_ALTERNATIVE = 0; -} diff --git a/src/com/android/dialer/calllog/PromoCardViewHolder.java b/src/com/android/dialer/calllog/PromoCardViewHolder.java deleted file mode 100644 index f5a7501fc..000000000 --- a/src/com/android/dialer/calllog/PromoCardViewHolder.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.dialer.calllog; - -import android.content.Context; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import com.android.contacts.common.testing.NeededForTesting; -import com.android.dialer.R; - -/** - * Generic ViewHolder class for a promo card with a primary and secondary action. - * Example: the promo card which appears in the voicemail tab. - */ -public class PromoCardViewHolder extends RecyclerView.ViewHolder { - public static PromoCardViewHolder create(View rootView) { - return new PromoCardViewHolder(rootView); - } - - /** - * The primary action button view. - */ - private View mPrimaryActionView; - - /** - * The secondary action button view. - * The "Ok" button view. - */ - private View mSecondaryActionView; - - /** - * Creates an instance of the {@link ViewHolder}. - * - * @param rootView The root view. - */ - private PromoCardViewHolder(View rootView) { - super(rootView); - - mPrimaryActionView = rootView.findViewById(R.id.primary_action); - mSecondaryActionView = rootView.findViewById(R.id.secondary_action); - } - - /** - * Retrieves the "primary" action button (eg. "OK"). - * - * @return The view. - */ - public View getPrimaryActionView() { - return mPrimaryActionView; - } - - /** - * Retrieves the "secondary" action button (eg. "Cancel" or "More Info"). - * - * @return The view. - */ - public View getSecondaryActionView() { - return mSecondaryActionView; - } - - @NeededForTesting - public static PromoCardViewHolder createForTest(Context context) { - PromoCardViewHolder viewHolder = new PromoCardViewHolder(new View(context)); - viewHolder.mPrimaryActionView = new View(context); - viewHolder.mSecondaryActionView = new View(context); - return viewHolder; - } -} diff --git a/src/com/android/dialer/calllog/VisualVoicemailCallLogFragment.java b/src/com/android/dialer/calllog/VisualVoicemailCallLogFragment.java deleted file mode 100644 index 311ff7dc5..000000000 --- a/src/com/android/dialer/calllog/VisualVoicemailCallLogFragment.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.calllog; - -import android.database.ContentObserver; -import android.os.Bundle; -import android.provider.CallLog; -import android.provider.VoicemailContract; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.dialer.R; -import com.android.dialer.list.ListsFragment; -import com.android.dialer.voicemail.VoicemailPlaybackPresenter; - -public class VisualVoicemailCallLogFragment extends CallLogFragment { - - private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; - private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver(); - - public VisualVoicemailCallLogFragment() { - super(CallLog.Calls.VOICEMAIL_TYPE); - } - - @Override - public void onCreate(Bundle state) { - super.onCreate(state); - mVoicemailPlaybackPresenter = VoicemailPlaybackPresenter.getInstance(getActivity(), state); - getActivity().getContentResolver().registerContentObserver( - VoicemailContract.Status.CONTENT_URI, true, mVoicemailStatusObserver); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { - View view = inflater.inflate(R.layout.call_log_fragment, container, false); - setupView(view, mVoicemailPlaybackPresenter); - return view; - } - - @Override - public void onResume() { - super.onResume(); - mVoicemailPlaybackPresenter.onResume(); - } - - @Override - public void onPause() { - mVoicemailPlaybackPresenter.onPause(); - super.onPause(); - } - - @Override - public void onDestroy() { - mVoicemailPlaybackPresenter.onDestroy(); - getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver); - super.onDestroy(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mVoicemailPlaybackPresenter.onSaveInstanceState(outState); - } - - @Override - public void fetchCalls() { - super.fetchCalls(); - ((ListsFragment) getParentFragment()).updateTabUnreadCounts(); - } -} diff --git a/src/com/android/dialer/calllog/VoicemailQueryHandler.java b/src/com/android/dialer/calllog/VoicemailQueryHandler.java deleted file mode 100644 index c6e644c32..000000000 --- a/src/com/android/dialer/calllog/VoicemailQueryHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.dialer.calllog; - -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.provider.CallLog.Calls; -import android.util.Log; - -/** - * Handles asynchronous queries to the call log for voicemail. - */ -public class VoicemailQueryHandler extends AsyncQueryHandler { - private static final String TAG = "VoicemailQueryHandler"; - - /** The token for the query to mark all new voicemails as old. */ - private static final int UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN = 50; - private Context mContext; - - public VoicemailQueryHandler(Context context, ContentResolver contentResolver) { - super(contentResolver); - mContext = context; - } - - /** Updates all new voicemails to mark them as old. */ - public void markNewVoicemailsAsOld() { - // Mark all "new" voicemails as not new anymore. - StringBuilder where = new StringBuilder(); - where.append(Calls.NEW); - where.append(" = 1 AND "); - where.append(Calls.TYPE); - where.append(" = ?"); - - ContentValues values = new ContentValues(1); - values.put(Calls.NEW, "0"); - - startUpdate(UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL, - values, where.toString(), new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) }); - } - - @Override - protected void onUpdateComplete(int token, Object cookie, int result) { - if (token == UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN) { - if (mContext != null) { - Intent serviceIntent = new Intent(mContext, CallLogNotificationsService.class); - serviceIntent.setAction( - CallLogNotificationsService.ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS); - mContext.startService(serviceIntent); - } else { - Log.w(TAG, "Unknown update completed: ignoring: " + token); - } - } - } -} diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCache.java b/src/com/android/dialer/calllog/calllogcache/CallLogCache.java deleted file mode 100644 index dc1217cf5..000000000 --- a/src/com/android/dialer/calllog/calllogcache/CallLogCache.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog.calllogcache; - -import android.content.Context; -import android.telecom.PhoneAccountHandle; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.compat.CompatUtils; -import com.android.dialer.calllog.CallLogAdapter; - -/** - * This is the base class for the CallLogCaches. - * - * Keeps a cache of recently made queries to the Telecom/Telephony processes. The aim of this cache - * is to reduce the number of cross-process requests to TelecomManager, which can negatively affect - * performance. - * - * This is designed with the specific use case of the {@link CallLogAdapter} in mind. - */ -public abstract class CallLogCache { - // TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of - // this writing, that was a much larger undertaking than creating this cache. - - protected final Context mContext; - - private boolean mHasCheckedForVideoEnabled; - private boolean mIsVideoEnabled; - - public CallLogCache(Context context) { - mContext = context; - } - - /** - * Return the most compatible version of the TelecomCallLogCache. - */ - public static CallLogCache getCallLogCache(Context context) { - if (CompatUtils.isClassAvailable("android.telecom.PhoneAccountHandle")) { - return new CallLogCacheLollipopMr1(context); - } - return new CallLogCacheLollipop(context); - } - - public void reset() { - mHasCheckedForVideoEnabled = false; - mIsVideoEnabled = false; - } - - /** - * Returns true if the given number is the number of the configured voicemail. To be able to - * mock-out this, it is not a static method. - */ - public abstract boolean isVoicemailNumber(PhoneAccountHandle accountHandle, - CharSequence number); - - public boolean isVideoEnabled() { - if (!mHasCheckedForVideoEnabled) { - mIsVideoEnabled = CallUtil.isVideoEnabled(mContext); - mHasCheckedForVideoEnabled = true; - } - return mIsVideoEnabled; - } - - /** - * Extract account label from PhoneAccount object. - */ - public abstract String getAccountLabel(PhoneAccountHandle accountHandle); - - /** - * Extract account color from PhoneAccount object. - */ - public abstract int getAccountColor(PhoneAccountHandle accountHandle); - - /** - * Determines if the PhoneAccount supports specifying a call subject (i.e. calling with a note) - * for outgoing calls. - * - * @param accountHandle The PhoneAccount handle. - * @return {@code true} if calling with a note is supported, {@code false} otherwise. - */ - public abstract boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle); -} diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java deleted file mode 100644 index 770cc9d3e..000000000 --- a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog.calllogcache; - -import android.content.Context; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; - -/** - * This is a compatibility class for the CallLogCache for versions of dialer before Lollipop Mr1 - * (the introduction of phone accounts). - * - * This class should not be initialized directly and instead be acquired from - * {@link CallLogCache#getCallLogCache}. - */ -class CallLogCacheLollipop extends CallLogCache { - private String mVoicemailNumber; - - /* package */ CallLogCacheLollipop(Context context) { - super(context); - } - - @Override - public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) { - if (TextUtils.isEmpty(number)) { - return false; - } - - String numberString = number.toString(); - - if (!TextUtils.isEmpty(mVoicemailNumber)) { - return PhoneNumberUtils.compare(numberString, mVoicemailNumber); - } - - if (PhoneNumberUtils.isVoiceMailNumber(numberString)) { - mVoicemailNumber = numberString; - return true; - } - - return false; - } - - @Override - public String getAccountLabel(PhoneAccountHandle accountHandle) { - return null; - } - - @Override - public int getAccountColor(PhoneAccountHandle accountHandle) { - return PhoneAccount.NO_HIGHLIGHT_COLOR; - } - - @Override - public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) { - return false; - } -} diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java deleted file mode 100644 index d1e3f7bcf..000000000 --- a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog.calllogcache; - -import android.content.Context; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; -import android.util.Pair; - -import com.android.dialer.calllog.PhoneAccountUtils; -import com.android.dialer.util.PhoneNumberUtil; - -import java.util.HashMap; -import java.util.Map; - -/** - * This is the CallLogCache for versions of dialer Lollipop Mr1 and above with support for - * multi-SIM devices. - * - * This class should not be initialized directly and instead be acquired from - * {@link CallLogCache#getCallLogCache}. - */ -class CallLogCacheLollipopMr1 extends CallLogCache { - // Maps from a phone-account/number pair to a boolean because multiple numbers could return true - // for the voicemail number if those numbers are not pre-normalized. - private final Map<Pair<PhoneAccountHandle, CharSequence>, Boolean> mVoicemailQueryCache = - new HashMap<>(); - private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new HashMap<>(); - private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new HashMap<>(); - private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new HashMap<>(); - - /* package */ CallLogCacheLollipopMr1(Context context) { - super(context); - } - - @Override - public void reset() { - mVoicemailQueryCache.clear(); - mPhoneAccountLabelCache.clear(); - mPhoneAccountColorCache.clear(); - mPhoneAccountCallWithNoteCache.clear(); - - super.reset(); - } - - @Override - public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) { - if (TextUtils.isEmpty(number)) { - return false; - } - - Pair<PhoneAccountHandle, CharSequence> key = new Pair<>(accountHandle, number); - if (mVoicemailQueryCache.containsKey(key)) { - return mVoicemailQueryCache.get(key); - } else { - Boolean isVoicemail = - PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, number.toString()); - mVoicemailQueryCache.put(key, isVoicemail); - return isVoicemail; - } - } - - @Override - public String getAccountLabel(PhoneAccountHandle accountHandle) { - if (mPhoneAccountLabelCache.containsKey(accountHandle)) { - return mPhoneAccountLabelCache.get(accountHandle); - } else { - String label = PhoneAccountUtils.getAccountLabel(mContext, accountHandle); - mPhoneAccountLabelCache.put(accountHandle, label); - return label; - } - } - - @Override - public int getAccountColor(PhoneAccountHandle accountHandle) { - if (mPhoneAccountColorCache.containsKey(accountHandle)) { - return mPhoneAccountColorCache.get(accountHandle); - } else { - Integer color = PhoneAccountUtils.getAccountColor(mContext, accountHandle); - mPhoneAccountColorCache.put(accountHandle, color); - return color; - } - } - - @Override - public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) { - if (mPhoneAccountCallWithNoteCache.containsKey(accountHandle)) { - return mPhoneAccountCallWithNoteCache.get(accountHandle); - } else { - Boolean supportsCallWithNote = - PhoneAccountUtils.getAccountSupportsCallSubject(mContext, accountHandle); - mPhoneAccountCallWithNoteCache.put(accountHandle, supportsCallWithNote); - return supportsCallWithNote; - } - } -} |