diff options
Diffstat (limited to 'src/com/android/dialer')
138 files changed, 0 insertions, 31437 deletions
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java deleted file mode 100644 index 94c2f0018..000000000 --- a/src/com/android/dialer/CallDetailActivity.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright (C) 2009 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; - -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v7.app.AppCompatActivity; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ListView; -import android.widget.QuickContactBadge; -import android.widget.TextView; -import android.widget.Toast; - -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.GeoUtil; -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.interactions.TouchPointManager; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.testing.NeededForTesting; -import com.android.contacts.common.util.UriUtils; -import com.android.dialer.calllog.CallDetailHistoryAdapter; -import com.android.dialer.calllog.CallLogAsyncTaskUtil; -import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener; -import com.android.dialer.calllog.CallTypeHelper; -import com.android.dialer.calllog.ContactInfoHelper; -import com.android.dialer.calllog.PhoneAccountUtils; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; -import com.android.dialer.filterednumber.BlockNumberDialogFragment; -import com.android.dialer.filterednumber.FilteredNumbersUtil; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil.CallIntentBuilder; -import com.android.dialer.util.PhoneNumberUtil; -import com.android.dialer.util.TelecomUtil; -import com.android.incallui.Call.LogState; - -/** - * Displays the details of a specific call log entry. - * <p> - * This activity can be either started with the URI of a single call log entry, or with the - * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries. - */ -public class CallDetailActivity extends AppCompatActivity - implements MenuItem.OnMenuItemClickListener, View.OnClickListener, - BlockNumberDialogFragment.Callback { - private static final String TAG = CallDetailActivity.class.getSimpleName(); - - /** A long array extra containing ids of call log entries to display. */ - public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS"; - /** If we are started with a voicemail, we'll find the uri to play with this extra. */ - public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI"; - /** If the activity was triggered from a notification. */ - public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; - - public static final String VOICEMAIL_FRAGMENT_TAG = "voicemail_fragment"; - - private CallLogAsyncTaskListener mCallLogAsyncTaskListener = new CallLogAsyncTaskListener() { - @Override - public void onDeleteCall() { - finish(); - } - - @Override - public void onDeleteVoicemail() { - finish(); - } - - @Override - public void onGetCallDetails(PhoneCallDetails[] details) { - if (details == null) { - // Somewhere went wrong: we're going to bail out and show error to users. - Toast.makeText(mContext, R.string.toast_call_detail_error, - Toast.LENGTH_SHORT).show(); - finish(); - return; - } - - // All calls are from the same number and same contact, so pick the first detail. - mDetails = details[0]; - mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString(); - mPostDialDigits = TextUtils.isEmpty(mDetails.postDialDigits) - ? "" : mDetails.postDialDigits; - mDisplayNumber = mDetails.displayNumber; - - final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails); - - final CharSequence displayNumber; - if (!TextUtils.isEmpty(mDetails.postDialDigits)) { - displayNumber = mDetails.number + mDetails.postDialDigits; - } else { - displayNumber = mDetails.displayNumber; - } - - final String displayNumberStr = mBidiFormatter.unicodeWrap( - displayNumber.toString(), TextDirectionHeuristics.LTR); - - mDetails.nameDisplayOrder = mContactsPreferences.getDisplayOrder(); - - if (!TextUtils.isEmpty(mDetails.getPreferredName())) { - mCallerName.setText(mDetails.getPreferredName()); - mCallerNumber.setText(callLocationOrType + " " + displayNumberStr); - } else { - mCallerName.setText(displayNumberStr); - if (!TextUtils.isEmpty(callLocationOrType)) { - mCallerNumber.setText(callLocationOrType); - mCallerNumber.setVisibility(View.VISIBLE); - } else { - mCallerNumber.setVisibility(View.GONE); - } - } - - CharSequence accountLabel = PhoneAccountUtils.getAccountLabel(mContext, - mDetails.accountHandle); - CharSequence accountContentDescription = - PhoneCallDetails.createAccountLabelDescription(mResources, mDetails.viaNumber, - accountLabel); - if (!TextUtils.isEmpty(mDetails.viaNumber)) { - if (!TextUtils.isEmpty(accountLabel)) { - accountLabel = mResources.getString(R.string.call_log_via_number_phone_account, - accountLabel, mDetails.viaNumber); - } else { - accountLabel = mResources.getString(R.string.call_log_via_number, - mDetails.viaNumber); - } - } - if (!TextUtils.isEmpty(accountLabel)) { - mAccountLabel.setText(accountLabel); - mAccountLabel.setContentDescription(accountContentDescription); - mAccountLabel.setVisibility(View.VISIBLE); - } else { - mAccountLabel.setVisibility(View.GONE); - } - - final boolean canPlaceCallsTo = - PhoneNumberUtil.canPlaceCallsTo(mNumber, mDetails.numberPresentation); - mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - mCopyNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - - updateBlockActionItemVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - - final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber); - final boolean isVoicemailNumber = - PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); - final boolean showEditNumberBeforeCallAction = - canPlaceCallsTo && !isSipNumber && !isVoicemailNumber; - mEditBeforeCallActionItem.setVisibility( - showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE); - - final boolean showReportAction = mContactInfoHelper.canReportAsInvalid( - mDetails.sourceType, mDetails.objectId); - mReportActionItem.setVisibility( - showReportAction ? View.VISIBLE : View.GONE); - - invalidateOptionsMenu(); - - mHistoryList.setAdapter( - new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details)); - - updateFilteredNumberChanges(); - updateContactPhoto(); - - findViewById(R.id.call_detail).setVisibility(View.VISIBLE); - } - - /** - * Determines the location geocode text for a call, or the phone number type - * (if available). - * - * @param details The call details. - * @return The phone number type or location. - */ - private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) { - if (!TextUtils.isEmpty(details.namePrimary)) { - return Phone.getTypeLabel(mResources, details.numberType, - details.numberLabel); - } else { - return details.geocode; - } - } - }; - - private Context mContext; - private ContactInfoHelper mContactInfoHelper; - private ContactsPreferences mContactsPreferences; - private CallTypeHelper mCallTypeHelper; - private ContactPhotoManager mContactPhotoManager; - private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); - private LayoutInflater mInflater; - private Resources mResources; - - private PhoneCallDetails mDetails; - protected String mNumber; - private Uri mVoicemailUri; - private String mPostDialDigits = ""; - private String mDisplayNumber; - - private ListView mHistoryList; - private QuickContactBadge mQuickContactBadge; - private TextView mCallerName; - private TextView mCallerNumber; - private TextView mAccountLabel; - private View mCallButton; - - private TextView mBlockNumberActionItem; - private View mEditBeforeCallActionItem; - private View mReportActionItem; - private View mCopyNumberActionItem; - - private Integer mBlockedNumberId; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mContext = this; - mResources = getResources(); - mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)); - mContactsPreferences = new ContactsPreferences(mContext); - mCallTypeHelper = new CallTypeHelper(getResources()); - mFilteredNumberAsyncQueryHandler = - new FilteredNumberAsyncQueryHandler(getContentResolver()); - - mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - setContentView(R.layout.call_detail); - mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); - - mHistoryList = (ListView) findViewById(R.id.history); - mHistoryList.addHeaderView(mInflater.inflate(R.layout.call_detail_header, null)); - mHistoryList.addFooterView( - mInflater.inflate(R.layout.call_detail_footer, null), null, false); - - mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo); - mQuickContactBadge.setOverlay(null); - if (CompatUtils.hasPrioritizedMimeType()) { - mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); - } - mCallerName = (TextView) findViewById(R.id.caller_name); - mCallerNumber = (TextView) findViewById(R.id.caller_number); - mAccountLabel = (TextView) findViewById(R.id.phone_account_label); - mContactPhotoManager = ContactPhotoManager.getInstance(this); - - mCallButton = findViewById(R.id.call_back_button); - mCallButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (TextUtils.isEmpty(mNumber)) { - return; - } - mContext.startActivity( - new CallIntentBuilder(getDialableNumber()) - .setCallInitiationType(LogState.INITIATION_CALL_DETAILS) - .build()); - } - }); - - - mBlockNumberActionItem = (TextView) findViewById(R.id.call_detail_action_block); - updateBlockActionItemVisibility(View.VISIBLE); - mBlockNumberActionItem.setOnClickListener(this); - mEditBeforeCallActionItem = findViewById(R.id.call_detail_action_edit_before_call); - mEditBeforeCallActionItem.setOnClickListener(this); - mReportActionItem = findViewById(R.id.call_detail_action_report); - mReportActionItem.setOnClickListener(this); - - mCopyNumberActionItem = findViewById(R.id.call_detail_action_copy); - mCopyNumberActionItem.setOnClickListener(this); - - if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) { - closeSystemDialogs(); - } - } - - private void updateBlockActionItemVisibility(int visibility) { - if (!FilteredNumberCompat.canAttemptBlockOperations(mContext)) { - visibility = View.GONE; - } - mBlockNumberActionItem.setVisibility(visibility); - } - - @Override - public void onResume() { - super.onResume(); - mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); - getCallDetails(); - } - - @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); - } - - public void getCallDetails() { - CallLogAsyncTaskUtil.getCallDetails(this, getCallLogEntryUris(), mCallLogAsyncTaskListener); - } - - /** - * Returns the list of URIs to show. - * <p> - * There are two ways the URIs can be provided to the activity: as the data on the intent, or as - * a list of ids in the call log added as an extra on the URI. - * <p> - * If both are available, the data on the intent takes precedence. - */ - private Uri[] getCallLogEntryUris() { - final Uri uri = getIntent().getData(); - if (uri != null) { - // If there is a data on the intent, it takes precedence over the extra. - return new Uri[]{ uri }; - } - final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS); - final int numIds = ids == null ? 0 : ids.length; - final Uri[] uris = new Uri[numIds]; - for (int index = 0; index < numIds; ++index) { - uris[index] = ContentUris.withAppendedId( - TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]); - } - return uris; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - final MenuItem deleteMenuItem = menu.add( - Menu.NONE, - R.id.call_detail_delete_menu_item, - Menu.NONE, - R.string.call_details_delete); - deleteMenuItem.setIcon(R.drawable.ic_delete_24dp); - deleteMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - deleteMenuItem.setOnMenuItemClickListener(this); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.call_detail_delete_menu_item) { - if (hasVoicemail()) { - CallLogAsyncTaskUtil.deleteVoicemail( - this, mVoicemailUri, mCallLogAsyncTaskListener); - } else { - final StringBuilder callIds = new StringBuilder(); - for (Uri callUri : getCallLogEntryUris()) { - if (callIds.length() != 0) { - callIds.append(","); - } - callIds.append(ContentUris.parseId(callUri)); - } - CallLogAsyncTaskUtil.deleteCalls( - this, callIds.toString(), mCallLogAsyncTaskListener); - } - } - return true; - } - - @Override - public void onClick(View view) { - int resId = view.getId(); - if (resId == R.id.call_detail_action_block) { - FilteredNumberCompat - .showBlockNumberDialogFlow(mContext.getContentResolver(), mBlockedNumberId, - mNumber, mDetails.countryIso, mDisplayNumber, R.id.call_detail, - getFragmentManager(), this); - } else if (resId == R.id.call_detail_action_copy) { - ClipboardUtils.copyText(mContext, null, mNumber, true); - } else if (resId == R.id.call_detail_action_edit_before_call) { - Intent dialIntent = new Intent(Intent.ACTION_DIAL, - CallUtil.getCallUri(getDialableNumber())); - DialerUtils.startActivityWithErrorToast(mContext, dialIntent); - } else { - Log.wtf(TAG, "Unexpected onClick event from " + view); - } - } - - @Override - public void onFilterNumberSuccess() { - Logger.logInteraction(InteractionEvent.BLOCK_NUMBER_CALL_DETAIL); - updateFilteredNumberChanges(); - } - - @Override - public void onUnfilterNumberSuccess() { - Logger.logInteraction(InteractionEvent.UNBLOCK_NUMBER_CALL_DETAIL); - updateFilteredNumberChanges(); - } - - @Override - public void onChangeFilteredNumberUndo() { - updateFilteredNumberChanges(); - } - - private void updateFilteredNumberChanges() { - if (mDetails == null || - !FilteredNumbersUtil.canBlockNumber(this, mNumber, mDetails.countryIso)) { - return; - } - - final boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber( - new OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - mBlockedNumberId = id; - updateBlockActionItem(); - } - }, mNumber, mDetails.countryIso); - - if (!success) { - updateBlockActionItem(); - } - } - - // Loads and displays the contact photo. - private void updateContactPhoto() { - if (mDetails == null) { - return; - } - - final boolean isVoicemailNumber = - PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); - final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType); - int contactType = ContactPhotoManager.TYPE_DEFAULT; - if (isVoicemailNumber) { - contactType = ContactPhotoManager.TYPE_VOICEMAIL; - } else if (isBusiness) { - contactType = ContactPhotoManager.TYPE_BUSINESS; - } - - final String displayName = TextUtils.isEmpty(mDetails.namePrimary) - ? mDetails.displayNumber : mDetails.namePrimary.toString(); - final String lookupKey = mDetails.contactUri == null - ? null : UriUtils.getLookupKeyFromUri(mDetails.contactUri); - - final DefaultImageRequest request = - new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */); - - mQuickContactBadge.assignContactUri(mDetails.contactUri); - mQuickContactBadge.setContentDescription( - mResources.getString(R.string.description_contact_details, displayName)); - - mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, mDetails.photoUri, - false /* darkTheme */, true /* isCircular */, request); - } - - private void updateBlockActionItem() { - if (mBlockedNumberId == null) { - mBlockNumberActionItem.setText(R.string.action_block_number); - mBlockNumberActionItem.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.ic_call_detail_block, 0, 0, 0); - } else { - mBlockNumberActionItem.setText(R.string.action_unblock_number); - mBlockNumberActionItem.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.ic_call_detail_unblock, 0, 0, 0); - } - } - - private void closeSystemDialogs() { - sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - private String getDialableNumber() { - return mNumber + mPostDialDigits; - } - - @NeededForTesting - public boolean hasVoicemail() { - return mVoicemailUri != null; - } -} diff --git a/src/com/android/dialer/DialerApplication.java b/src/com/android/dialer/DialerApplication.java deleted file mode 100644 index 1a0497bb9..000000000 --- a/src/com/android/dialer/DialerApplication.java +++ /dev/null @@ -1,58 +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; - -import android.app.Application; -import android.content.Context; -import android.os.Trace; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; - -import com.android.contacts.common.extensions.ExtensionsFactory; -import com.android.contacts.common.testing.NeededForTesting; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.filterednumber.BlockedNumbersAutoMigrator; - -public class DialerApplication extends Application { - - private static final String TAG = "DialerApplication"; - - private static Context sContext; - - @Override - public void onCreate() { - sContext = this; - Trace.beginSection(TAG + " onCreate"); - super.onCreate(); - Trace.beginSection(TAG + " ExtensionsFactory initialization"); - ExtensionsFactory.init(getApplicationContext()); - Trace.endSection(); - new BlockedNumbersAutoMigrator(PreferenceManager.getDefaultSharedPreferences(this), - new FilteredNumberAsyncQueryHandler(getContentResolver())).autoMigrate(); - Trace.endSection(); - } - - @Nullable - public static Context getContext() { - return sContext; - } - - @NeededForTesting - public static void setContextForTest(Context context) { - sContext = context; - } -} diff --git a/src/com/android/dialer/DialerBackupAgent.java b/src/com/android/dialer/DialerBackupAgent.java deleted file mode 100644 index 928be029c..000000000 --- a/src/com/android/dialer/DialerBackupAgent.java +++ /dev/null @@ -1,38 +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; - -import android.app.backup.BackupAgentHelper; -import android.app.backup.BackupDataInput; -import android.app.backup.SharedPreferencesBackupHelper; -import android.content.Context; - -/** - * The Dialer backup agent backs up the shared preferences settings of the - * Dialer App. Right now it backs up the whole shared preference file. This - * can be modified in the future to accommodate partical backup. - */ -public class DialerBackupAgent extends BackupAgentHelper -{ - private static final String SHARED_KEY = "shared_pref"; - - @Override - public void onCreate() { - addHelper(SHARED_KEY, new SharedPreferencesBackupHelper(this, - DialtactsActivity.SHARED_PREFS_NAME)); - } -} diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java deleted file mode 100644 index 441501cfd..000000000 --- a/src/com/android/dialer/DialtactsActivity.java +++ /dev/null @@ -1,1413 +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; - -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Bundle; -import android.os.Trace; -import android.provider.CallLog.Calls; -import android.speech.RecognizerIntent; -import android.support.design.widget.CoordinatorLayout; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.telecom.PhoneAccount; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Log; -import android.view.DragEvent; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnDragListener; -import android.view.ViewTreeObserver; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.AbsListView.OnScrollListener; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.PopupMenu; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.contacts.common.dialog.ClearFrequentsDialog; -import com.android.contacts.common.interactions.ImportExportDialogFragment; -import com.android.contacts.common.interactions.TouchPointManager; -import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.common.widget.FloatingActionButtonController; -import com.android.dialer.calllog.CallLogActivity; -import com.android.dialer.calllog.CallLogFragment; -import com.android.dialer.database.DialerDatabaseHelper; -import com.android.dialer.dialpad.DialpadFragment; -import com.android.dialer.dialpad.SmartDialNameMatcher; -import com.android.dialer.dialpad.SmartDialPrefix; -import com.android.dialer.interactions.PhoneNumberInteraction; -import com.android.dialer.list.DragDropController; -import com.android.dialer.list.ListsFragment; -import com.android.dialer.list.OnDragDropListener; -import com.android.dialer.list.OnListFragmentScrolledListener; -import com.android.dialer.list.PhoneFavoriteSquareTileView; -import com.android.dialer.list.RegularSearchFragment; -import com.android.dialer.list.SearchFragment; -import com.android.dialer.list.SmartDialSearchFragment; -import com.android.dialer.list.SpeedDialFragment; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.settings.DialerSettingsActivity; -import com.android.dialer.util.Assert; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.util.IntentUtil.CallIntentBuilder; -import com.android.dialer.util.TelecomUtil; -import com.android.dialer.voicemail.VoicemailArchiveActivity; -import com.android.dialer.widget.ActionBarController; -import com.android.dialer.widget.SearchEditTextLayout; -import com.android.dialerbind.DatabaseHelperManager; -import com.android.dialerbind.ObjectFactory; -import com.android.phone.common.animation.AnimUtils; -import com.android.phone.common.animation.AnimationListenerAdapter; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.List; - -/** - * The dialer tab's title is 'phone', a more common name (see strings.xml). - */ -public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, - DialpadFragment.OnDialpadQueryChangedListener, - OnListFragmentScrolledListener, - CallLogFragment.HostInterface, - DialpadFragment.HostInterface, - ListsFragment.HostInterface, - SpeedDialFragment.HostInterface, - SearchFragment.HostInterface, - OnDragDropListener, - OnPhoneNumberPickerActionListener, - PopupMenu.OnMenuItemClickListener, - ViewPager.OnPageChangeListener, - ActionBarController.ActivityUi { - private static final String TAG = "DialtactsActivity"; - - public static final boolean DEBUG = false; - - public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; - - private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; - private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; - private static final String KEY_SEARCH_QUERY = "search_query"; - private static final String KEY_FIRST_LAUNCH = "first_launch"; - private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown"; - - @VisibleForTesting - public static final String TAG_DIALPAD_FRAGMENT = "dialpad"; - private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; - private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; - private static final String TAG_FAVORITES_FRAGMENT = "favorites"; - - /** - * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. - */ - private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; - public static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB"; - - private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; - - private static final int FAB_SCALE_IN_DELAY_MS = 300; - - private CoordinatorLayout mParentLayout; - - /** - * Fragment containing the dialpad that slides into view - */ - protected DialpadFragment mDialpadFragment; - - /** - * Fragment for searching phone numbers using the alphanumeric keyboard. - */ - private RegularSearchFragment mRegularSearchFragment; - - /** - * Fragment for searching phone numbers using the dialpad. - */ - private SmartDialSearchFragment mSmartDialSearchFragment; - - /** - * Animation that slides in. - */ - private Animation mSlideIn; - - /** - * Animation that slides out. - */ - private Animation mSlideOut; - - AnimationListenerAdapter mSlideInListener = new AnimationListenerAdapter() { - @Override - public void onAnimationEnd(Animation animation) { - maybeEnterSearchUi(); - } - }; - - /** - * Listener for after slide out animation completes on dialer fragment. - */ - AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() { - @Override - public void onAnimationEnd(Animation animation) { - commitDialpadFragmentHide(); - } - }; - - /** - * Fragment containing the speed dial list, call history list, and all contacts list. - */ - private ListsFragment mListsFragment; - - /** - * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can - * be commited. - */ - private boolean mStateSaved; - private boolean mIsRestarting; - private boolean mInDialpadSearch; - private boolean mInRegularSearch; - private boolean mClearSearchOnPause; - private boolean mIsDialpadShown; - private boolean mShowDialpadOnResume; - - /** - * Whether or not the device is in landscape orientation. - */ - private boolean mIsLandscape; - - /** - * True if the dialpad is only temporarily showing due to being in call - */ - private boolean mInCallDialpadUp; - - /** - * True when this activity has been launched for the first time. - */ - private boolean mFirstLaunch; - - /** - * Search query to be applied to the SearchView in the ActionBar once - * onCreateOptionsMenu has been called. - */ - private String mPendingSearchViewQuery; - - private PopupMenu mOverflowMenu; - private EditText mSearchView; - private View mVoiceSearchButton; - - private String mSearchQuery; - private String mDialpadQuery; - - private DialerDatabaseHelper mDialerDatabaseHelper; - private DragDropController mDragDropController; - private ActionBarController mActionBarController; - - private FloatingActionButtonController mFloatingActionButtonController; - - private int mActionBarHeight; - private int mPreviouslySelectedTabIndex; - - /** - * The text returned from a voice search query. Set in {@link #onActivityResult} and used in - * {@link #onResume()} to populate the search box. - */ - private String mVoiceSearchQuery; - - protected class OptionsPopupMenu extends PopupMenu { - public OptionsPopupMenu(Context context, View anchor) { - super(context, anchor, Gravity.END); - } - - @Override - public void show() { - final boolean hasContactsPermission = - PermissionsUtil.hasContactsPermissions(DialtactsActivity.this); - final Menu menu = getMenu(); - final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); - clearFrequents.setVisible(mListsFragment != null && - mListsFragment.getSpeedDialFragment() != null && - mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission); - - menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission); - menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission); - - menu.findItem(R.id.menu_history).setVisible( - PermissionsUtil.hasPhonePermissions(DialtactsActivity.this)); - super.show(); - } - } - - /** - * Listener that listens to drag events and sends their x and y coordinates to a - * {@link DragDropController}. - */ - private class LayoutOnDragListener implements OnDragListener { - @Override - public boolean onDrag(View v, DragEvent event) { - if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { - mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY()); - } - return true; - } - } - - /** - * Listener used to send search queries to the phone search fragment. - */ - private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - final String newText = s.toString(); - if (newText.equals(mSearchQuery)) { - // If the query hasn't changed (perhaps due to activity being destroyed - // and restored, or user launching the same DIAL intent twice), then there is - // no need to do anything here. - return; - } - if (DEBUG) { - Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); - Log.d(TAG, "Previous Query: " + mSearchQuery); - } - mSearchQuery = newText; - - // Show search fragment only when the query string is changed to non-empty text. - if (!TextUtils.isEmpty(newText)) { - // Call enterSearchUi only if we are switching search modes, or showing a search - // fragment for the first time. - final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) || - (!mIsDialpadShown && mInRegularSearch); - if (!sameSearchMode) { - enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */); - } - } - - if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { - mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); - } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { - mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); - } - } - - @Override - public void afterTextChanged(Editable s) { - } - }; - - - /** - * Open the search UI when the user clicks on the search box. - */ - private final View.OnClickListener mSearchViewOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!isInSearchUi()) { - mActionBarController.onSearchBoxTapped(); - enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString(), - true /* animate */); - } - } - }; - - /** - * Handles the user closing the soft keyboard. - */ - private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { - if (TextUtils.isEmpty(mSearchView.getText().toString())) { - // If the search term is empty, close the search UI. - maybeExitSearchUi(); - } else { - // If the search term is not empty, show the dialpad fab. - showFabInSearchUi(); - } - } - return false; - } - }; - - @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) { - Trace.beginSection(TAG + " onCreate"); - super.onCreate(savedInstanceState); - - mFirstLaunch = true; - - final Resources resources = getResources(); - mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large); - - Trace.beginSection(TAG + " setContentView"); - setContentView(R.layout.dialtacts_activity); - Trace.endSection(); - getWindow().setBackgroundDrawable(null); - - Trace.beginSection(TAG + " setup Views"); - final ActionBar actionBar = getSupportActionBar(); - actionBar.setCustomView(R.layout.search_edittext); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setBackgroundDrawable(null); - - SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar - .getCustomView().findViewById(R.id.search_view_container); - searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener); - - mActionBarController = new ActionBarController(this, searchEditTextLayout); - - mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view); - mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); - mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button); - searchEditTextLayout.findViewById(R.id.search_magnifying_glass) - .setOnClickListener(mSearchViewOnClickListener); - searchEditTextLayout.findViewById(R.id.search_box_start_search) - .setOnClickListener(mSearchViewOnClickListener); - searchEditTextLayout.setOnClickListener(mSearchViewOnClickListener); - searchEditTextLayout.setCallback(new SearchEditTextLayout.Callback() { - @Override - public void onBackButtonClicked() { - onBackPressed(); - } - - @Override - public void onSearchViewClicked() { - // Hide FAB, as the keyboard is shown. - mFloatingActionButtonController.scaleOut(); - } - }); - - mIsLandscape = getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - mPreviouslySelectedTabIndex = ListsFragment.TAB_INDEX_SPEED_DIAL; - final View floatingActionButtonContainer = findViewById( - R.id.floating_action_button_container); - ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); - floatingActionButton.setOnClickListener(this); - mFloatingActionButtonController = new FloatingActionButtonController(this, - floatingActionButtonContainer, floatingActionButton); - - ImageButton optionsMenuButton = - (ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button); - optionsMenuButton.setOnClickListener(this); - mOverflowMenu = buildOptionsMenu(searchEditTextLayout); - optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener()); - - // Add the favorites fragment but only if savedInstanceState is null. Otherwise the - // fragment manager is responsible for recreating it. - if (savedInstanceState == null) { - getFragmentManager().beginTransaction() - .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) - .commit(); - } else { - mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); - mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); - mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); - mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); - mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN); - mActionBarController.restoreInstanceState(savedInstanceState); - } - - final boolean isLayoutRtl = DialerUtils.isRtl(); - if (mIsLandscape) { - mSlideIn = AnimationUtils.loadAnimation(this, - isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right); - mSlideOut = AnimationUtils.loadAnimation(this, - isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right); - } else { - mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom); - mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom); - } - - mSlideIn.setInterpolator(AnimUtils.EASE_IN); - mSlideOut.setInterpolator(AnimUtils.EASE_OUT); - - mSlideIn.setAnimationListener(mSlideInListener); - mSlideOut.setAnimationListener(mSlideOutListener); - - mParentLayout = (CoordinatorLayout) findViewById(R.id.dialtacts_mainlayout); - mParentLayout.setOnDragListener(new LayoutOnDragListener()); - floatingActionButtonContainer.getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - final ViewTreeObserver observer = - floatingActionButtonContainer.getViewTreeObserver(); - if (!observer.isAlive()) { - return; - } - observer.removeOnGlobalLayoutListener(this); - int screenWidth = mParentLayout.getWidth(); - mFloatingActionButtonController.setScreenWidth(screenWidth); - mFloatingActionButtonController.align( - getFabAlignment(), false /* animate */); - } - }); - - Trace.endSection(); - - Trace.beginSection(TAG + " initialize smart dialing"); - mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); - SmartDialPrefix.initializeNanpSettings(this); - Trace.endSection(); - Trace.endSection(); - } - - @Override - protected void onResume() { - Trace.beginSection(TAG + " onResume"); - super.onResume(); - - mStateSaved = false; - if (mFirstLaunch) { - displayFragment(getIntent()); - } else if (!phoneIsInUse() && mInCallDialpadUp) { - hideDialpadFragment(false, true); - mInCallDialpadUp = false; - } else if (mShowDialpadOnResume) { - showDialpadFragment(false); - mShowDialpadOnResume = false; - } - - // If there was a voice query result returned in the {@link #onActivityResult} callback, it - // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be - // shown until onResume has completed. Active the search UI and set the search term now. - if (!TextUtils.isEmpty(mVoiceSearchQuery)) { - mActionBarController.onSearchBoxTapped(); - mSearchView.setText(mVoiceSearchQuery); - mVoiceSearchQuery = null; - } - - mFirstLaunch = false; - - if (mIsRestarting) { - // This is only called when the activity goes from resumed -> paused -> resumed, so it - // will not cause an extra view to be sent out on rotation - if (mIsDialpadShown) { - Logger.logScreenView(ScreenEvent.DIALPAD, this); - } - mIsRestarting = false; - } - - prepareVoiceSearchButton(); - mDialerDatabaseHelper.startSmartDialUpdateThread(); - mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); - - if (Calls.CONTENT_TYPE.equals(getIntent().getType())) { - // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only - // used internally. - final Bundle extras = getIntent().getExtras(); - if (extras != null - && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) { - mListsFragment.showTab(ListsFragment.TAB_INDEX_VOICEMAIL); - } else { - mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY); - } - } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) { - int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL); - if (index < mListsFragment.getTabCount()) { - mListsFragment.showTab(index); - } - } - - setSearchBoxHint(); - - Trace.endSection(); - } - - @Override - protected void onRestart() { - super.onRestart(); - mIsRestarting = true; - } - - @Override - protected void onPause() { - // Only clear missed calls if the pause was not triggered by an orientation change - // (or any other confirguration change) - if (!isChangingConfigurations()) { - updateMissedCalls(); - } - if (mClearSearchOnPause) { - hideDialpadAndSearchUi(); - mClearSearchOnPause = false; - } - if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) { - commitDialpadFragmentHide(); - } - super.onPause(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(KEY_SEARCH_QUERY, mSearchQuery); - outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); - outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); - outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); - outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown); - mActionBarController.saveInstanceState(outState); - mStateSaved = true; - } - - @Override - public void onAttachFragment(Fragment fragment) { - if (fragment instanceof DialpadFragment) { - mDialpadFragment = (DialpadFragment) fragment; - if (!mIsDialpadShown && !mShowDialpadOnResume) { - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.hide(mDialpadFragment); - transaction.commit(); - } - } else if (fragment instanceof SmartDialSearchFragment) { - mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; - mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); - if (!TextUtils.isEmpty(mDialpadQuery)) { - mSmartDialSearchFragment.setAddToContactNumber(mDialpadQuery); - } - } else if (fragment instanceof SearchFragment) { - mRegularSearchFragment = (RegularSearchFragment) fragment; - mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); - } else if (fragment instanceof ListsFragment) { - mListsFragment = (ListsFragment) fragment; - mListsFragment.addOnPageChangeListener(this); - } - } - - protected void handleMenuSettings() { - final Intent intent = new Intent(this, DialerSettingsActivity.class); - startActivity(intent); - } - - @Override - public void onClick(View view) { - int resId = view.getId(); - if (resId == R.id.floating_action_button) { - if (mListsFragment.getCurrentTabIndex() - == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch && - !mInDialpadSearch) { - DialerUtils.startActivityWithErrorToast( - this, - IntentUtil.getNewContactIntent(), - R.string.add_contact_not_available); - } else if (!mIsDialpadShown) { - mInCallDialpadUp = false; - showDialpadFragment(true); - } - } else if (resId == R.id.voice_search_button) { - try { - startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), - ACTIVITY_REQUEST_CODE_VOICE_SEARCH); - } catch (ActivityNotFoundException e) { - Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, - Toast.LENGTH_SHORT).show(); - } - } else if (resId == R.id.dialtacts_options_menu_button) { - mOverflowMenu.show(); - } else { - Log.wtf(TAG, "Unexpected onClick event from " + view); - } - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - if (!isSafeToCommitTransactions()) { - return true; - } - - int resId = item.getItemId(); - if (resId == R.id.menu_history) {// Use explicit CallLogActivity intent instead of ACTION_VIEW + - // CONTENT_TYPE, so that we always open our call log from our dialer - final Intent intent = new Intent(this, CallLogActivity.class); - startActivity(intent); - } else if (resId == R.id.menu_add_contact) { - DialerUtils.startActivityWithErrorToast( - this, - IntentUtil.getNewContactIntent(), - R.string.add_contact_not_available); - } else if (resId == R.id.menu_import_export) {// We hard-code the "contactsAreAvailable" argument because doing it properly would - // involve querying a {@link ProviderStatusLoader}, which we don't want to do right - // now in Dialtacts for (potential) performance reasons. Compare with how it is - // done in {@link PeopleActivity}. - if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { - ImportExportDialogFragment.show(getFragmentManager(), true, - DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES); - } else { - ImportExportDialogFragment.show(getFragmentManager(), true, - DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_DEFAULT); - } - Logger.logScreenView(ScreenEvent.IMPORT_EXPORT_CONTACTS, this); - return true; - } else if (resId == R.id.menu_clear_frequents) { - ClearFrequentsDialog.show(getFragmentManager()); - Logger.logScreenView(ScreenEvent.CLEAR_FREQUENTS, this); - return true; - } else if (resId == R.id.menu_call_settings) { - handleMenuSettings(); - Logger.logScreenView(ScreenEvent.SETTINGS, this); - return true; - } else if (resId == R.id.menu_archive) { - final Intent intent = new Intent(this, VoicemailArchiveActivity.class); - startActivity(intent); - return true; - } - return false; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { - if (resultCode == RESULT_OK) { - final ArrayList<String> matches = data.getStringArrayListExtra( - RecognizerIntent.EXTRA_RESULTS); - if (matches.size() > 0) { - final String match = matches.get(0); - mVoiceSearchQuery = match; - } else { - Log.e(TAG, "Voice search - nothing heard"); - } - } else { - Log.e(TAG, "Voice search failed"); - } - } - super.onActivityResult(requestCode, resultCode, data); - } - - /** - * Update the number of unread voicemails (potentially other tabs) displayed next to the tab - * icon. - */ - public void updateTabUnreadCounts() { - mListsFragment.updateTabUnreadCounts(); - } - - /** - * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual - * updates are handled by a callback which is invoked after the dialpad fragment is shown. - * @see #onDialpadShown - */ - private void showDialpadFragment(boolean animate) { - if (mIsDialpadShown || mStateSaved) { - return; - } - mIsDialpadShown = true; - - mListsFragment.setUserVisibleHint(false); - - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - if (mDialpadFragment == null) { - mDialpadFragment = new DialpadFragment(); - ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT); - } else { - ft.show(mDialpadFragment); - } - - mDialpadFragment.setAnimate(animate); - Logger.logScreenView(ScreenEvent.DIALPAD, this); - ft.commit(); - - if (animate) { - mFloatingActionButtonController.scaleOut(); - } else { - mFloatingActionButtonController.setVisible(false); - maybeEnterSearchUi(); - } - mActionBarController.onDialpadUp(); - - mListsFragment.getView().animate().alpha(0).withLayer(); - - //adjust the title, so the user will know where we're at when the activity start/resumes. - setTitle(R.string.launcherDialpadActivityLabel); - } - - /** - * Callback from child DialpadFragment when the dialpad is shown. - */ - public void onDialpadShown() { - Assert.assertNotNull(mDialpadFragment); - if (mDialpadFragment.getAnimate()) { - mDialpadFragment.getView().startAnimation(mSlideIn); - } else { - mDialpadFragment.setYFraction(0); - } - - updateSearchFragmentPosition(); - } - - /** - * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in - * a callback after the hide animation ends. - * @see #commitDialpadFragmentHide - */ - public void hideDialpadFragment(boolean animate, boolean clearDialpad) { - if (mDialpadFragment == null || mDialpadFragment.getView() == null) { - return; - } - if (clearDialpad) { - // Temporarily disable accessibility when we clear the dialpad, since it should be - // invisible and should not announce anything. - mDialpadFragment.getDigitsWidget().setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_NO); - mDialpadFragment.clearDialpad(); - mDialpadFragment.getDigitsWidget().setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - } - if (!mIsDialpadShown) { - return; - } - mIsDialpadShown = false; - mDialpadFragment.setAnimate(animate); - mListsFragment.setUserVisibleHint(true); - mListsFragment.sendScreenViewForCurrentPosition(); - - updateSearchFragmentPosition(); - - mFloatingActionButtonController.align(getFabAlignment(), animate); - if (animate) { - mDialpadFragment.getView().startAnimation(mSlideOut); - } else { - commitDialpadFragmentHide(); - } - - mActionBarController.onDialpadDown(); - - if (isInSearchUi()) { - if (TextUtils.isEmpty(mSearchQuery)) { - exitSearchUi(); - } - } - //reset the title to normal. - setTitle(R.string.launcherActivityLabel); - } - - /** - * Finishes hiding the dialpad fragment after any animations are completed. - */ - private void commitDialpadFragmentHide() { - if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) { - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.hide(mDialpadFragment); - ft.commit(); - } - mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); - } - - private void updateSearchFragmentPosition() { - SearchFragment fragment = null; - if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { - fragment = mSmartDialSearchFragment; - } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { - fragment = mRegularSearchFragment; - } - if (fragment != null && fragment.isVisible()) { - fragment.updatePosition(true /* animate */); - } - } - - @Override - public boolean isInSearchUi() { - return mInDialpadSearch || mInRegularSearch; - } - - @Override - public boolean hasSearchQuery() { - return !TextUtils.isEmpty(mSearchQuery); - } - - @Override - public boolean shouldShowActionBar() { - return mListsFragment.shouldShowActionBar(); - } - - private void setNotInSearchUi() { - mInDialpadSearch = false; - mInRegularSearch = false; - } - - private void hideDialpadAndSearchUi() { - if (mIsDialpadShown) { - hideDialpadFragment(false, true); - } else { - exitSearchUi(); - } - } - - private void prepareVoiceSearchButton() { - final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - if (canIntentBeHandled(voiceIntent)) { - mVoiceSearchButton.setVisibility(View.VISIBLE); - mVoiceSearchButton.setOnClickListener(this); - } else { - mVoiceSearchButton.setVisibility(View.GONE); - } - } - - public boolean isNearbyPlacesSearchEnabled() { - return false; - } - - protected int getSearchBoxHint () { - return R.string.dialer_hint_find_contact; - } - - /** - * Sets the hint text for the contacts search box - */ - private void setSearchBoxHint() { - SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar() - .getCustomView().findViewById(R.id.search_view_container); - ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search)) - .setHint(getSearchBoxHint()); - } - - protected OptionsPopupMenu buildOptionsMenu(View invoker) { - final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker); - popupMenu.inflate(R.menu.dialtacts_options); - if (ObjectFactory.isVoicemailArchiveEnabled(this)) { - popupMenu.getMenu().findItem(R.id.menu_archive).setVisible(true); - } - popupMenu.setOnMenuItemClickListener(this); - return popupMenu; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (mPendingSearchViewQuery != null) { - mSearchView.setText(mPendingSearchViewQuery); - mPendingSearchViewQuery = null; - } - if (mActionBarController != null) { - mActionBarController.restoreActionBarOffset(); - } - return false; - } - - /** - * Returns true if the intent is due to hitting the green send key (hardware call button: - * KEYCODE_CALL) while in a call. - * - * @param intent the intent that launched this activity - * @return true if the intent is due to hitting the green send key while in a call - */ - private boolean isSendKeyWhileInCall(Intent intent) { - // If there is a call in progress and the user launched the dialer by hitting the call - // button, go straight to the in-call screen. - final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); - - if (callKey) { - TelecomUtil.showInCallScreen(this, false); - return true; - } - - return false; - } - - /** - * Sets the current tab based on the intent's request type - * - * @param intent Intent that contains information about which tab should be selected - */ - private void displayFragment(Intent intent) { - // If we got here by hitting send and we're in call forward along to the in-call activity - if (isSendKeyWhileInCall(intent)) { - finish(); - return; - } - - final boolean showDialpadChooser = phoneIsInUse() && !DialpadFragment.isAddCallMode(intent); - if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) { - showDialpadFragment(false); - mDialpadFragment.setStartedFromNewIntent(true); - if (showDialpadChooser && !mDialpadFragment.isVisible()) { - mInCallDialpadUp = true; - } - } - } - - @Override - public void onNewIntent(Intent newIntent) { - setIntent(newIntent); - - mStateSaved = false; - displayFragment(newIntent); - - invalidateOptionsMenu(); - } - - /** Returns true if the given intent contains a phone number to populate the dialer with */ - private boolean isDialIntent(Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { - return true; - } - if (Intent.ACTION_VIEW.equals(action)) { - final Uri data = intent.getData(); - if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { - return true; - } - } - return false; - } - - /** - * Shows the search fragment - */ - private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) { - if (mStateSaved || getFragmentManager().isDestroyed()) { - // Weird race condition where fragment is doing work after the activity is destroyed - // due to talkback being on (b/10209937). Just return since we can't do any - // constructive here. - return; - } - - if (DEBUG) { - Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); - } - - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - if (mInDialpadSearch && mSmartDialSearchFragment != null) { - transaction.remove(mSmartDialSearchFragment); - } else if (mInRegularSearch && mRegularSearchFragment != null) { - transaction.remove(mRegularSearchFragment); - } - - final String tag; - if (smartDialSearch) { - tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; - } else { - tag = TAG_REGULAR_SEARCH_FRAGMENT; - } - mInDialpadSearch = smartDialSearch; - mInRegularSearch = !smartDialSearch; - - mFloatingActionButtonController.scaleOut(); - - SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); - if (animate) { - transaction.setCustomAnimations(android.R.animator.fade_in, 0); - } else { - transaction.setTransition(FragmentTransaction.TRANSIT_NONE); - } - if (fragment == null) { - if (smartDialSearch) { - fragment = new SmartDialSearchFragment(); - } else { - fragment = ObjectFactory.newRegularSearchFragment(); - fragment.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - // Show the FAB when the user touches the lists fragment and the soft - // keyboard is hidden. - hideDialpadFragment(true, false); - showFabInSearchUi(); - return false; - } - }); - } - transaction.add(R.id.dialtacts_frame, fragment, tag); - } else { - transaction.show(fragment); - } - // DialtactsActivity will provide the options menu - fragment.setHasOptionsMenu(false); - fragment.setShowEmptyListForNullQuery(true); - if (!smartDialSearch) { - fragment.setQueryString(query, false /* delaySelection */); - } - transaction.commit(); - - if (animate) { - mListsFragment.getView().animate().alpha(0).withLayer(); - } - mListsFragment.setUserVisibleHint(false); - - if (smartDialSearch) { - Logger.logScreenView(ScreenEvent.SMART_DIAL_SEARCH, this); - } else { - Logger.logScreenView(ScreenEvent.REGULAR_SEARCH, this); - } - } - - /** - * Hides the search fragment - */ - private void exitSearchUi() { - // See related bug in enterSearchUI(); - if (getFragmentManager().isDestroyed() || mStateSaved) { - return; - } - - mSearchView.setText(null); - - if (mDialpadFragment != null) { - mDialpadFragment.clearDialpad(); - } - - setNotInSearchUi(); - - // Restore the FAB for the lists fragment. - if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) { - mFloatingActionButtonController.setVisible(false); - } - mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); - onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); - onPageSelected(mListsFragment.getCurrentTabIndex()); - - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - if (mSmartDialSearchFragment != null) { - transaction.remove(mSmartDialSearchFragment); - } - if (mRegularSearchFragment != null) { - transaction.remove(mRegularSearchFragment); - } - transaction.commit(); - - mListsFragment.getView().animate().alpha(1).withLayer(); - - if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { - // If the dialpad fragment wasn't previously visible, then send a screen view because - // we are exiting regular search. Otherwise, the screen view will be sent by - // {@link #hideDialpadFragment}. - mListsFragment.sendScreenViewForCurrentPosition(); - mListsFragment.setUserVisibleHint(true); - } - - mActionBarController.onSearchUiExited(); - } - - @Override - public void onBackPressed() { - if (mStateSaved) { - return; - } - if (mIsDialpadShown) { - if (TextUtils.isEmpty(mSearchQuery) || - (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible() - && mSmartDialSearchFragment.getAdapter().getCount() == 0)) { - exitSearchUi(); - } - hideDialpadFragment(true, false); - } else if (isInSearchUi()) { - exitSearchUi(); - DialerUtils.hideInputMethod(mParentLayout); - } else { - super.onBackPressed(); - } - } - - private void maybeEnterSearchUi() { - if (!isInSearchUi()) { - enterSearchUi(true /* isSmartDial */, mSearchQuery, false); - } - } - - /** - * @return True if the search UI was exited, false otherwise - */ - private boolean maybeExitSearchUi() { - if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) { - exitSearchUi(); - DialerUtils.hideInputMethod(mParentLayout); - return true; - } - return false; - } - - private void showFabInSearchUi() { - mFloatingActionButtonController.changeIcon( - getResources().getDrawable(R.drawable.fab_ic_dial), - getResources().getString(R.string.action_menu_dialpad_button)); - mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); - mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); - } - - @Override - public void onDialpadQueryChanged(String query) { - mDialpadQuery = query; - if (mSmartDialSearchFragment != null) { - mSmartDialSearchFragment.setAddToContactNumber(query); - } - final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, - SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); - - if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { - if (DEBUG) { - Log.d(TAG, "onDialpadQueryChanged - new query: " + query); - } - if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { - // This callback can happen if the dialpad fragment is recreated because of - // activity destruction. In that case, don't update the search view because - // that would bring the user back to the search fragment regardless of the - // previous state of the application. Instead, just return here and let the - // fragment manager correctly figure out whatever fragment was last displayed. - if (!TextUtils.isEmpty(normalizedQuery)) { - mPendingSearchViewQuery = normalizedQuery; - } - return; - } - mSearchView.setText(normalizedQuery); - } - - try { - if (mDialpadFragment != null && mDialpadFragment.isVisible()) { - mDialpadFragment.process_quote_emergency_unquote(normalizedQuery); - } - } catch (Exception ignored) { - // Skip any exceptions for this piece of code - } - } - - @Override - public boolean onDialpadSpacerTouchWithEmptyQuery() { - if (mInDialpadSearch && mSmartDialSearchFragment != null - && !mSmartDialSearchFragment.isShowingPermissionRequest()) { - hideDialpadFragment(true /* animate */, true /* clearDialpad */); - return true; - } - return false; - } - - @Override - public void onListFragmentScrollStateChange(int scrollState) { - if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { - hideDialpadFragment(true, false); - DialerUtils.hideInputMethod(mParentLayout); - } - } - - @Override - public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - // TODO: No-op for now. This should eventually show/hide the actionBar based on - // interactions with the ListsFragments. - } - - private boolean phoneIsInUse() { - return TelecomUtil.isInCall(this); - } - - private boolean canIntentBeHandled(Intent intent) { - final PackageManager packageManager = getPackageManager(); - final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY); - return resolveInfo != null && resolveInfo.size() > 0; - } - - /** - * Called when the user has long-pressed a contact tile to start a drag operation. - */ - @Override - public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { - mListsFragment.showRemoveView(true); - } - - @Override - public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { - } - - /** - * Called when the user has released a contact tile after long-pressing it. - */ - @Override - public void onDragFinished(int x, int y) { - mListsFragment.showRemoveView(false); - } - - @Override - public void onDroppedOnRemove() {} - - /** - * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer - * once it has been attached to the activity. - */ - @Override - public void setDragDropController(DragDropController dragController) { - mDragDropController = dragController; - mListsFragment.getRemoveView().setDragDropController(dragController); - } - - /** - * Implemented to satisfy {@link SpeedDialFragment.HostInterface} - */ - @Override - public void showAllContactsTab() { - if (mListsFragment != null) { - mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS); - } - } - - /** - * Implemented to satisfy {@link CallLogFragment.HostInterface} - */ - @Override - public void showDialpad() { - showDialpadFragment(true); - } - - @Override - public void onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType) { - mClearSearchOnPause = true; - PhoneNumberInteraction.startInteractionForPhoneCall( - DialtactsActivity.this, dataUri, isVideoCall, callInitiationType); - } - - @Override - public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall, int callInitiationType) { - if (phoneNumber == null) { - // Invalid phone number, but let the call go through so that InCallUI can show - // an error message. - phoneNumber = ""; - } - - final Intent intent = new CallIntentBuilder(phoneNumber) - .setIsVideoCall(isVideoCall) - .setCallInitiationType(callInitiationType) - .build(); - - DialerUtils.startActivityWithErrorToast(this, intent); - mClearSearchOnPause = true; - } - - @Override - public void onShortcutIntentCreated(Intent intent) { - Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); - } - - @Override - public void onHomeInActionBarSelected() { - exitSearchUi(); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - int tabIndex = mListsFragment.getCurrentTabIndex(); - - // Scroll the button from center to end when moving from the Speed Dial to Call History tab. - // In RTL, scroll when the current tab is Call History instead, since the order of the tabs - // is reversed and the ViewPager returns the left tab position during scroll. - boolean isRtl = DialerUtils.isRtl(); - if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) { - mFloatingActionButtonController.onPageScrolled(positionOffset); - } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) { - mFloatingActionButtonController.onPageScrolled(1 - positionOffset); - } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) { - mFloatingActionButtonController.onPageScrolled(1); - } - } - - @Override - public void onPageSelected(int position) { - updateMissedCalls(); - int tabIndex = mListsFragment.getCurrentTabIndex(); - mPreviouslySelectedTabIndex = tabIndex; - if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS && - !mInRegularSearch && !mInDialpadSearch) { - mFloatingActionButtonController.changeIcon( - getResources().getDrawable(R.drawable.ic_person_add_24dp), - getResources().getString(R.string.search_shortcut_create_new_contact)); - } else { - mFloatingActionButtonController.changeIcon( - getResources().getDrawable(R.drawable.fab_ic_dial), - getResources().getString(R.string.action_menu_dialpad_button)); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - } - - @Override - public boolean isActionBarShowing() { - return mActionBarController.isActionBarShowing(); - } - - @Override - public ActionBarController getActionBarController() { - return mActionBarController; - } - - @Override - public boolean isDialpadShown() { - return mIsDialpadShown; - } - - @Override - public int getDialpadHeight() { - if (mDialpadFragment != null) { - return mDialpadFragment.getDialpadHeight(); - } - return 0; - } - - @Override - public int getActionBarHideOffset() { - return getSupportActionBar().getHideOffset(); - } - - @Override - public void setActionBarHideOffset(int offset) { - getSupportActionBar().setHideOffset(offset); - } - - @Override - public int getActionBarHeight() { - return mActionBarHeight; - } - - private int getFabAlignment() { - if (!mIsLandscape && !isInSearchUi() && - mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { - return FloatingActionButtonController.ALIGN_MIDDLE; - } - return FloatingActionButtonController.ALIGN_END; - } - - private void updateMissedCalls() { - if (mPreviouslySelectedTabIndex == ListsFragment.TAB_INDEX_HISTORY) { - mListsFragment.markMissedCallsAsReadAndRemoveNotifications(); - } - } -} diff --git a/src/com/android/dialer/FloatingActionButtonBehavior.java b/src/com/android/dialer/FloatingActionButtonBehavior.java deleted file mode 100644 index 679c9a7c1..000000000 --- a/src/com/android/dialer/FloatingActionButtonBehavior.java +++ /dev/null @@ -1,47 +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; - -import android.content.Context; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar.SnackbarLayout; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -/** - * Implements custom behavior for the movement of the FAB in response to the Snackbar. - * Because we are not using the design framework FloatingActionButton widget, we need to manually - * implement the Material Design behavior of having the FAB translate upward and downward with - * the appearance and disappearance of a Snackbar. - */ -public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FrameLayout> { - public FloatingActionButtonBehavior(Context context, AttributeSet attrs) { - } - - @Override - public boolean layoutDependsOn(CoordinatorLayout parent, FrameLayout child, View dependency) { - return dependency instanceof SnackbarLayout; - } - - @Override - public boolean onDependentViewChanged(CoordinatorLayout parent, FrameLayout child, - View dependency) { - float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight()); - child.setTranslationY(translationY); - return true; - } -} diff --git a/src/com/android/dialer/NeededForReflection.java b/src/com/android/dialer/NeededForReflection.java deleted file mode 100644 index e836908b1..000000000 --- a/src/com/android/dialer/NeededForReflection.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Denotes that the class, constructor, method or field is used for reflection and therefore cannot - * be removed by tools like ProGuard. - */ -@Retention(RetentionPolicy.CLASS) -@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD}) -public @interface NeededForReflection{} diff --git a/src/com/android/dialer/PhoneCallDetails.java b/src/com/android/dialer/PhoneCallDetails.java deleted file mode 100644 index 8a2e52090..000000000 --- a/src/com/android/dialer/PhoneCallDetails.java +++ /dev/null @@ -1,187 +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; - -import com.android.contacts.common.ContactsUtils.UserType; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.calllog.PhoneNumberDisplayUtil; - -import android.content.Context; -import android.content.res.Resources; -import android.net.Uri; -import android.provider.CallLog.Calls; -import android.support.annotation.Nullable; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; - -/** - * The details of a phone call to be shown in the UI. - */ -public class PhoneCallDetails { - // The number of the other party involved in the call. - public CharSequence number; - // Post-dial digits associated with the outgoing call. - public String postDialDigits; - // The secondary line number the call was received via. - public String viaNumber; - // The number presenting rules set by the network, e.g., {@link Calls#PRESENTATION_ALLOWED} - public int numberPresentation; - // The formatted version of {@link #number}. - public CharSequence formattedNumber; - // The country corresponding with the phone number. - public String countryIso; - // The geocoded location for the phone number. - public String geocode; - - /** - * The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}. - * <p> - * There might be multiple types if this represents a set of entries grouped together. - */ - public int[] callTypes; - - // The date of the call, in milliseconds since the epoch. - public long date; - // The duration of the call in milliseconds, or 0 for missed calls. - public long duration; - // The name of the contact, or the empty string. - public CharSequence namePrimary; - // The alternative name of the contact, e.g. last name first, or the empty string - public CharSequence nameAlternative; - /** - * The user's preference on name display order, last name first or first time first. - * {@see ContactsPreferences} - */ - public int nameDisplayOrder; - // The type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available. - public int numberType; - // The custom label associated with the phone number in the contact, or the empty string. - public CharSequence numberLabel; - // The URI of the contact associated with this phone call. - public Uri contactUri; - - /** - * The photo URI of the picture of the contact that is associated with this phone call or - * null if there is none. - * <p> - * This is meant to store the high-res photo only. - */ - public Uri photoUri; - - // The source type of the contact associated with this call. - public int sourceType; - - // The object id type of the contact associated with this call. - public String objectId; - - // The unique identifier for the account associated with the call. - public PhoneAccountHandle accountHandle; - - // Features applicable to this call. - public int features; - - // Total data usage for this call. - public Long dataUsage; - - // Voicemail transcription - public String transcription; - - // The display string for the number. - public String displayNumber; - - // Whether the contact number is a voicemail number. - public boolean isVoicemail; - - /** The {@link UserType} of the contact */ - public @UserType long contactUserType; - - /** - * If this is a voicemail, whether the message is read. For other types of calls, this defaults - * to {@code true}. - */ - public boolean isRead = true; - - // If this call is a spam number. - public boolean isSpam = false; - - /** - * Constructor with required fields for the details of a call with a number associated with a - * contact. - */ - public PhoneCallDetails( - Context context, - CharSequence number, - int numberPresentation, - CharSequence formattedNumber, - CharSequence postDialDigits, - boolean isVoicemail) { - this.number = number; - this.numberPresentation = numberPresentation; - this.formattedNumber = formattedNumber; - this.isVoicemail = isVoicemail; - this.postDialDigits = postDialDigits.toString(); - this.displayNumber = PhoneNumberDisplayUtil.getDisplayNumber( - context, - this.number, - this.numberPresentation, - this.formattedNumber, - this.postDialDigits, - this.isVoicemail).toString(); - } - - /** - * Returns the preferred name for the call details as specified by the - * {@link #nameDisplayOrder} - * - * @return the preferred name - */ - public CharSequence getPreferredName() { - if (nameDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY - || TextUtils.isEmpty(nameAlternative)) { - return namePrimary; - } - return nameAlternative; - } - - /** - * Construct the "on {accountLabel} via {viaNumber}" accessibility description for the account - * list item, depending on the existence of the accountLabel and viaNumber. - * @param viaNumber The number that this call is being placed via. - * @param accountLabel The {@link PhoneAccount} label that this call is being placed with. - * @return The description of the account that this call has been placed on. - */ - public static CharSequence createAccountLabelDescription(Resources resources, - @Nullable String viaNumber, @Nullable CharSequence accountLabel) { - - if((!TextUtils.isEmpty(viaNumber)) && !TextUtils.isEmpty(accountLabel)) { - String msg = resources.getString(R.string.description_via_number_phone_account, - accountLabel, viaNumber); - CharSequence accountNumberLabel = ContactDisplayUtils.getTelephoneTtsSpannable(msg, - viaNumber); - return (accountNumberLabel == null) ? msg : accountNumberLabel; - } else if (!TextUtils.isEmpty(viaNumber)) { - CharSequence viaNumberLabel = ContactDisplayUtils.getTtsSpannedPhoneNumber(resources, - R.string.description_via_number, viaNumber); - return (viaNumberLabel == null) ? viaNumber : viaNumberLabel; - } else if (!TextUtils.isEmpty(accountLabel)) { - return TextUtils.expandTemplate( - resources.getString(R.string.description_phone_account), accountLabel); - } - return ""; - } -} diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java deleted file mode 100644 index fe2163f17..000000000 --- a/src/com/android/dialer/SpecialCharSequenceMgr.java +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (C) 2006 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; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DialogFragment; -import android.app.KeyguardManager; -import android.app.ProgressDialog; -import android.content.ActivityNotFoundException; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Looper; -import android.provider.Settings; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.Toast; - -import com.android.common.io.MoreCloseables; -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.TelephonyManagerCompat; -import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; -import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; -import com.android.dialer.calllog.PhoneAccountUtils; -import com.android.dialer.util.TelecomUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * Helper class to listen for some magic character sequences - * that are handled specially by the dialer. - * - * Note the Phone app also handles these sequences too (in a couple of - * relatively obscure places in the UI), so there's a separate version of - * this class under apps/Phone. - * - * TODO: there's lots of duplicated code between this class and the - * corresponding class under apps/Phone. Let's figure out a way to - * unify these two classes (in the framework? in a common shared library?) - */ -public class SpecialCharSequenceMgr { - private static final String TAG = "SpecialCharSequenceMgr"; - - private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment"; - - private static final String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE"; - private static final String MMI_IMEI_DISPLAY = "*#06#"; - private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#"; - - /** - * Remembers the previous {@link QueryHandler} and cancel the operation when needed, to - * prevent possible crash. - * - * QueryHandler may call {@link ProgressDialog#dismiss()} when the screen is already gone, - * which will cause the app crash. This variable enables the class to prevent the crash - * on {@link #cleanup()}. - * - * TODO: Remove this and replace it (and {@link #cleanup()}) with better implementation. - * One complication is that we have SpecialCharSequenceMgr in Phone package too, which has - * *slightly* different implementation. Note that Phone package doesn't have this problem, - * so the class on Phone side doesn't have this functionality. - * Fundamental fix would be to have one shared implementation and resolve this corner case more - * gracefully. - */ - private static QueryHandler sPreviousAdnQueryHandler; - - public static class HandleAdnEntryAccountSelectedCallback extends SelectPhoneAccountListener{ - final private Context mContext; - final private QueryHandler mQueryHandler; - final private SimContactQueryCookie mCookie; - - public HandleAdnEntryAccountSelectedCallback(Context context, - QueryHandler queryHandler, SimContactQueryCookie cookie) { - mContext = context; - mQueryHandler = queryHandler; - mCookie = cookie; - } - - @Override - public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle, - boolean setDefault) { - Uri uri = TelecomUtil.getAdnUriForPhoneAccount(mContext, selectedAccountHandle); - handleAdnQuery(mQueryHandler, mCookie, uri); - // TODO: Show error dialog if result isn't valid. - } - - } - - public static class HandleMmiAccountSelectedCallback extends SelectPhoneAccountListener{ - final private Context mContext; - final private String mInput; - public HandleMmiAccountSelectedCallback(Context context, String input) { - mContext = context.getApplicationContext(); - mInput = input; - } - - @Override - public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle, - boolean setDefault) { - TelecomUtil.handleMmi(mContext, mInput, selectedAccountHandle); - } - } - - /** This class is never instantiated. */ - private SpecialCharSequenceMgr() { - } - - public static boolean handleChars(Context context, String input, EditText textField) { - //get rid of the separators so that the string gets parsed correctly - String dialString = PhoneNumberUtils.stripSeparators(input); - - if (handleDeviceIdDisplay(context, dialString) - || handleRegulatoryInfoDisplay(context, dialString) - || handlePinEntry(context, dialString) - || handleAdnEntry(context, dialString, textField) - || handleSecretCode(context, dialString)) { - return true; - } - - return false; - } - - /** - * Cleanup everything around this class. Must be run inside the main thread. - * - * This should be called when the screen becomes background. - */ - public static void cleanup() { - if (Looper.myLooper() != Looper.getMainLooper()) { - Log.wtf(TAG, "cleanup() is called outside the main thread"); - return; - } - - if (sPreviousAdnQueryHandler != null) { - sPreviousAdnQueryHandler.cancel(); - sPreviousAdnQueryHandler = null; - } - } - - /** - * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*. - * If a secret code is encountered, an Intent is started with the android_secret_code://<code> - * URI. - * - * @param context the context to use - * @param input the text to check for a secret code in - * @return true if a secret code was encountered and intent is sent out - */ - static boolean handleSecretCode(Context context, String input) { - final TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager != null) { - return telephonyManager.sendDialerCode(input); - } - return false; - } - - /** - * Handle ADN requests by filling in the SIM contact number into the requested - * EditText. - * - * This code works alongside the Asynchronous query handler {@link QueryHandler} - * and query cancel handler implemented in {@link SimContactQueryCookie}. - */ - static boolean handleAdnEntry(Context context, String input, EditText textField) { - /* ADN entries are of the form "N(N)(N)#" */ - TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager == null - || telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) { - return false; - } - - // if the phone is keyguard-restricted, then just ignore this - // input. We want to make sure that sim card contacts are NOT - // exposed unless the phone is unlocked, and this code can be - // accessed from the emergency dialer. - KeyguardManager keyguardManager = - (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - if (keyguardManager.inKeyguardRestrictedInputMode()) { - return false; - } - - int len = input.length(); - if ((len > 1) && (len < 5) && (input.endsWith("#"))) { - try { - // get the ordinal number of the sim contact - final int index = Integer.parseInt(input.substring(0, len-1)); - - // The original code that navigated to a SIM Contacts list view did not - // highlight the requested contact correctly, a requirement for PTCRB - // certification. This behaviour is consistent with the UI paradigm - // for touch-enabled lists, so it does not make sense to try to work - // around it. Instead we fill in the the requested phone number into - // the dialer text field. - - // create the async query handler - final QueryHandler handler = new QueryHandler(context.getContentResolver()); - - // create the cookie object - final SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler, - ADN_QUERY_TOKEN); - - // setup the cookie fields - sc.contactNum = index - 1; - sc.setTextField(textField); - - // create the progress dialog - sc.progressDialog = new ProgressDialog(context); - sc.progressDialog.setTitle(R.string.simContacts_title); - sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading)); - sc.progressDialog.setIndeterminate(true); - sc.progressDialog.setCancelable(true); - sc.progressDialog.setOnCancelListener(sc); - sc.progressDialog.getWindow().addFlags( - WindowManager.LayoutParams.FLAG_BLUR_BEHIND); - - List<PhoneAccountHandle> subscriptionAccountHandles = - PhoneAccountUtils.getSubscriptionPhoneAccounts(context); - Context applicationContext = context.getApplicationContext(); - boolean hasUserSelectedDefault = subscriptionAccountHandles.contains( - TelecomUtil.getDefaultOutgoingPhoneAccount(applicationContext, - PhoneAccount.SCHEME_TEL)); - - if (subscriptionAccountHandles.size() <= 1 || hasUserSelectedDefault) { - Uri uri = TelecomUtil.getAdnUriForPhoneAccount(applicationContext, null); - handleAdnQuery(handler, sc, uri); - } else { - SelectPhoneAccountListener callback = new HandleAdnEntryAccountSelectedCallback( - applicationContext, handler, sc); - - DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance( - subscriptionAccountHandles, callback); - dialogFragment.show(((Activity) context).getFragmentManager(), - TAG_SELECT_ACCT_FRAGMENT); - } - - return true; - } catch (NumberFormatException ex) { - // Ignore - } - } - return false; - } - - private static void handleAdnQuery(QueryHandler handler, SimContactQueryCookie cookie, - Uri uri) { - if (handler == null || cookie == null || uri == null) { - Log.w(TAG, "queryAdn parameters incorrect"); - return; - } - - // display the progress dialog - cookie.progressDialog.show(); - - // run the query. - handler.startQuery(ADN_QUERY_TOKEN, cookie, uri, new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, - null, null, null); - - if (sPreviousAdnQueryHandler != null) { - // It is harmless to call cancel() even after the handler's gone. - sPreviousAdnQueryHandler.cancel(); - } - sPreviousAdnQueryHandler = handler; - } - - static boolean handlePinEntry(final Context context, final String input) { - if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) { - List<PhoneAccountHandle> subscriptionAccountHandles = - PhoneAccountUtils.getSubscriptionPhoneAccounts(context); - boolean hasUserSelectedDefault = subscriptionAccountHandles.contains( - TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL)); - - if (subscriptionAccountHandles.size() <= 1 || hasUserSelectedDefault) { - // Don't bring up the dialog for single-SIM or if the default outgoing account is - // a subscription account. - return TelecomUtil.handleMmi(context, input, null); - } else { - SelectPhoneAccountListener listener = - new HandleMmiAccountSelectedCallback(context, input); - - DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance( - subscriptionAccountHandles, listener); - dialogFragment.show(((Activity) context).getFragmentManager(), - TAG_SELECT_ACCT_FRAGMENT); - } - return true; - } - return false; - } - - // TODO: Use TelephonyCapabilities.getDeviceIdLabel() to get the device id label instead of a - // hard-coded string. - static boolean handleDeviceIdDisplay(Context context, String input) { - TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - - if (telephonyManager != null && input.equals(MMI_IMEI_DISPLAY)) { - int labelResId = (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) ? - R.string.imei : R.string.meid; - - List<String> deviceIds = new ArrayList<String>(); - if (TelephonyManagerCompat.getPhoneCount(telephonyManager) > 1 && - CompatUtils.isMethodAvailable(TelephonyManagerCompat.TELEPHONY_MANAGER_CLASS, - "getDeviceId", Integer.TYPE)) { - for (int slot = 0; slot < telephonyManager.getPhoneCount(); slot++) { - String deviceId = telephonyManager.getDeviceId(slot); - if (!TextUtils.isEmpty(deviceId)) { - deviceIds.add(deviceId); - } - } - } else { - deviceIds.add(telephonyManager.getDeviceId()); - } - - AlertDialog alert = new AlertDialog.Builder(context) - .setTitle(labelResId) - .setItems(deviceIds.toArray(new String[deviceIds.size()]), null) - .setPositiveButton(android.R.string.ok, null) - .setCancelable(false) - .show(); - return true; - } - return false; - } - - private static boolean handleRegulatoryInfoDisplay(Context context, String input) { - if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) { - Log.d(TAG, "handleRegulatoryInfoDisplay() sending intent to settings app"); - Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO); - try { - context.startActivity(showRegInfoIntent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity() failed: " + e); - } - return true; - } - return false; - } - - /******* - * This code is used to handle SIM Contact queries - *******/ - private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number"; - private static final String ADN_NAME_COLUMN_NAME = "name"; - private static final int ADN_QUERY_TOKEN = -1; - - /** - * Cookie object that contains everything we need to communicate to the - * handler's onQuery Complete, as well as what we need in order to cancel - * the query (if requested). - * - * Note, access to the textField field is going to be synchronized, because - * the user can request a cancel at any time through the UI. - */ - private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{ - public ProgressDialog progressDialog; - public int contactNum; - - // Used to identify the query request. - private int mToken; - private QueryHandler mHandler; - - // The text field we're going to update - private EditText textField; - - public SimContactQueryCookie(int number, QueryHandler handler, int token) { - contactNum = number; - mHandler = handler; - mToken = token; - } - - /** - * Synchronized getter for the EditText. - */ - public synchronized EditText getTextField() { - return textField; - } - - /** - * Synchronized setter for the EditText. - */ - public synchronized void setTextField(EditText text) { - textField = text; - } - - /** - * Cancel the ADN query by stopping the operation and signaling - * the cookie that a cancel request is made. - */ - public synchronized void onCancel(DialogInterface dialog) { - // close the progress dialog - if (progressDialog != null) { - progressDialog.dismiss(); - } - - // setting the textfield to null ensures that the UI does NOT get - // updated. - textField = null; - - // Cancel the operation if possible. - mHandler.cancelOperation(mToken); - } - } - - /** - * Asynchronous query handler that services requests to look up ADNs - * - * Queries originate from {@link #handleAdnEntry}. - */ - private static class QueryHandler extends NoNullCursorAsyncQueryHandler { - - private boolean mCanceled; - - public QueryHandler(ContentResolver cr) { - super(cr); - } - - /** - * Override basic onQueryComplete to fill in the textfield when - * we're handed the ADN cursor. - */ - @Override - protected void onNotNullableQueryComplete(int token, Object cookie, Cursor c) { - try { - sPreviousAdnQueryHandler = null; - if (mCanceled) { - return; - } - - SimContactQueryCookie sc = (SimContactQueryCookie) cookie; - - // close the progress dialog. - sc.progressDialog.dismiss(); - - // get the EditText to update or see if the request was cancelled. - EditText text = sc.getTextField(); - - // if the TextView is valid, and the cursor is valid and positionable on the - // Nth number, then we update the text field and display a toast indicating the - // caller name. - if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) { - String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME)); - String number = - c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME)); - - // fill the text in. - text.getText().replace(0, 0, number); - - // display the name as a toast - Context context = sc.progressDialog.getContext(); - CharSequence msg = ContactDisplayUtils.getTtsSpannedPhoneNumber( - context.getResources(), R.string.menu_callNumber, name); - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); - } - } finally { - MoreCloseables.closeQuietly(c); - } - } - - public void cancel() { - mCanceled = true; - // Ask AsyncQueryHandler to cancel the whole request. This will fail when the query is - // already started. - cancelOperation(ADN_QUERY_TOKEN); - } - } -} diff --git a/src/com/android/dialer/TransactionSafeActivity.java b/src/com/android/dialer/TransactionSafeActivity.java deleted file mode 100644 index 81e50128d..000000000 --- a/src/com/android/dialer/TransactionSafeActivity.java +++ /dev/null @@ -1,65 +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; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; - -/** - * A common superclass that keeps track of whether an {@link Activity} has saved its state yet or - * not. - */ -public abstract class TransactionSafeActivity extends AppCompatActivity { - - private boolean mIsSafeToCommitTransactions; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mIsSafeToCommitTransactions = true; - } - - @Override - protected void onStart() { - super.onStart(); - mIsSafeToCommitTransactions = true; - } - - @Override - protected void onResume() { - super.onResume(); - mIsSafeToCommitTransactions = true; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mIsSafeToCommitTransactions = false; - } - - /** - * Returns true if it is safe to commit {@link FragmentTransaction}s at this time, based on - * whether {@link Activity#onSaveInstanceState} has been called or not. - * - * Make sure that the current activity calls into - * {@link super.onSaveInstanceState(Bundle outState)} (if that method is overridden), - * so the flag is properly set. - */ - public boolean isSafeToCommitTransactions() { - return mIsSafeToCommitTransactions; - } -} diff --git a/src/com/android/dialer/calllog/BlockReportSpamListener.java b/src/com/android/dialer/calllog/BlockReportSpamListener.java deleted file mode 100644 index 92cbc804b..000000000 --- a/src/com/android/dialer/calllog/BlockReportSpamListener.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.android.dialer.calllog; - -import android.app.Activity; -import android.app.FragmentManager; -import android.content.ContentValues; -import android.content.DialogInterface; -import android.net.Uri; -import android.support.v7.widget.RecyclerView; - -import com.android.dialer.util.BlockReportSpamDialogs; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.service.ExtendedCallInfoService; - -/** - * Listener to show dialogs for block and report spam actions. - */ -public class BlockReportSpamListener implements CallLogListItemViewHolder.OnClickListener { - - private final FragmentManager mFragmentManager; - private final RecyclerView.Adapter mAdapter; - private final ExtendedCallInfoService mExtendedCallInfoService; - private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - - public BlockReportSpamListener(FragmentManager fragmentManager, RecyclerView.Adapter adapter, - ExtendedCallInfoService extendedCallInfoService, - FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler) { - mFragmentManager = fragmentManager; - mAdapter = adapter; - mExtendedCallInfoService = extendedCallInfoService; - mFilteredNumberAsyncQueryHandler = filteredNumberAsyncQueryHandler; - } - - @Override - public void onBlockReportSpam(String displayNumber, final String number, - final String countryIso, final int callType) { - BlockReportSpamDialogs.BlockReportSpamDialogFragment.newInstance( - displayNumber, - false, - new BlockReportSpamDialogs.OnSpamDialogClickListener() { - @Override - public void onClick(boolean isSpamChecked) { - if (isSpamChecked) { - mExtendedCallInfoService.reportSpam( - number, countryIso, callType, - ExtendedCallInfoService.REPORTING_LOCATION_CALL_LOG_HISTORY); - } - mFilteredNumberAsyncQueryHandler.blockNumber( - new FilteredNumberAsyncQueryHandler.OnBlockNumberListener() { - @Override - public void onBlockComplete(Uri uri) { - mAdapter.notifyDataSetChanged(); - } - }, - number, - countryIso); - } - }, null) - .show(mFragmentManager, BlockReportSpamDialogs.BLOCK_REPORT_SPAM_DIALOG_TAG); - } - - @Override - public void onBlock(String displayNumber, final String number, final String countryIso, - final int callType) { - BlockReportSpamDialogs.BlockDialogFragment.newInstance(displayNumber, - new BlockReportSpamDialogs.OnConfirmListener() { - @Override - public void onClick() { - mExtendedCallInfoService.reportSpam(number, countryIso, callType, - ExtendedCallInfoService.REPORTING_LOCATION_CALL_LOG_HISTORY); - mFilteredNumberAsyncQueryHandler.blockNumber( - new FilteredNumberAsyncQueryHandler.OnBlockNumberListener() { - @Override - public void onBlockComplete(Uri uri) { - mAdapter.notifyDataSetChanged(); - } - }, - number, - countryIso); - } - }, null) - .show(mFragmentManager, BlockReportSpamDialogs.BLOCK_DIALOG_TAG); - } - - @Override - public void onUnblock(String displayNumber, final String number, final String countryIso, - final Integer blockId, final boolean isSpam, final int callType) { - BlockReportSpamDialogs.UnblockDialogFragment.newInstance(displayNumber, isSpam, - new BlockReportSpamDialogs.OnConfirmListener() { - @Override - public void onClick() { - if (isSpam) { - mExtendedCallInfoService.reportNotSpam( - number, countryIso, callType, - ExtendedCallInfoService.REPORTING_LOCATION_CALL_LOG_HISTORY); - } - mFilteredNumberAsyncQueryHandler.unblock( - new FilteredNumberAsyncQueryHandler.OnUnblockNumberListener() { - @Override - public void onUnblockComplete(int rows, ContentValues values) { - mAdapter.notifyDataSetChanged(); - } - }, - blockId); - } - }, null) - .show(mFragmentManager, BlockReportSpamDialogs.UNBLOCK_DIALOG_TAG); - } - - @Override - public void onReportNotSpam(String displayNumber, final String number, final String countryIso, - final int callType) { - BlockReportSpamDialogs.ReportNotSpamDialogFragment.newInstance(displayNumber, - new BlockReportSpamDialogs.OnConfirmListener() { - @Override - public void onClick() { - mExtendedCallInfoService.reportNotSpam( - number, countryIso, callType, - ExtendedCallInfoService.REPORTING_LOCATION_CALL_LOG_HISTORY); - mAdapter.notifyDataSetChanged(); - } - }, null) - .show(mFragmentManager, BlockReportSpamDialogs.NOT_SPAM_DIALOG_TAG); - } -} 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 9cde0b65d..000000000 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ /dev/null @@ -1,959 +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.android.dialer.filterednumber.BlockNumberDialogFragment; -import com.android.dialer.service.ExtendedCallInfoService; -import com.android.dialerbind.ObjectFactory; -import com.google.common.annotations.VisibleForTesting; - -import android.app.Activity; -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.TelephonyManager; -import android.text.TextUtils; -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.logging.InteractionEvent; -import com.android.dialer.logging.Logger; -import com.android.dialer.util.PhoneNumberUtil; -import com.android.dialer.voicemail.VoicemailPlaybackPresenter; - -import java.util.HashMap; - -/** - * Adapter class to fill in data for the Call Log. - */ -public class CallLogAdapter extends GroupingListAdapter - implements CallLogGroupBuilder.GroupCreator, - VoicemailPlaybackPresenter.OnVoicemailDeletedListener { - - // 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; - - 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; - private BlockNumberDialogFragment.Callback mBlockedNumberDialogCallback = - new BlockNumberDialogFragment.Callback() { - @Override - public void onFilterNumberSuccess() { - Logger.logInteraction( - InteractionEvent.BLOCK_NUMBER_CALL_LOG); - notifyDataSetChanged(); - } - - @Override - public void onUnfilterNumberSuccess() { - Logger.logInteraction( - InteractionEvent.UNBLOCK_NUMBER_CALL_LOG); - notifyDataSetChanged(); - } - - @Override - public void onChangeFilteredNumberUndo() { - } - }; - private CallLogListItemViewHolder.OnClickListener mBlockReportSpamListener; - - /** - * 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; - - private ExtendedCallInfoService mExtendedCallInfoService; - - /** - * 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(); - - mExtendedCallInfoService = ObjectFactory.newExtendedCallInfoService(context); - mBlockReportSpamListener = new BlockReportSpamListener( - ((Activity) mContext).getFragmentManager(), this, - mExtendedCallInfoService, mFilteredNumberAsyncQueryHandler); - setHasStableIds(true); - } - - 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); - } - } - - /** - * 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, - mBlockReportSpamListener, - mExpandCollapseListener, - mCallLogCache, - mCallLogListItemHelper, - mVoicemailPlaybackPresenter, - mFilteredNumberAsyncQueryHandler, - mBlockedNumberDialogCallback, - 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(final ViewHolder viewHolder, final int position) { - Cursor c = (Cursor) getItem(position); - if (c == null) { - return; - } - - final String number = c.getString(CallLogQuery.NUMBER); - final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO); - final CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder; - boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber( - new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - views.blockId = id; - if (mExtendedCallInfoService == null) { - loadDataAndRender(views); - } else { - views.isSpamFeatureEnabled = true; - mExtendedCallInfoService.getExtendedCallInfo(number, countryIso, - new ExtendedCallInfoService.Listener() { - @Override - public void onComplete(boolean isSpam) { - views.isSpam = isSpam; - loadDataAndRender(views); - } - }); - } - } - }, number, countryIso); - if (!success) { - loadDataAndRender(views); - } - } - - private void loadDataAndRender(CallLogListItemViewHolder views) { - int position = views.getAdapterPosition(); - 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; - } - - 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); - } - - // Reversely pass spam information from views since details is not constructed when spam - // information comes back. This is used to render phone call details. - details.isSpam = views.isSpam; - render(views, details); - } - - private void render(CallLogListItemViewHolder views, PhoneCallDetails details) { - 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 = views.getAdapterPosition(); - views.showActions(true); - } else { - views.showActions(false); - } - } - - 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 long getItemId(int position) { - Cursor cursor = (Cursor) getItem(position); - if (cursor != null) { - return cursor.getLong(CallLogQuery.ID); - } else { - return 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 b95d58e26..000000000 --- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java +++ /dev/null @@ -1,505 +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 com.google.common.base.Preconditions; - -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, - GET_NUMBER_IN_CALL_HISTORY - } - - 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 OnGetNumberInCallHistoryListener { - void onComplete(boolean inCallHistory); - } - - 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; - } - }); - } - - /** - * Checks if the number is in the call history. - */ - public static void getNumberInCallHistory( - final Context context, - final String number, - final OnGetNumberInCallHistoryListener listener) { - Preconditions.checkNotNull(listener); - if (!PermissionsUtil.hasPhonePermissions(context)) { - return; - } - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit(Tasks.GET_NUMBER_IN_CALL_HISTORY, - new AsyncTask<Void, Void, Boolean>() { - @Override - public Boolean doInBackground(Void... params) { - try (Cursor cursor = context.getContentResolver().query( - TelecomUtil.getCallLogUri(context), - new String[] {CallLog.Calls._ID}, - CallLog.Calls.NUMBER + " = ?", - new String[] {number}, - null)) { - return cursor != null && cursor.getCount() > 0; - } - } - - @Override - public void onPostExecute(Boolean inCallHistory) { - listener.onComplete(inCallHistory); - } - }); - } - - @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 18b6ff5d3..000000000 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ /dev/null @@ -1,268 +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; - - views.updatePhoto(); - } - - /** - * 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 858cc2102..000000000 --- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java +++ /dev/null @@ -1,776 +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.BlockedNumbersMigrator; -import com.android.dialer.filterednumber.FilteredNumbersUtil; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.PhoneNumberUtil; -import com.android.dialer.voicemail.VoicemailPlaybackLayout; -import com.android.dialer.voicemail.VoicemailPlaybackPresenter; - -/** - * 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 { - - public interface OnClickListener { - void onBlockReportSpam( - String displayNumber, String number, String countryIso, int callType); - void onBlock(String displayNumber, String number, String countryIso, int callType); - void onUnblock(String displayNumber, String number, String countryIso, Integer blockId, - boolean isSpam, int callType); - void onReportNotSpam(String displayNumber, String number, String countryIso, int callType); - } - - /** 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 blockReportView; - public View blockView; - public View unblockView; - public View reportNotSpamView; - 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 spam feature is enabled, which affects UI. - */ - public boolean isSpamFeatureEnabled; - - /** - * Whether the current log entry is a spam number or not. - */ - public boolean isSpam; - - /** - * 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 OnClickListener mBlockReportListener; - - private final BlockNumberDialogFragment.Callback mFilteredNumberDialogCallback; - - private final int mPhotoSize; - - private View.OnClickListener mExpandCollapseListener; - private boolean mVoicemailPrimaryActionButtonClicked; - - private CallLogListItemViewHolder( - Context context, - OnClickListener blockReportListener, - 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; - mBlockReportListener = blockReportListener; - - 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); - } - - public static CallLogListItemViewHolder create( - View view, - Context context, - OnClickListener blockReportListener, - View.OnClickListener expandCollapseListener, - CallLogCache callLogCache, - CallLogListItemHelper callLogListItemHelper, - VoicemailPlaybackPresenter voicemailPlaybackPresenter, - FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, - BlockNumberDialogFragment.Callback filteredNumberDialogCallback, - boolean isArchiveTab) { - - return new CallLogListItemViewHolder( - context, - blockReportListener, - 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); - - blockReportView = actionsView.findViewById(R.id.block_report_action); - blockReportView.setOnClickListener(this); - - blockView = actionsView.findViewById(R.id.block_action); - blockView.setOnClickListener(this); - - unblockView = actionsView.findViewById(R.id.unblock_action); - unblockView.setOnClickListener(this); - - reportNotSpamView = actionsView.findViewById(R.id.report_not_spam_action); - reportNotSpamView.setOnClickListener(this); - - detailsButtonView = actionsView.findViewById(R.id.details_action); - detailsButtonView.setOnClickListener(this); - - callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action); - callWithNoteButtonView.setOnClickListener(this); - } - - 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); - - updateBlockReportActions(); - } - - /** - * 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() { - if (isSpamFeatureEnabled && isSpam) { - quickContactView.setImageDrawable( - mContext.getDrawable(R.drawable.blocked_contact)); - return; - } - 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); - } - } - - @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 if (view.getId() == R.id.block_report_action) { - maybeShowBlockNumberMigrationDialog(new BlockedNumbersMigrator.Listener() { - @Override - public void onComplete() { - mBlockReportListener.onBlockReportSpam( - displayNumber, number, countryIso, callType); - } - }); - } else if (view.getId() == R.id.block_action) { - maybeShowBlockNumberMigrationDialog(new BlockedNumbersMigrator.Listener() { - @Override - public void onComplete() { - mBlockReportListener.onBlock(displayNumber, number, countryIso, callType); - } - }); - } else if (view.getId() == R.id.unblock_action) { - mBlockReportListener.onUnblock( - displayNumber, number, countryIso, blockId, isSpam, callType); - } else if (view.getId() == R.id.report_not_spam_action) { - mBlockReportListener.onReportNotSpam(displayNumber, number, countryIso, callType); - } 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); - } - } - } - } - - private void maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener) { - if (!FilteredNumberCompat.maybeShowBlockNumberMigrationDialog( - mContext.getContentResolver(), - ((Activity) mContext).getFragmentManager(), listener)) { - listener.onComplete(); - } - } - - @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; - } - - private void updateBlockReportActions() { - if (!isSpamFeatureEnabled) { - return; - } - // Set block/spam actions. - blockReportView.setVisibility(View.GONE); - blockView.setVisibility(View.GONE); - unblockView.setVisibility(View.GONE); - reportNotSpamView.setVisibility(View.GONE); - boolean isBlocked = blockId != null; - if (isBlocked) { - unblockView.setVisibility(View.VISIBLE); - } else { - if (isSpam) { - blockView.setVisibility(View.VISIBLE); - reportNotSpamView.setVisibility(View.VISIBLE); - } else { - blockReportView.setVisibility(View.VISIBLE); - } - } - - } -}
\ 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 d9898ab94..000000000 --- a/src/com/android/dialer/calllog/ContactInfoHelper.java +++ /dev/null @@ -1,499 +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.primitives.Longs; - -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.compat.DirectoryCompat; -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; - } - - final String directory = uri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); - final Long directoryId = directory == null ? null : Longs.tryParse(directory); - - 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, directoryId); - 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, @Nullable Long directoryId) { - // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed. - if (lookupKey == null || userType == ContactsUtils.USER_TYPE_WORK) { - return null; - } - - if (directoryId != null) { - // Query {@link Contacts#CONTENT_LOOKUP_URI} with work lookup key is not allowed. - if (DirectoryCompat.isEnterpriseDirectoryId(directoryId)) { - return null; - } - - // Skip this to avoid an extra remote network call for alternative name - if (DirectoryCompat.isRemoteDirectoryId(directoryId)) { - 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 - Log.e(TAG, "IllegalArgumentException in lookUpDisplayNameAlternative", e); - } 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 47f9ea770..000000000 --- a/src/com/android/dialer/calllog/MissedCallNotifier.java +++ /dev/null @@ -1,292 +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(); - String missedNumber = useCallLog ? newestCall.number : number; - - 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(missedNumber, - 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(missedNumber) - && !TextUtils.equals( - missedNumber, mContext.getString(R.string.handle_restricted))) { - builder.addAction(R.drawable.ic_phone_24dp, - mContext.getString(R.string.notification_missedCall_call_back), - createCallBackPendingIntent(missedNumber)); - - if (!PhoneNumberHelper.isUriNumber(missedNumber)) { - builder.addAction(R.drawable.ic_message_24dp, - mContext.getString(R.string.notification_missedCall_message), - createSendSmsFromNotificationPendingIntent(missedNumber)); - } - } - } - - 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 53121614c..000000000 --- a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java +++ /dev/null @@ -1,359 +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) { - if (details.isSpam) { - views.callLocationAndDate.setText( - mContext.getString(R.string.spam_number_call_log_label)); - return; - } - // 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; - } - } -} diff --git a/src/com/android/dialer/compat/DialerCompatUtils.java b/src/com/android/dialer/compat/DialerCompatUtils.java deleted file mode 100644 index a9c9c5319..000000000 --- a/src/com/android/dialer/compat/DialerCompatUtils.java +++ /dev/null @@ -1,31 +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.compat; - -import com.android.contacts.common.compat.CompatUtils; - -public final class DialerCompatUtils { - /** - * Determines if this version has access to the - * {@link android.provider.CallLog.Calls.CACHED_PHOTO_URI} column - * - * @return {@code true} if {@link android.provider.CallLog.Calls.CACHED_PHOTO_URI} is available, - * {@code false} otherwise - */ - public static boolean isCallsCachedPhotoUriCompatible() { - return CompatUtils.isMarshmallowCompatible(); - } -}
\ No newline at end of file diff --git a/src/com/android/dialer/compat/FilteredNumberCompat.java b/src/com/android/dialer/compat/FilteredNumberCompat.java deleted file mode 100644 index 8d8e9a2a5..000000000 --- a/src/com/android/dialer/compat/FilteredNumberCompat.java +++ /dev/null @@ -1,393 +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.compat; - -import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; - -import android.app.FragmentManager; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.UserManager; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.telecom.TelecomManager; -import android.telephony.PhoneNumberUtils; -import android.util.Log; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.TelecomManagerUtil; -import com.android.contacts.common.testing.NeededForTesting; -import com.android.dialer.DialerApplication; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; -import com.android.dialer.database.FilteredNumberContract.FilteredNumber; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberSources; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; -import com.android.dialer.filterednumber.BlockNumberDialogFragment; -import com.android.dialer.filterednumber.BlockNumberDialogFragment.Callback; -import com.android.dialer.filterednumber.BlockedNumbersMigrator; -import com.android.dialer.filterednumber.BlockedNumbersSettingsActivity; -import com.android.dialer.filterednumber.MigrateBlockedNumbersDialogFragment; -import com.android.dialerbind.ObjectFactory; - -import java.util.ArrayList; -import java.util.List; - -/** - * Compatibility class to encapsulate logic to switch between call blocking using - * {@link com.android.dialer.database.FilteredNumberContract} and using - * {@link android.provider.BlockedNumberContract}. This class should be used rather than explicitly - * referencing columns from either contract class in situations where both blocking solutions may be - * used. - */ -public class FilteredNumberCompat { - - private static final String TAG = "FilteredNumberCompat"; - - protected static final String HAS_MIGRATED_TO_NEW_BLOCKING_KEY = "migratedToNewBlocking"; - - private static Boolean isEnabledForTest; - - private static Context contextForTest; - - /** - * @return The column name for ID in the filtered number database. - */ - public static String getIdColumnName() { - return useNewFiltering() ? BlockedNumbersSdkCompat._ID : FilteredNumberColumns._ID; - } - - /** - * @return The column name for type in the filtered number database. Will be {@code null} for - * the framework blocking implementation. - */ - @Nullable - public static String getTypeColumnName() { - return useNewFiltering() ? null : FilteredNumberColumns.TYPE; - } - - /** - * @return The column name for source in the filtered number database. Will be {@code null} for - * the framework blocking implementation - */ - @Nullable - public static String getSourceColumnName() { - return useNewFiltering() ? null : FilteredNumberColumns.SOURCE; - } - - /** - * @return The column name for the original number in the filtered number database. - */ - public static String getOriginalNumberColumnName() { - return useNewFiltering() ? BlockedNumbersSdkCompat.COLUMN_ORIGINAL_NUMBER - : FilteredNumberColumns.NUMBER; - } - - /** - * @return The column name for country iso in the filtered number database. Will be {@code null} - * the framework blocking implementation - */ - @Nullable - public static String getCountryIsoColumnName() { - return useNewFiltering() ? null : FilteredNumberColumns.COUNTRY_ISO; - } - - /** - * @return The column name for the e164 formatted number in the filtered number database. - */ - public static String getE164NumberColumnName() { - return useNewFiltering() ? BlockedNumbersSdkCompat.E164_NUMBER - : FilteredNumberColumns.NORMALIZED_NUMBER; - } - - /** - * @return {@code true} if the current SDK version supports using new filtering, {@code false} - * otherwise. - */ - public static boolean canUseNewFiltering() { - if (isEnabledForTest != null) { - return CompatUtils.isNCompatible() && isEnabledForTest; - } - return CompatUtils.isNCompatible() && ObjectFactory - .isNewBlockingEnabled(DialerApplication.getContext()); - } - - /** - * @return {@code true} if the new filtering should be used, i.e. it's enabled and any necessary - * migration has been performed, {@code false} otherwise. - */ - public static boolean useNewFiltering() { - return canUseNewFiltering() && hasMigratedToNewBlocking(); - } - - /** - * @return {@code true} if the user has migrated to use - * {@link android.provider.BlockedNumberContract} blocking, {@code false} otherwise. - */ - public static boolean hasMigratedToNewBlocking() { - return PreferenceManager.getDefaultSharedPreferences(DialerApplication.getContext()) - .getBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, false); - } - - /** - * Called to inform this class whether the user has fully migrated to use - * {@link android.provider.BlockedNumberContract} blocking or not. - * - * @param hasMigrated {@code true} if the user has migrated, {@code false} otherwise. - */ - @NeededForTesting - public static void setHasMigratedToNewBlocking(boolean hasMigrated) { - PreferenceManager.getDefaultSharedPreferences( - MoreObjects.firstNonNull(contextForTest, DialerApplication.getContext())).edit() - .putBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, hasMigrated).apply(); - } - - @NeededForTesting - public static void setIsEnabledForTest(Boolean isEnabled) { - isEnabledForTest = isEnabled; - } - - @NeededForTesting - public static void setContextForTest(Context context) { - contextForTest = context; - } - - /** - * Gets the content {@link Uri} for number filtering. - * - * @param id The optional id to append with the base content uri. - * @return The Uri for number filtering. - */ - public static Uri getContentUri(@Nullable Integer id) { - if (id == null) { - return getBaseUri(); - } - return ContentUris.withAppendedId(getBaseUri(), id); - } - - - private static Uri getBaseUri() { - return useNewFiltering() ? BlockedNumbersSdkCompat.CONTENT_URI : FilteredNumber.CONTENT_URI; - } - - /** - * Removes any null column names from the given projection array. This method is intended to be - * used to strip out any column names that aren't available in every version of number blocking. - * Example: - * {@literal - * getContext().getContentResolver().query( - * someUri, - * // Filtering ensures that no non-existant columns are queried - * FilteredNumberCompat.filter(new String[] {FilteredNumberCompat.getIdColumnName(), - * FilteredNumberCompat.getTypeColumnName()}, - * FilteredNumberCompat.getE164NumberColumnName() + " = ?", - * new String[] {e164Number}); - * } - * - * @param projection The projection array. - * @return The filtered projection array. - */ - @Nullable - public static String[] filter(@Nullable String[] projection) { - if (projection == null) { - return null; - } - List<String> filtered = new ArrayList<>(); - for (String column : projection) { - if (column != null) { - filtered.add(column); - } - } - return filtered.toArray(new String[filtered.size()]); - } - - /** - * Creates a new {@link ContentValues} suitable for inserting in the filtered number table. - * - * @param number The unformatted number to insert. - * @param e164Number (optional) The number to insert formatted to E164 standard. - * @param countryIso (optional) The country iso to use to format the number. - * @return The ContentValues to insert. - * @throws NullPointerException If number is null. - */ - public static ContentValues newBlockNumberContentValues(String number, - @Nullable String e164Number, @Nullable String countryIso) { - ContentValues contentValues = new ContentValues(); - contentValues.put(getOriginalNumberColumnName(), Preconditions.checkNotNull(number)); - if (!useNewFiltering()) { - if (e164Number == null) { - e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); - } - contentValues.put(getE164NumberColumnName(), e164Number); - contentValues.put(getCountryIsoColumnName(), countryIso); - contentValues.put(getTypeColumnName(), FilteredNumberTypes.BLOCKED_NUMBER); - contentValues.put(getSourceColumnName(), FilteredNumberSources.USER); - } - return contentValues; - } - - /** - * Shows block number migration dialog if necessary. - * - * @param fragmentManager The {@link FragmentManager} used to show fragments. - * @param listener The {@link BlockedNumbersMigrator.Listener} to call when migration is - * complete. - * @return boolean True if migration dialog is shown. - */ - public static boolean maybeShowBlockNumberMigrationDialog( - ContentResolver contentResolver, FragmentManager fragmentManager, - BlockedNumbersMigrator.Listener listener) { - if (shouldShowMigrationDialog(true)) { - Log.i(TAG, "maybeShowBlockNumberMigrationDialog - showing migration dialog"); - MigrateBlockedNumbersDialogFragment - .newInstance(new BlockedNumbersMigrator(contentResolver), listener) - .show(fragmentManager, "MigrateBlockedNumbers"); - return true; - } - return false; - } - - /** - * Shows the flow of {@link android.app.DialogFragment}s for blocking or unblocking numbers. - * - * @param blockId The id into the blocked numbers database. - * @param number The number to block or unblock. - * @param countryIso The countryIso used to format the given number. - * @param displayNumber The form of the number to block, suitable for displaying. - * @param parentViewId The id for the containing view of the Dialog. - * @param fragmentManager The {@link FragmentManager} used to show fragments. - * @param callback (optional) The {@link Callback} to call when the block or unblock operation - * is complete. - */ - public static void showBlockNumberDialogFlow(final ContentResolver contentResolver, - final Integer blockId, final String number, final String countryIso, - final String displayNumber, final Integer parentViewId, - final FragmentManager fragmentManager, @Nullable final Callback callback) { - Log.i(TAG, "showBlockNumberDialogFlow - start"); - // If the user is blocking a number and isn't using the framework solution when they - // should be, show the migration dialog - if (shouldShowMigrationDialog(blockId == null)) { - Log.i(TAG, "showBlockNumberDialogFlow - showing migration dialog"); - MigrateBlockedNumbersDialogFragment - .newInstance(new BlockedNumbersMigrator(contentResolver), newMigrationListener( - DialerApplication.getContext().getContentResolver(), number, countryIso, - displayNumber, parentViewId, fragmentManager, callback)) - .show(fragmentManager, "MigrateBlockedNumbers"); - return; - } - Log.i(TAG, "showBlockNumberDialogFlow - showing block number dialog"); - BlockNumberDialogFragment - .show(blockId, number, countryIso, displayNumber, parentViewId, fragmentManager, - callback); - } - - private static boolean shouldShowMigrationDialog(boolean isBlocking) { - return isBlocking && canUseNewFiltering() && !hasMigratedToNewBlocking(); - } - - private static BlockedNumbersMigrator.Listener newMigrationListener( - final ContentResolver contentResolver, final String number, final String countryIso, - final String displayNumber, final Integer parentViewId, - final FragmentManager fragmentManager, @Nullable final Callback callback) { - return new BlockedNumbersMigrator.Listener() { - @Override - public void onComplete() { - Log.i(TAG, "showBlockNumberDialogFlow - listener showing block number dialog"); - if (!hasMigratedToNewBlocking()) { - Log.i(TAG, "showBlockNumberDialogFlow - migration failed"); - return; - } - /* - * Edge case to cover here: if the user initiated the migration workflow with a - * number that's already blocked in the framework, don't show the block number - * dialog. Doing so would allow them to block the same number twice, causing a - * crash. - */ - new FilteredNumberAsyncQueryHandler(contentResolver).isBlockedNumber( - new OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - if (id != null) { - Log.i(TAG, - "showBlockNumberDialogFlow - number already blocked"); - return; - } - Log.i(TAG, "showBlockNumberDialogFlow - need to block number"); - BlockNumberDialogFragment - .show(null, number, countryIso, displayNumber, parentViewId, - fragmentManager, callback); - } - }, number, countryIso); - } - }; - } - - /** - * Creates the {@link Intent} which opens the blocked numbers management interface. - * - * @param context The {@link Context}. - * @return The intent. - */ - public static Intent createManageBlockedNumbersIntent(Context context) { - if (canUseNewFiltering() && hasMigratedToNewBlocking()) { - return TelecomManagerUtil.createManageBlockedNumbersIntent( - (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE)); - } - return new Intent(context, BlockedNumbersSettingsActivity.class); - } - - /** - * Method used to determine if block operations are possible. - * - * @param context The {@link Context}. - * @return {@code true} if the app and user can block numbers, {@code false} otherwise. - */ - public static boolean canAttemptBlockOperations(Context context) { - if (!CompatUtils.isNCompatible()) { - // Dialer blocking, must be primary user - return UserManagerCompat.isSystemUser( - (UserManager) context.getSystemService(Context.USER_SERVICE)); - } - - // Great Wall blocking, must be primary user and the default or system dialer - // TODO(maxwelb): check that we're the default or system Dialer - return BlockedNumbersSdkCompat.canCurrentUserBlockNumbers(context); - } - - /** - * Used to determine if the call blocking settings can be opened. - * - * @param context The {@link Context}. - * @return {@code true} if the current user can open the call blocking settings, {@code false} - * otherwise. - */ - public static boolean canCurrentUserOpenBlockSettings(Context context) { - if (!CompatUtils.isNCompatible()) { - // Dialer blocking, must be primary user - return UserManagerCompat.isSystemUser( - (UserManager) context.getSystemService(Context.USER_SERVICE)); - } - // BlockedNumberContract blocking, verify through Contract API - return BlockedNumbersSdkCompat.canCurrentUserBlockNumbers(context); - } -} diff --git a/src/com/android/dialer/compat/SettingsCompat.java b/src/com/android/dialer/compat/SettingsCompat.java deleted file mode 100644 index 474a600a4..000000000 --- a/src/com/android/dialer/compat/SettingsCompat.java +++ /dev/null @@ -1,47 +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.compat; - -import android.content.Context; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.provider.Settings; - -import com.android.contacts.common.compat.SdkVersionOverride; - -/** - * Compatibility class for {@link android.provider.Settings} - */ -public class SettingsCompat { - - public static class System { - - /** - * Compatibility version of {@link android.provider.Settings.System#canWrite(Context)} - * - * Note: Since checking preferences at runtime started in M, this method always returns - * {@code true} for SDK versions prior to 23. In those versions, the app wouldn't be - * installed if it didn't have the proper permission - */ - public static boolean canWrite(Context context) { - if (SdkVersionOverride.getSdkVersion(VERSION_CODES.LOLLIPOP) >= Build.VERSION_CODES.M) { - return Settings.System.canWrite(context); - } - return true; - } - } - -} diff --git a/src/com/android/dialer/compat/UserManagerCompat.java b/src/com/android/dialer/compat/UserManagerCompat.java deleted file mode 100644 index 576703364..000000000 --- a/src/com/android/dialer/compat/UserManagerCompat.java +++ /dev/null @@ -1,71 +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.compat; - -import android.content.Context; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; - -import com.android.contacts.common.compat.CompatUtils; - -/** - * Compatibility class for {@link UserManager}. - */ -public class UserManagerCompat { - /** - * A user id constant to indicate the "system" user of the device. Copied from - * {@link UserHandle}. - */ - private static final int USER_SYSTEM = 0; - /** - * Range of uids allocated for a user. - */ - private static final int PER_USER_RANGE = 100000; - - /** - * Used to check if this process is running under the system user. The system user is the - * initial user that is implicitly created on first boot and hosts most of the system services. - * - * @return whether this process is running under the system user. - */ - public static boolean isSystemUser(UserManager userManager) { - if (CompatUtils.isMarshmallowCompatible()) { - return userManager.isSystemUser(); - } - // Adapted from {@link UserManager} and {@link UserHandle}. - return (Process.myUid() / PER_USER_RANGE) == USER_SYSTEM; - } - - /** - * Return whether the calling user is running in an "unlocked" state. A user - * is unlocked only after they've entered their credentials (such as a lock - * pattern or PIN), and credential-encrypted private app data storage is - * available. - * - * TODO b/26688153 - * - * @param context the current context - * @return {@code true} if the user is unlocked, {@code false} otherwise - * @throws NullPointerException if context is null - */ - public static boolean isUserUnlocked(Context context) { - if (CompatUtils.isNCompatible()) { - return UserManagerSdkCompat.isUserUnlocked(context); - } - return true; - } -} diff --git a/src/com/android/dialer/contact/ContactUpdateService.java b/src/com/android/dialer/contact/ContactUpdateService.java deleted file mode 100644 index 9edd19827..000000000 --- a/src/com/android/dialer/contact/ContactUpdateService.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2012 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.contact; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; - -import com.android.contacts.common.database.ContactUpdateUtils; - -/** - * Service for updating primary number on a contact. - */ -public class ContactUpdateService extends IntentService { - - public static final String EXTRA_PHONE_NUMBER_DATA_ID = "phone_number_data_id"; - - public ContactUpdateService() { - super(ContactUpdateService.class.getSimpleName()); - setIntentRedelivery(true); - } - - /** Creates an intent that sets the selected data item as super primary (default) */ - public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { - Intent serviceIntent = new Intent(context, ContactUpdateService.class); - serviceIntent.putExtra(EXTRA_PHONE_NUMBER_DATA_ID, dataId); - return serviceIntent; - } - - @Override - protected void onHandleIntent(Intent intent) { - // Currently this service only handles one type of update. - long dataId = intent.getLongExtra(EXTRA_PHONE_NUMBER_DATA_ID, -1); - - ContactUpdateUtils.setSuperPrimary(this, dataId); - } -} diff --git a/src/com/android/dialer/contactinfo/ContactInfoCache.java b/src/com/android/dialer/contactinfo/ContactInfoCache.java deleted file mode 100644 index 28a919430..000000000 --- a/src/com/android/dialer/contactinfo/ContactInfoCache.java +++ /dev/null @@ -1,333 +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.contactinfo; - -import android.os.Handler; -import android.os.Message; -import android.text.TextUtils; - -import com.android.dialer.calllog.ContactInfo; -import com.android.dialer.calllog.ContactInfoHelper; -import com.android.dialer.util.ExpirableCache; -import com.google.common.annotations.VisibleForTesting; - -import java.util.LinkedList; - -/** - * This is a cache of contact details for the phone numbers in the call log. The key is the - * phone number with the country in which the call was placed or received. The content of the - * cache is expired (but not purged) whenever the application comes to the foreground. - * - * This cache queues request for information and queries for information on a background thread, - * so {@code start()} and {@code stop()} must be called to initiate or halt that thread's exeuction - * as needed. - * - * TODO: Explore whether there is a pattern to remove external dependencies for starting and - * stopping the query thread. - */ -public class ContactInfoCache { - public interface OnContactInfoChangedListener { - public void onContactInfoChanged(); - } - - /* - * Handles requests for contact name and number type. - */ - private class QueryThread extends Thread { - private volatile boolean mDone = false; - - public QueryThread() { - super("ContactInfoCache.QueryThread"); - } - - public void stopProcessing() { - mDone = true; - } - - @Override - public void run() { - boolean needRedraw = false; - while (true) { - // Check if thread is finished, and if so return immediately. - if (mDone) return; - - // Obtain next request, if any is available. - // Keep synchronized section small. - ContactInfoRequest req = null; - synchronized (mRequests) { - if (!mRequests.isEmpty()) { - req = mRequests.removeFirst(); - } - } - - if (req != null) { - // Process the request. If the lookup succeeds, schedule a redraw. - needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo); - } else { - // Throttle redraw rate by only sending them when there are - // more requests. - if (needRedraw) { - needRedraw = false; - mHandler.sendEmptyMessage(REDRAW); - } - - // Wait until another request is available, or until this - // thread is no longer needed (as indicated by being - // interrupted). - try { - synchronized (mRequests) { - mRequests.wait(1000); - } - } catch (InterruptedException ie) { - // Ignore, and attempt to continue processing requests. - } - } - } - } - } - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case REDRAW: - mOnContactInfoChangedListener.onContactInfoChanged(); - break; - case START_THREAD: - startRequestProcessing(); - break; - } - } - }; - - private static final int REDRAW = 1; - private static final int START_THREAD = 2; - - private static final int CONTACT_INFO_CACHE_SIZE = 100; - private static final int START_PROCESSING_REQUESTS_DELAY_MS = 1000; - - - /** - * List of requests to update contact details. Each request contains a phone number to look up, - * and the contact info currently stored in the call log for this number. - * - * The requests are added when displaying contacts and are processed by a background thread. - */ - private final LinkedList<ContactInfoRequest> mRequests; - - private ExpirableCache<NumberWithCountryIso, ContactInfo> mCache; - - private ContactInfoHelper mContactInfoHelper; - private QueryThread mContactInfoQueryThread; - private OnContactInfoChangedListener mOnContactInfoChangedListener; - - public ContactInfoCache(ContactInfoHelper contactInfoHelper, - OnContactInfoChangedListener onContactInfoChangedListener) { - mContactInfoHelper = contactInfoHelper; - mOnContactInfoChangedListener = onContactInfoChangedListener; - - mRequests = new LinkedList<ContactInfoRequest>(); - mCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE); - } - - public ContactInfo getValue(String number, String countryIso, ContactInfo cachedContactInfo) { - NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso); - ExpirableCache.CachedValue<ContactInfo> cachedInfo = - mCache.getCachedValue(numberCountryIso); - ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue(); - if (cachedInfo == null) { - mCache.put(numberCountryIso, ContactInfo.EMPTY); - // Use the cached contact info from the call log. - info = cachedContactInfo; - // The db request should happen on a non-UI thread. - // Request the contact details immediately since they are currently missing. - enqueueRequest(number, countryIso, cachedContactInfo, true); - // We will format the phone number when we make the background request. - } else { - if (cachedInfo.isExpired()) { - // The contact info is no longer up to date, we should request it. However, we - // do not need to request them immediately. - enqueueRequest(number, countryIso, cachedContactInfo, false); - } else if (!callLogInfoMatches(cachedContactInfo, info)) { - // The call log information does not match the one we have, look it up again. - // We could simply update the call log directly, but that needs to be done in a - // background thread, so it is easier to simply request a new lookup, which will, as - // a side-effect, update the call log. - enqueueRequest(number, countryIso, cachedContactInfo, false); - } - - if (info == ContactInfo.EMPTY) { - // Use the cached contact info from the call log. - info = cachedContactInfo; - } - } - return info; - } - - /** - * Queries the appropriate content provider for the contact associated with the number. - * - * Upon completion it also updates the cache in the call log, if it is different from - * {@code callLogInfo}. - * - * The number might be either a SIP address or a phone number. - * - * It returns true if it updated the content of the cache and we should therefore tell the - * view to update its content. - */ - private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) { - final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso); - - if (info == null) { - // The lookup failed, just return without requesting to update the view. - return false; - } - - // Check the existing entry in the cache: only if it has changed we should update the - // view. - NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso); - ContactInfo existingInfo = mCache.getPossiblyExpired(numberCountryIso); - - final boolean isRemoteSource = info.sourceType != 0; - - // Don't force redraw if existing info in the cache is equal to {@link ContactInfo#EMPTY} - // to avoid updating the data set for every new row that is scrolled into view. - // see (https://googleplex-android-review.git.corp.google.com/#/c/166680/) - - // Exception: Photo uris for contacts from remote sources are not cached in the call log - // cache, so we have to force a redraw for these contacts regardless. - boolean updated = (existingInfo != ContactInfo.EMPTY || isRemoteSource) && - !info.equals(existingInfo); - - // Store the data in the cache so that the UI thread can use to display it. Store it - // even if it has not changed so that it is marked as not expired. - mCache.put(numberCountryIso, info); - - // Update the call log even if the cache it is up-to-date: it is possible that the cache - // contains the value from a different call log entry. - mContactInfoHelper.updateCallLogContactInfo(number, countryIso, info, callLogInfo); - return updated; - } - - /** - * After a delay, start the thread to begin processing requests. We perform lookups on a - * background thread, but this must be called to indicate the thread should be running. - */ - public void start() { - // Schedule a thread-creation message if the thread hasn't been created yet, as an - // optimization to queue fewer messages. - if (mContactInfoQueryThread == null) { - // TODO: Check whether this delay before starting to process is necessary. - mHandler.sendEmptyMessageDelayed(START_THREAD, START_PROCESSING_REQUESTS_DELAY_MS); - } - } - - /** - * Stops the thread and clears the queue of messages to process. This cleans up the thread - * for lookups so that it is not perpetually running. - */ - public void stop() { - stopRequestProcessing(); - } - - /** - * Starts a background thread to process contact-lookup requests, unless one - * has already been started. - */ - private synchronized void startRequestProcessing() { - // For unit-testing. - if (mRequestProcessingDisabled) return; - - // If a thread is already started, don't start another. - if (mContactInfoQueryThread != null) { - return; - } - - mContactInfoQueryThread = new QueryThread(); - mContactInfoQueryThread.setPriority(Thread.MIN_PRIORITY); - mContactInfoQueryThread.start(); - } - - public void invalidate() { - mCache.expireAll(); - stopRequestProcessing(); - } - - /** - * Stops the background thread that processes updates and cancels any - * pending requests to start it. - */ - private synchronized void stopRequestProcessing() { - // Remove any pending requests to start the processing thread. - mHandler.removeMessages(START_THREAD); - if (mContactInfoQueryThread != null) { - // Stop the thread; we are finished with it. - mContactInfoQueryThread.stopProcessing(); - mContactInfoQueryThread.interrupt(); - mContactInfoQueryThread = null; - } - } - - /** - * Enqueues a request to look up the contact details for the given phone number. - * <p> - * It also provides the current contact info stored in the call log for this number. - * <p> - * If the {@code immediate} parameter is true, it will start immediately the thread that looks - * up the contact information (if it has not been already started). Otherwise, it will be - * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}. - */ - protected void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo, - boolean immediate) { - ContactInfoRequest request = new ContactInfoRequest(number, countryIso, callLogInfo); - synchronized (mRequests) { - if (!mRequests.contains(request)) { - mRequests.add(request); - mRequests.notifyAll(); - } - } - if (immediate) { - startRequestProcessing(); - } - } - - /** - * Checks whether the contact info from the call log matches the one from the contacts db. - */ - private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) { - // The call log only contains a subset of the fields in the contacts db. Only check those. - return TextUtils.equals(callLogInfo.name, info.name) - && callLogInfo.type == info.type - && TextUtils.equals(callLogInfo.label, info.label); - } - - private volatile boolean mRequestProcessingDisabled = false; - - /** - * Sets whether processing of requests for contact details should be enabled. - */ - public void disableRequestProcessing() { - mRequestProcessingDisabled = true; - } - - @VisibleForTesting - public void injectContactInfoForTest( - String number, String countryIso, ContactInfo contactInfo) { - NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso); - mCache.put(numberCountryIso, contactInfo); - } -} diff --git a/src/com/android/dialer/contactinfo/ContactInfoRequest.java b/src/com/android/dialer/contactinfo/ContactInfoRequest.java deleted file mode 100644 index ec5c1198e..000000000 --- a/src/com/android/dialer/contactinfo/ContactInfoRequest.java +++ /dev/null @@ -1,65 +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.contactinfo; - -import android.text.TextUtils; - -import com.android.dialer.calllog.ContactInfo; -import com.google.common.base.Objects; - -/** - * A request for contact details for the given number, used by the ContactInfoCache. - */ -public final class ContactInfoRequest { - /** The number to look-up. */ - public final String number; - /** The country in which a call to or from this number was placed or received. */ - public final String countryIso; - /** The cached contact information stored in the call log. */ - public final ContactInfo callLogInfo; - - public ContactInfoRequest(String number, String countryIso, ContactInfo callLogInfo) { - this.number = number; - this.countryIso = countryIso; - this.callLogInfo = callLogInfo; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (!(obj instanceof ContactInfoRequest)) return false; - - ContactInfoRequest other = (ContactInfoRequest) obj; - - if (!TextUtils.equals(number, other.number)) return false; - if (!TextUtils.equals(countryIso, other.countryIso)) return false; - if (!Objects.equal(callLogInfo, other.callLogInfo)) return false; - - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((callLogInfo == null) ? 0 : callLogInfo.hashCode()); - result = prime * result + ((countryIso == null) ? 0 : countryIso.hashCode()); - result = prime * result + ((number == null) ? 0 : number.hashCode()); - return result; - } -} diff --git a/src/com/android/dialer/contactinfo/ContactPhotoLoader.java b/src/com/android/dialer/contactinfo/ContactPhotoLoader.java deleted file mode 100644 index f36c438f6..000000000 --- a/src/com/android/dialer/contactinfo/ContactPhotoLoader.java +++ /dev/null @@ -1,120 +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.contactinfo; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.provider.MediaStore; -import android.support.annotation.Nullable; -import android.support.v4.graphics.drawable.RoundedBitmapDrawable; -import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; -import android.util.Log; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.lettertiles.LetterTileDrawable; -import com.android.dialer.R; -import com.android.dialer.calllog.ContactInfo; -import com.android.dialer.calllog.ContactInfoHelper; -import com.android.dialer.util.Assert; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; - -import java.io.IOException; -/** - * Class to create the appropriate contact icon from a ContactInfo. - * This class is for synchronous, blocking calls to generate bitmaps, while - * ContactCommons.ContactPhotoManager is to cache, manage and update a ImageView asynchronously. - */ -public class ContactPhotoLoader { - - private static final String TAG = "ContactPhotoLoader"; - - private final Context mContext; - private final ContactInfo mContactInfo; - - public ContactPhotoLoader(Context context, ContactInfo contactInfo) { - mContext = Preconditions.checkNotNull(context); - mContactInfo = Preconditions.checkNotNull(contactInfo); - } - - /** - * Create a contact photo icon bitmap appropriate for the ContactInfo. - */ - public Bitmap loadPhotoIcon() { - Assert.assertNotUiThread("ContactPhotoLoader#loadPhotoIcon called on UI thread"); - int photoSize = mContext.getResources().getDimensionPixelSize(R.dimen.contact_photo_size); - return drawableToBitmap(getIcon(), photoSize, photoSize); - } - - @VisibleForTesting - Drawable getIcon() { - Drawable drawable = createPhotoIconDrawable(); - if (drawable == null) { - drawable = createLetterTileDrawable(); - } - return drawable; - } - - /** - * @return a {@link Drawable} of circular photo icon if the photo can be loaded, {@code null} - * otherwise. - */ - @Nullable - private Drawable createPhotoIconDrawable() { - if (mContactInfo.photoUri == null) { - return null; - } - try { - Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), - mContactInfo.photoUri); - final RoundedBitmapDrawable drawable = - RoundedBitmapDrawableFactory.create(mContext.getResources(), bitmap); - drawable.setAntiAlias(true); - drawable.setCornerRadius(bitmap.getHeight() / 2); - return drawable; - } catch (IOException e) { - Log.e(TAG, e.toString()); - return null; - } - } - - /** - * @return a {@link LetterTileDrawable} based on the ContactInfo. - */ - private Drawable createLetterTileDrawable() { - LetterTileDrawable drawable = new LetterTileDrawable(mContext.getResources()); - drawable.setIsCircular(true); - ContactInfoHelper helper = - new ContactInfoHelper(mContext, GeoUtil.getCurrentCountryIso(mContext)); - if (helper.isBusiness(mContactInfo.sourceType)) { - drawable.setContactType(LetterTileDrawable.TYPE_BUSINESS); - } - drawable.setLetterAndColorFromContactDetails(mContactInfo.name, mContactInfo.lookupKey); - return drawable; - } - - private static Bitmap drawableToBitmap(Drawable drawable, int width, int height) { - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } - -} diff --git a/src/com/android/dialer/contactinfo/NumberWithCountryIso.java b/src/com/android/dialer/contactinfo/NumberWithCountryIso.java deleted file mode 100644 index 1383fb7e9..000000000 --- a/src/com/android/dialer/contactinfo/NumberWithCountryIso.java +++ /dev/null @@ -1,53 +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.contactinfo; - -import android.text.TextUtils; - -/** - * Stores a phone number of a call with the country code where it originally occurred. This object - * is used as a key in the {@code ContactInfoCache}. - * - * The country does not necessarily specify the country of the phone number itself, but rather - * it is the country in which the user was in when the call was placed or received. - */ -public final class NumberWithCountryIso { - public final String number; - public final String countryIso; - - public NumberWithCountryIso(String number, String countryIso) { - this.number = number; - this.countryIso = countryIso; - } - - @Override - public boolean equals(Object o) { - if (o == null) return false; - if (!(o instanceof NumberWithCountryIso)) return false; - NumberWithCountryIso other = (NumberWithCountryIso) o; - return TextUtils.equals(number, other.number) - && TextUtils.equals(countryIso, other.countryIso); - } - - @Override - public int hashCode() { - int numberHashCode = number == null ? 0 : number.hashCode(); - int countryHashCode = countryIso == null ? 0 : countryIso.hashCode(); - - return numberHashCode ^ countryHashCode; - } -} diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java deleted file mode 100644 index 5edfb270d..000000000 --- a/src/com/android/dialer/database/DialerDatabaseHelper.java +++ /dev/null @@ -1,1169 +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.database; - -import android.content.ContentValues; -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteStatement; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.BaseColumns; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.Directory; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.common.util.StopWatch; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialer.database.VoicemailArchiveContract.VoicemailArchive; -import com.android.dialer.R; -import com.android.dialer.dialpad.SmartDialNameMatcher; -import com.android.dialer.dialpad.SmartDialPrefix; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Objects; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Database helper for smart dial. Designed as a singleton to make sure there is - * only one access point to the database. Provides methods to maintain, update, - * and query the database. - */ -public class DialerDatabaseHelper extends SQLiteOpenHelper { - private static final String TAG = "DialerDatabaseHelper"; - private static final boolean DEBUG = false; - private boolean mIsTestInstance = false; - - private static DialerDatabaseHelper sSingleton = null; - - private static final Object mLock = new Object(); - private static final AtomicBoolean sInUpdate = new AtomicBoolean(false); - private final Context mContext; - - /** - * SmartDial DB version ranges: - * <pre> - * 0-98 KitKat - * </pre> - */ - public static final int DATABASE_VERSION = 9; - public static final String DATABASE_NAME = "dialer.db"; - - /** - * Saves the last update time of smart dial databases to shared preferences. - */ - private static final String DATABASE_LAST_CREATED_SHARED_PREF = "com.android.dialer"; - private static final String LAST_UPDATED_MILLIS = "last_updated_millis"; - private static final String DATABASE_VERSION_PROPERTY = "database_version"; - - private static final int MAX_ENTRIES = 20; - - public interface Tables { - /** Saves a list of numbers to be blocked.*/ - static final String FILTERED_NUMBER_TABLE = "filtered_numbers_table"; - /** Saves the necessary smart dial information of all contacts. */ - static final String SMARTDIAL_TABLE = "smartdial_table"; - /** Saves all possible prefixes to refer to a contacts.*/ - static final String PREFIX_TABLE = "prefix_table"; - /** Saves all archived voicemail information. */ - static final String VOICEMAIL_ARCHIVE_TABLE = "voicemail_archive_table"; - /** Database properties for internal use */ - static final String PROPERTIES = "properties"; - } - - public static final Uri SMART_DIAL_UPDATED_URI = - Uri.parse("content://com.android.dialer/smart_dial_updated"); - - public interface SmartDialDbColumns { - static final String _ID = "id"; - static final String DATA_ID = "data_id"; - static final String NUMBER = "phone_number"; - static final String CONTACT_ID = "contact_id"; - static final String LOOKUP_KEY = "lookup_key"; - static final String DISPLAY_NAME_PRIMARY = "display_name"; - static final String PHOTO_ID = "photo_id"; - static final String LAST_TIME_USED = "last_time_used"; - static final String TIMES_USED = "times_used"; - static final String STARRED = "starred"; - static final String IS_SUPER_PRIMARY = "is_super_primary"; - static final String IN_VISIBLE_GROUP = "in_visible_group"; - static final String IS_PRIMARY = "is_primary"; - static final String CARRIER_PRESENCE = "carrier_presence"; - static final String LAST_SMARTDIAL_UPDATE_TIME = "last_smartdial_update_time"; - } - - public static interface PrefixColumns extends BaseColumns { - static final String PREFIX = "prefix"; - static final String CONTACT_ID = "contact_id"; - } - - public interface PropertiesColumns { - String PROPERTY_KEY = "property_key"; - String PROPERTY_VALUE = "property_value"; - } - - /** Query options for querying the contact database.*/ - public static interface PhoneQuery { - static final Uri URI = Phone.CONTENT_URI.buildUpon(). - appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, - String.valueOf(Directory.DEFAULT)). - appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true"). - build(); - - static final String[] PROJECTION = new String[] { - Phone._ID, // 0 - Phone.TYPE, // 1 - Phone.LABEL, // 2 - Phone.NUMBER, // 3 - Phone.CONTACT_ID, // 4 - Phone.LOOKUP_KEY, // 5 - Phone.DISPLAY_NAME_PRIMARY, // 6 - Phone.PHOTO_ID, // 7 - Data.LAST_TIME_USED, // 8 - Data.TIMES_USED, // 9 - Contacts.STARRED, // 10 - Data.IS_SUPER_PRIMARY, // 11 - Contacts.IN_VISIBLE_GROUP, // 12 - Data.IS_PRIMARY, // 13 - Data.CARRIER_PRESENCE, // 14 - }; - - static final int PHONE_ID = 0; - static final int PHONE_TYPE = 1; - static final int PHONE_LABEL = 2; - static final int PHONE_NUMBER = 3; - static final int PHONE_CONTACT_ID = 4; - static final int PHONE_LOOKUP_KEY = 5; - static final int PHONE_DISPLAY_NAME = 6; - static final int PHONE_PHOTO_ID = 7; - static final int PHONE_LAST_TIME_USED = 8; - static final int PHONE_TIMES_USED = 9; - static final int PHONE_STARRED = 10; - static final int PHONE_IS_SUPER_PRIMARY = 11; - static final int PHONE_IN_VISIBLE_GROUP = 12; - static final int PHONE_IS_PRIMARY = 13; - static final int PHONE_CARRIER_PRESENCE = 14; - - /** Selects only rows that have been updated after a certain time stamp.*/ - static final String SELECT_UPDATED_CLAUSE = - Phone.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?"; - - /** Ignores contacts that have an unreasonably long lookup key. These are likely to be - * the result of multiple (> 50) merged raw contacts, and are likely to cause - * OutOfMemoryExceptions within SQLite, or cause memory allocation problems later on - * when iterating through the cursor set (see b/13133579) - */ - static final String SELECT_IGNORE_LOOKUP_KEY_TOO_LONG_CLAUSE = - "length(" + Phone.LOOKUP_KEY + ") < 1000"; - - static final String SELECTION = SELECT_UPDATED_CLAUSE + " AND " + - SELECT_IGNORE_LOOKUP_KEY_TOO_LONG_CLAUSE; - } - - /** - * Query for all contacts that have been updated since the last time the smart dial database - * was updated. - */ - public static interface UpdatedContactQuery { - static final Uri URI = ContactsContract.Contacts.CONTENT_URI; - - static final String[] PROJECTION = new String[] { - ContactsContract.Contacts._ID // 0 - }; - - static final int UPDATED_CONTACT_ID = 0; - - static final String SELECT_UPDATED_CLAUSE = - ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?"; - } - - /** Query options for querying the deleted contact database.*/ - public static interface DeleteContactQuery { - static final Uri URI = ContactsContract.DeletedContacts.CONTENT_URI; - - static final String[] PROJECTION = new String[] { - ContactsContract.DeletedContacts.CONTACT_ID, // 0 - ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP, // 1 - }; - - static final int DELETED_CONTACT_ID = 0; - static final int DELECTED_TIMESTAMP = 1; - - /** Selects only rows that have been deleted after a certain time stamp.*/ - public static final String SELECT_UPDATED_CLAUSE = - ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?"; - } - - /** - * Gets the sorting order for the smartdial table. This computes a SQL "ORDER BY" argument by - * composing contact status and recent contact details together. - */ - private static interface SmartDialSortingOrder { - /** Current contacts - those contacted within the last 3 days (in milliseconds) */ - static final long LAST_TIME_USED_CURRENT_MS = 3L * 24 * 60 * 60 * 1000; - /** Recent contacts - those contacted within the last 30 days (in milliseconds) */ - static final long LAST_TIME_USED_RECENT_MS = 30L * 24 * 60 * 60 * 1000; - - /** Time since last contact. */ - static final String TIME_SINCE_LAST_USED_MS = "( ?1 - " + - Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.LAST_TIME_USED + ")"; - - /** Contacts that have been used in the past 3 days rank higher than contacts that have - * been used in the past 30 days, which rank higher than contacts that have not been used - * in recent 30 days. - */ - static final String SORT_BY_DATA_USAGE = - "(CASE WHEN " + TIME_SINCE_LAST_USED_MS + " < " + LAST_TIME_USED_CURRENT_MS + - " THEN 0 " + - " WHEN " + TIME_SINCE_LAST_USED_MS + " < " + LAST_TIME_USED_RECENT_MS + - " THEN 1 " + - " ELSE 2 END)"; - - /** This sort order is similar to that used by the ContactsProvider when returning a list - * of frequently called contacts. - */ - static final String SORT_ORDER = - Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.STARRED + " DESC, " - + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.IS_SUPER_PRIMARY + " DESC, " - + SORT_BY_DATA_USAGE + ", " - + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.TIMES_USED + " DESC, " - + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.IN_VISIBLE_GROUP + " DESC, " - + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " - + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.CONTACT_ID + ", " - + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.IS_PRIMARY + " DESC"; - } - - /** - * Simple data format for a contact, containing only information needed for showing up in - * smart dial interface. - */ - public static class ContactNumber { - public final long id; - public final long dataId; - public final String displayName; - public final String phoneNumber; - public final String lookupKey; - public final long photoId; - public final int carrierPresence; - - public ContactNumber(long id, long dataID, String displayName, String phoneNumber, - String lookupKey, long photoId, int carrierPresence) { - this.dataId = dataID; - this.id = id; - this.displayName = displayName; - this.phoneNumber = phoneNumber; - this.lookupKey = lookupKey; - this.photoId = photoId; - this.carrierPresence = carrierPresence; - } - - @Override - public int hashCode() { - return Objects.hashCode(id, dataId, displayName, phoneNumber, lookupKey, photoId, - carrierPresence); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object instanceof ContactNumber) { - final ContactNumber that = (ContactNumber) object; - return Objects.equal(this.id, that.id) - && Objects.equal(this.dataId, that.dataId) - && Objects.equal(this.displayName, that.displayName) - && Objects.equal(this.phoneNumber, that.phoneNumber) - && Objects.equal(this.lookupKey, that.lookupKey) - && Objects.equal(this.photoId, that.photoId) - && Objects.equal(this.carrierPresence, that.carrierPresence); - } - return false; - } - } - - /** - * Data format for finding duplicated contacts. - */ - private class ContactMatch { - private final String lookupKey; - private final long id; - - public ContactMatch(String lookupKey, long id) { - this.lookupKey = lookupKey; - this.id = id; - } - - @Override - public int hashCode() { - return Objects.hashCode(lookupKey, id); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object instanceof ContactMatch) { - final ContactMatch that = (ContactMatch) object; - return Objects.equal(this.lookupKey, that.lookupKey) - && Objects.equal(this.id, that.id); - } - return false; - } - } - - /** - * Access function to get the singleton instance of DialerDatabaseHelper. - */ - public static synchronized DialerDatabaseHelper getInstance(Context context) { - if (DEBUG) { - Log.v(TAG, "Getting Instance"); - } - if (sSingleton == null) { - // Use application context instead of activity context because this is a singleton, - // and we don't want to leak the activity if the activity is not running but the - // dialer database helper is still doing work. - sSingleton = new DialerDatabaseHelper(context.getApplicationContext(), - DATABASE_NAME); - } - return sSingleton; - } - - /** - * Returns a new instance for unit tests. The database will be created in memory. - */ - @VisibleForTesting - static DialerDatabaseHelper getNewInstanceForTest(Context context) { - return new DialerDatabaseHelper(context, null, true); - } - - protected DialerDatabaseHelper(Context context, String databaseName, boolean isTestInstance) { - this(context, databaseName, DATABASE_VERSION); - mIsTestInstance = isTestInstance; - } - - protected DialerDatabaseHelper(Context context, String databaseName) { - this(context, databaseName, DATABASE_VERSION); - } - - protected DialerDatabaseHelper(Context context, String databaseName, int dbVersion) { - super(context, databaseName, null, dbVersion); - mContext = Preconditions.checkNotNull(context, "Context must not be null"); - } - - /** - * Creates tables in the database when database is created for the first time. - * - * @param db The database. - */ - @Override - public void onCreate(SQLiteDatabase db) { - setupTables(db); - } - - private void setupTables(SQLiteDatabase db) { - dropTables(db); - db.execSQL("CREATE TABLE " + Tables.SMARTDIAL_TABLE + " (" - + SmartDialDbColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SmartDialDbColumns.DATA_ID + " INTEGER, " - + SmartDialDbColumns.NUMBER + " TEXT," - + SmartDialDbColumns.CONTACT_ID + " INTEGER," - + SmartDialDbColumns.LOOKUP_KEY + " TEXT," - + SmartDialDbColumns.DISPLAY_NAME_PRIMARY + " TEXT, " - + SmartDialDbColumns.PHOTO_ID + " INTEGER, " - + SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + " LONG, " - + SmartDialDbColumns.LAST_TIME_USED + " LONG, " - + SmartDialDbColumns.TIMES_USED + " INTEGER, " - + SmartDialDbColumns.STARRED + " INTEGER, " - + SmartDialDbColumns.IS_SUPER_PRIMARY + " INTEGER, " - + SmartDialDbColumns.IN_VISIBLE_GROUP + " INTEGER, " - + SmartDialDbColumns.IS_PRIMARY + " INTEGER, " - + SmartDialDbColumns.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0" - + ");"); - - db.execSQL("CREATE TABLE " + Tables.PREFIX_TABLE + " (" - + PrefixColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + PrefixColumns.PREFIX + " TEXT COLLATE NOCASE, " - + PrefixColumns.CONTACT_ID + " INTEGER" - + ");"); - - db.execSQL("CREATE TABLE " + Tables.PROPERTIES + " (" - + PropertiesColumns.PROPERTY_KEY + " TEXT PRIMARY KEY, " - + PropertiesColumns.PROPERTY_VALUE + " TEXT " - + ");"); - - // This will need to also be updated in setupTablesForFilteredNumberTest and onUpgrade. - // Hardcoded so we know on glance what columns are updated in setupTables, - // and to be able to guarantee the state of the DB at each upgrade step. - db.execSQL("CREATE TABLE " + Tables.FILTERED_NUMBER_TABLE + " (" - + FilteredNumberColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + FilteredNumberColumns.NORMALIZED_NUMBER + " TEXT UNIQUE," - + FilteredNumberColumns.NUMBER + " TEXT," - + FilteredNumberColumns.COUNTRY_ISO + " TEXT," - + FilteredNumberColumns.TIMES_FILTERED + " INTEGER," - + FilteredNumberColumns.LAST_TIME_FILTERED + " LONG," - + FilteredNumberColumns.CREATION_TIME + " LONG," - + FilteredNumberColumns.TYPE + " INTEGER," - + FilteredNumberColumns.SOURCE + " INTEGER" - + ");"); - - createVoicemailArchiveTable(db); - setProperty(db, DATABASE_VERSION_PROPERTY, String.valueOf(DATABASE_VERSION)); - if (!mIsTestInstance) { - resetSmartDialLastUpdatedTime(); - } - } - - public void dropTables(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + Tables.PREFIX_TABLE); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SMARTDIAL_TABLE); - db.execSQL("DROP TABLE IF EXISTS " + Tables.PROPERTIES); - db.execSQL("DROP TABLE IF EXISTS " + Tables.FILTERED_NUMBER_TABLE); - db.execSQL("DROP TABLE IF EXISTS " + Tables.VOICEMAIL_ARCHIVE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldNumber, int newNumber) { - // Disregard the old version and new versions provided by SQLiteOpenHelper, we will read - // our own from the database. - - int oldVersion; - - oldVersion = getPropertyAsInt(db, DATABASE_VERSION_PROPERTY, 0); - - if (oldVersion == 0) { - Log.e(TAG, "Malformed database version..recreating database"); - } - - if (oldVersion < 4) { - setupTables(db); - return; - } - - if (oldVersion < 7) { - db.execSQL("DROP TABLE IF EXISTS " + Tables.FILTERED_NUMBER_TABLE); - db.execSQL("CREATE TABLE " + Tables.FILTERED_NUMBER_TABLE + " (" - + FilteredNumberColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + FilteredNumberColumns.NORMALIZED_NUMBER + " TEXT UNIQUE," - + FilteredNumberColumns.NUMBER + " TEXT," - + FilteredNumberColumns.COUNTRY_ISO + " TEXT," - + FilteredNumberColumns.TIMES_FILTERED + " INTEGER," - + FilteredNumberColumns.LAST_TIME_FILTERED + " LONG," - + FilteredNumberColumns.CREATION_TIME + " LONG," - + FilteredNumberColumns.TYPE + " INTEGER," - + FilteredNumberColumns.SOURCE + " INTEGER" - + ");"); - oldVersion = 7; - } - - if (oldVersion < 8) { - upgradeToVersion8(db); - oldVersion = 8; - } - - if (oldVersion < 9) { - db.execSQL("DROP TABLE IF EXISTS " + Tables.VOICEMAIL_ARCHIVE_TABLE); - createVoicemailArchiveTable(db); - oldVersion = 9; - } - - if (oldVersion != DATABASE_VERSION) { - throw new IllegalStateException( - "error upgrading the database to version " + DATABASE_VERSION); - } - - setProperty(db, DATABASE_VERSION_PROPERTY, String.valueOf(DATABASE_VERSION)); - } - - public void upgradeToVersion8(SQLiteDatabase db) { - db.execSQL("ALTER TABLE smartdial_table ADD carrier_presence INTEGER NOT NULL DEFAULT 0"); - } - - /** - * Stores a key-value pair in the {@link Tables#PROPERTIES} table. - */ - public void setProperty(String key, String value) { - setProperty(getWritableDatabase(), key, value); - } - - public void setProperty(SQLiteDatabase db, String key, String value) { - final ContentValues values = new ContentValues(); - values.put(PropertiesColumns.PROPERTY_KEY, key); - values.put(PropertiesColumns.PROPERTY_VALUE, value); - db.replace(Tables.PROPERTIES, null, values); - } - - /** - * Returns the value from the {@link Tables#PROPERTIES} table. - */ - public String getProperty(String key, String defaultValue) { - return getProperty(getReadableDatabase(), key, defaultValue); - } - - public String getProperty(SQLiteDatabase db, String key, String defaultValue) { - try { - String value = null; - final Cursor cursor = db.query(Tables.PROPERTIES, - new String[] {PropertiesColumns.PROPERTY_VALUE}, - PropertiesColumns.PROPERTY_KEY + "=?", - new String[] {key}, null, null, null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - value = cursor.getString(0); - } - } finally { - cursor.close(); - } - } - return value != null ? value : defaultValue; - } catch (SQLiteException e) { - return defaultValue; - } - } - - public int getPropertyAsInt(SQLiteDatabase db, String key, int defaultValue) { - final String stored = getProperty(db, key, ""); - try { - return Integer.parseInt(stored); - } catch (NumberFormatException e) { - return defaultValue; - } - } - - private void resetSmartDialLastUpdatedTime() { - final SharedPreferences databaseLastUpdateSharedPref = mContext.getSharedPreferences( - DATABASE_LAST_CREATED_SHARED_PREF, Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = databaseLastUpdateSharedPref.edit(); - editor.putLong(LAST_UPDATED_MILLIS, 0); - editor.commit(); - } - - /** - * Starts the database upgrade process in the background. - */ - public void startSmartDialUpdateThread() { - if (PermissionsUtil.hasContactsPermissions(mContext)) { - new SmartDialUpdateAsyncTask().execute(); - } - } - - private class SmartDialUpdateAsyncTask extends AsyncTask { - @Override - protected Object doInBackground(Object[] objects) { - if (DEBUG) { - Log.v(TAG, "Updating database"); - } - updateSmartDialDatabase(); - return null; - } - - @Override - protected void onCancelled() { - if (DEBUG) { - Log.v(TAG, "Updating Cancelled"); - } - super.onCancelled(); - } - - @Override - protected void onPostExecute(Object o) { - if (DEBUG) { - Log.v(TAG, "Updating Finished"); - } - super.onPostExecute(o); - } - } - /** - * Removes rows in the smartdial database that matches the contacts that have been deleted - * by other apps since last update. - * - * @param db Database to operate on. - * @param deletedContactCursor Cursor containing rows of deleted contacts - */ - @VisibleForTesting - void removeDeletedContacts(SQLiteDatabase db, Cursor deletedContactCursor) { - if (deletedContactCursor == null) { - return; - } - - db.beginTransaction(); - try { - while (deletedContactCursor.moveToNext()) { - final Long deleteContactId = - deletedContactCursor.getLong(DeleteContactQuery.DELETED_CONTACT_ID); - db.delete(Tables.SMARTDIAL_TABLE, - SmartDialDbColumns.CONTACT_ID + "=" + deleteContactId, null); - db.delete(Tables.PREFIX_TABLE, - PrefixColumns.CONTACT_ID + "=" + deleteContactId, null); - } - - db.setTransactionSuccessful(); - } finally { - deletedContactCursor.close(); - db.endTransaction(); - } - } - - private Cursor getDeletedContactCursor(String lastUpdateMillis) { - return mContext.getContentResolver().query( - DeleteContactQuery.URI, - DeleteContactQuery.PROJECTION, - DeleteContactQuery.SELECT_UPDATED_CLAUSE, - new String[] {lastUpdateMillis}, - null); - } - - /** - * Removes potentially corrupted entries in the database. These contacts may be added before - * the previous instance of the dialer was destroyed for some reason. For data integrity, we - * delete all of them. - - * @param db Database pointer to the dialer database. - * @param last_update_time Time stamp of last successful update of the dialer database. - */ - private void removePotentiallyCorruptedContacts(SQLiteDatabase db, String last_update_time) { - db.delete(Tables.PREFIX_TABLE, - PrefixColumns.CONTACT_ID + " IN " + - "(SELECT " + SmartDialDbColumns.CONTACT_ID + " FROM " + Tables.SMARTDIAL_TABLE + - " WHERE " + SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + " > " + - last_update_time + ")", - null); - db.delete(Tables.SMARTDIAL_TABLE, - SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + " > " + last_update_time, null); - } - - /** - * All columns excluding MIME_TYPE, _DATA, ARCHIVED, SERVER_ID, are the same as - * the columns in the {@link android.provider.CallLog.Calls} table. - * - * @param db Database pointer to the dialer database. - */ - private void createVoicemailArchiveTable(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_ARCHIVE_TABLE + " (" - + VoicemailArchive._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + VoicemailArchive.NUMBER + " TEXT," - + VoicemailArchive.DATE + " LONG," - + VoicemailArchive.DURATION + " LONG," - + VoicemailArchive.MIME_TYPE + " TEXT," - + VoicemailArchive.COUNTRY_ISO + " TEXT," - + VoicemailArchive._DATA + " TEXT," - + VoicemailArchive.GEOCODED_LOCATION + " TEXT," - + VoicemailArchive.CACHED_NAME + " TEXT," - + VoicemailArchive.CACHED_NUMBER_TYPE + " INTEGER," - + VoicemailArchive.CACHED_NUMBER_LABEL + " TEXT," - + VoicemailArchive.CACHED_LOOKUP_URI + " TEXT," - + VoicemailArchive.CACHED_MATCHED_NUMBER + " TEXT," - + VoicemailArchive.CACHED_NORMALIZED_NUMBER + " TEXT," - + VoicemailArchive.CACHED_PHOTO_ID + " LONG," - + VoicemailArchive.CACHED_FORMATTED_NUMBER + " TEXT," - + VoicemailArchive.ARCHIVED + " INTEGER," - + VoicemailArchive.NUMBER_PRESENTATION + " INTEGER," - + VoicemailArchive.ACCOUNT_COMPONENT_NAME + " TEXT," - + VoicemailArchive.ACCOUNT_ID + " TEXT," - + VoicemailArchive.FEATURES + " INTEGER," - + VoicemailArchive.SERVER_ID + " INTEGER," - + VoicemailArchive.TRANSCRIPTION + " TEXT," - + VoicemailArchive.CACHED_PHOTO_URI + " TEXT" - + ");"); - } - - /** - * Removes all entries in the smartdial contact database. - */ - @VisibleForTesting - void removeAllContacts(SQLiteDatabase db) { - db.delete(Tables.SMARTDIAL_TABLE, null, null); - db.delete(Tables.PREFIX_TABLE, null, null); - } - - /** - * Counts number of rows of the prefix table. - */ - @VisibleForTesting - int countPrefixTableRows(SQLiteDatabase db) { - return (int)DatabaseUtils.longForQuery(db, "SELECT COUNT(1) FROM " + Tables.PREFIX_TABLE, - null); - } - - /** - * Removes rows in the smartdial database that matches updated contacts. - * - * @param db Database pointer to the smartdial database - * @param updatedContactCursor Cursor pointing to the list of recently updated contacts. - */ - @VisibleForTesting - void removeUpdatedContacts(SQLiteDatabase db, Cursor updatedContactCursor) { - db.beginTransaction(); - try { - updatedContactCursor.moveToPosition(-1); - while (updatedContactCursor.moveToNext()) { - final Long contactId = - updatedContactCursor.getLong(UpdatedContactQuery.UPDATED_CONTACT_ID); - - db.delete(Tables.SMARTDIAL_TABLE, SmartDialDbColumns.CONTACT_ID + "=" + - contactId, null); - db.delete(Tables.PREFIX_TABLE, PrefixColumns.CONTACT_ID + "=" + - contactId, null); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - /** - * Inserts updated contacts as rows to the smartdial table. - * - * @param db Database pointer to the smartdial database. - * @param updatedContactCursor Cursor pointing to the list of recently updated contacts. - * @param currentMillis Current time to be recorded in the smartdial table as update timestamp. - */ - @VisibleForTesting - protected void insertUpdatedContactsAndNumberPrefix(SQLiteDatabase db, - Cursor updatedContactCursor, Long currentMillis) { - db.beginTransaction(); - try { - final String sqlInsert = "INSERT INTO " + Tables.SMARTDIAL_TABLE + " (" + - SmartDialDbColumns.DATA_ID + ", " + - SmartDialDbColumns.NUMBER + ", " + - SmartDialDbColumns.CONTACT_ID + ", " + - SmartDialDbColumns.LOOKUP_KEY + ", " + - SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " + - SmartDialDbColumns.PHOTO_ID + ", " + - SmartDialDbColumns.LAST_TIME_USED + ", " + - SmartDialDbColumns.TIMES_USED + ", " + - SmartDialDbColumns.STARRED + ", " + - SmartDialDbColumns.IS_SUPER_PRIMARY + ", " + - SmartDialDbColumns.IN_VISIBLE_GROUP+ ", " + - SmartDialDbColumns.IS_PRIMARY + ", " + - SmartDialDbColumns.CARRIER_PRESENCE + ", " + - SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + ") " + - " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - final SQLiteStatement insert = db.compileStatement(sqlInsert); - - final String numberSqlInsert = "INSERT INTO " + Tables.PREFIX_TABLE + " (" + - PrefixColumns.CONTACT_ID + ", " + - PrefixColumns.PREFIX + ") " + - " VALUES (?, ?)"; - final SQLiteStatement numberInsert = db.compileStatement(numberSqlInsert); - - updatedContactCursor.moveToPosition(-1); - while (updatedContactCursor.moveToNext()) { - insert.clearBindings(); - - // Handle string columns which can possibly be null first. In the case of certain - // null columns (due to malformed rows possibly inserted by third-party apps - // or sync adapters), skip the phone number row. - final String number = updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER); - if (TextUtils.isEmpty(number)) { - continue; - } else { - insert.bindString(2, number); - } - - final String lookupKey = updatedContactCursor.getString( - PhoneQuery.PHONE_LOOKUP_KEY); - if (TextUtils.isEmpty(lookupKey)) { - continue; - } else { - insert.bindString(4, lookupKey); - } - - final String displayName = updatedContactCursor.getString( - PhoneQuery.PHONE_DISPLAY_NAME); - if (displayName == null) { - insert.bindString(5, mContext.getResources().getString(R.string.missing_name)); - } else { - insert.bindString(5, displayName); - } - insert.bindLong(1, updatedContactCursor.getLong(PhoneQuery.PHONE_ID)); - insert.bindLong(3, updatedContactCursor.getLong(PhoneQuery.PHONE_CONTACT_ID)); - insert.bindLong(6, updatedContactCursor.getLong(PhoneQuery.PHONE_PHOTO_ID)); - insert.bindLong(7, updatedContactCursor.getLong(PhoneQuery.PHONE_LAST_TIME_USED)); - insert.bindLong(8, updatedContactCursor.getInt(PhoneQuery.PHONE_TIMES_USED)); - insert.bindLong(9, updatedContactCursor.getInt(PhoneQuery.PHONE_STARRED)); - insert.bindLong(10, updatedContactCursor.getInt(PhoneQuery.PHONE_IS_SUPER_PRIMARY)); - insert.bindLong(11, updatedContactCursor.getInt(PhoneQuery.PHONE_IN_VISIBLE_GROUP)); - insert.bindLong(12, updatedContactCursor.getInt(PhoneQuery.PHONE_IS_PRIMARY)); - insert.bindLong(13, updatedContactCursor.getInt(PhoneQuery.PHONE_CARRIER_PRESENCE)); - insert.bindLong(14, currentMillis); - insert.executeInsert(); - final String contactPhoneNumber = - updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER); - final ArrayList<String> numberPrefixes = - SmartDialPrefix.parseToNumberTokens(contactPhoneNumber); - - for (String numberPrefix : numberPrefixes) { - numberInsert.bindLong(1, updatedContactCursor.getLong( - PhoneQuery.PHONE_CONTACT_ID)); - numberInsert.bindString(2, numberPrefix); - numberInsert.executeInsert(); - numberInsert.clearBindings(); - } - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - /** - * Inserts prefixes of contact names to the prefix table. - * - * @param db Database pointer to the smartdial database. - * @param nameCursor Cursor pointing to the list of distinct updated contacts. - */ - @VisibleForTesting - void insertNamePrefixes(SQLiteDatabase db, Cursor nameCursor) { - final int columnIndexName = nameCursor.getColumnIndex( - SmartDialDbColumns.DISPLAY_NAME_PRIMARY); - final int columnIndexContactId = nameCursor.getColumnIndex(SmartDialDbColumns.CONTACT_ID); - - db.beginTransaction(); - try { - final String sqlInsert = "INSERT INTO " + Tables.PREFIX_TABLE + " (" + - PrefixColumns.CONTACT_ID + ", " + - PrefixColumns.PREFIX + ") " + - " VALUES (?, ?)"; - final SQLiteStatement insert = db.compileStatement(sqlInsert); - - while (nameCursor.moveToNext()) { - /** Computes a list of prefixes of a given contact name. */ - final ArrayList<String> namePrefixes = - SmartDialPrefix.generateNamePrefixes(nameCursor.getString(columnIndexName)); - - for (String namePrefix : namePrefixes) { - insert.bindLong(1, nameCursor.getLong(columnIndexContactId)); - insert.bindString(2, namePrefix); - insert.executeInsert(); - insert.clearBindings(); - } - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - /** - * Updates the smart dial and prefix database. - * This method queries the Delta API to get changed contacts since last update, and updates the - * records in smartdial database and prefix database accordingly. - * It also queries the deleted contact database to remove newly deleted contacts since last - * update. - */ - public void updateSmartDialDatabase() { - final SQLiteDatabase db = getWritableDatabase(); - - synchronized(mLock) { - if (DEBUG) { - Log.v(TAG, "Starting to update database"); - } - final StopWatch stopWatch = DEBUG ? StopWatch.start("Updating databases") : null; - - /** Gets the last update time on the database. */ - final SharedPreferences databaseLastUpdateSharedPref = mContext.getSharedPreferences( - DATABASE_LAST_CREATED_SHARED_PREF, Context.MODE_PRIVATE); - final String lastUpdateMillis = String.valueOf( - databaseLastUpdateSharedPref.getLong(LAST_UPDATED_MILLIS, 0)); - - if (DEBUG) { - Log.v(TAG, "Last updated at " + lastUpdateMillis); - } - - /** Sets the time after querying the database as the current update time. */ - final Long currentMillis = System.currentTimeMillis(); - - if (DEBUG) { - stopWatch.lap("Queried the Contacts database"); - } - - /** Prevents the app from reading the dialer database when updating. */ - sInUpdate.getAndSet(true); - - /** Removes contacts that have been deleted. */ - removeDeletedContacts(db, getDeletedContactCursor(lastUpdateMillis)); - removePotentiallyCorruptedContacts(db, lastUpdateMillis); - - if (DEBUG) { - stopWatch.lap("Finished deleting deleted entries"); - } - - /** If the database did not exist before, jump through deletion as there is nothing - * to delete. - */ - if (!lastUpdateMillis.equals("0")) { - /** Removes contacts that have been updated. Updated contact information will be - * inserted later. Note that this has to use a separate result set from - * updatePhoneCursor, since it is possible for a contact to be updated (e.g. - * phone number deleted), but have no results show up in updatedPhoneCursor (since - * all of its phone numbers have been deleted). - */ - final Cursor updatedContactCursor = mContext.getContentResolver().query( - UpdatedContactQuery.URI, - UpdatedContactQuery.PROJECTION, - UpdatedContactQuery.SELECT_UPDATED_CLAUSE, - new String[] {lastUpdateMillis}, - null - ); - if (updatedContactCursor == null) { - Log.e(TAG, "SmartDial query received null for cursor"); - return; - } - try { - removeUpdatedContacts(db, updatedContactCursor); - } finally { - updatedContactCursor.close(); - } - if (DEBUG) { - stopWatch.lap("Finished deleting entries belonging to updated contacts"); - } - } - - /** Queries the contact database to get all phone numbers that have been updated since the last - * update time. - */ - final Cursor updatedPhoneCursor = mContext.getContentResolver().query(PhoneQuery.URI, - PhoneQuery.PROJECTION, PhoneQuery.SELECTION, - new String[]{lastUpdateMillis}, null); - if (updatedPhoneCursor == null) { - Log.e(TAG, "SmartDial query received null for cursor"); - return; - } - - try { - /** Inserts recently updated phone numbers to the smartdial database.*/ - insertUpdatedContactsAndNumberPrefix(db, updatedPhoneCursor, currentMillis); - if (DEBUG) { - stopWatch.lap("Finished building the smart dial table"); - } - } finally { - updatedPhoneCursor.close(); - } - - /** Gets a list of distinct contacts which have been updated, and adds the name prefixes - * of these contacts to the prefix table. - */ - final Cursor nameCursor = db.rawQuery( - "SELECT DISTINCT " + - SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " + SmartDialDbColumns.CONTACT_ID + - " FROM " + Tables.SMARTDIAL_TABLE + - " WHERE " + SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + - " = " + Long.toString(currentMillis), - new String[] {}); - if (nameCursor != null) { - try { - if (DEBUG) { - stopWatch.lap("Queried the smart dial table for contact names"); - } - - /** Inserts prefixes of names into the prefix table.*/ - insertNamePrefixes(db, nameCursor); - if (DEBUG) { - stopWatch.lap("Finished building the name prefix table"); - } - } finally { - nameCursor.close(); - } - } - - /** Creates index on contact_id for fast JOIN operation. */ - db.execSQL("CREATE INDEX IF NOT EXISTS smartdial_contact_id_index ON " + - Tables.SMARTDIAL_TABLE + " (" + SmartDialDbColumns.CONTACT_ID + ");"); - /** Creates index on last_smartdial_update_time for fast SELECT operation. */ - db.execSQL("CREATE INDEX IF NOT EXISTS smartdial_last_update_index ON " + - Tables.SMARTDIAL_TABLE + " (" + - SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + ");"); - /** Creates index on sorting fields for fast sort operation. */ - db.execSQL("CREATE INDEX IF NOT EXISTS smartdial_sort_index ON " + - Tables.SMARTDIAL_TABLE + " (" + - SmartDialDbColumns.STARRED + ", " + - SmartDialDbColumns.IS_SUPER_PRIMARY + ", " + - SmartDialDbColumns.LAST_TIME_USED + ", " + - SmartDialDbColumns.TIMES_USED + ", " + - SmartDialDbColumns.IN_VISIBLE_GROUP + ", " + - SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " + - SmartDialDbColumns.CONTACT_ID + ", " + - SmartDialDbColumns.IS_PRIMARY + - ");"); - /** Creates index on prefix for fast SELECT operation. */ - db.execSQL("CREATE INDEX IF NOT EXISTS nameprefix_index ON " + - Tables.PREFIX_TABLE + " (" + PrefixColumns.PREFIX + ");"); - /** Creates index on contact_id for fast JOIN operation. */ - db.execSQL("CREATE INDEX IF NOT EXISTS nameprefix_contact_id_index ON " + - Tables.PREFIX_TABLE + " (" + PrefixColumns.CONTACT_ID + ");"); - - if (DEBUG) { - stopWatch.lap(TAG + "Finished recreating index"); - } - - /** Updates the database index statistics.*/ - db.execSQL("ANALYZE " + Tables.SMARTDIAL_TABLE); - db.execSQL("ANALYZE " + Tables.PREFIX_TABLE); - db.execSQL("ANALYZE smartdial_contact_id_index"); - db.execSQL("ANALYZE smartdial_last_update_index"); - db.execSQL("ANALYZE nameprefix_index"); - db.execSQL("ANALYZE nameprefix_contact_id_index"); - if (DEBUG) { - stopWatch.stopAndLog(TAG + "Finished updating index stats", 0); - } - - sInUpdate.getAndSet(false); - - final SharedPreferences.Editor editor = databaseLastUpdateSharedPref.edit(); - editor.putLong(LAST_UPDATED_MILLIS, currentMillis); - editor.commit(); - - // Notify content observers that smart dial database has been updated. - mContext.getContentResolver().notifyChange(SMART_DIAL_UPDATED_URI, null, false); - } - } - - /** - * Returns a list of candidate contacts where the query is a prefix of the dialpad index of - * the contact's name or phone number. - * - * @param query The prefix of a contact's dialpad index. - * @return A list of top candidate contacts that will be suggested to user to match their input. - */ - public ArrayList<ContactNumber> getLooseMatches(String query, - SmartDialNameMatcher nameMatcher) { - final boolean inUpdate = sInUpdate.get(); - if (inUpdate) { - return Lists.newArrayList(); - } - - final SQLiteDatabase db = getReadableDatabase(); - - /** Uses SQL query wildcard '%' to represent prefix matching.*/ - final String looseQuery = query + "%"; - - final ArrayList<ContactNumber> result = Lists.newArrayList(); - - final StopWatch stopWatch = DEBUG ? StopWatch.start(":Name Prefix query") : null; - - final String currentTimeStamp = Long.toString(System.currentTimeMillis()); - - /** Queries the database to find contacts that have an index matching the query prefix. */ - final Cursor cursor = db.rawQuery("SELECT " + - SmartDialDbColumns.DATA_ID + ", " + - SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " + - SmartDialDbColumns.PHOTO_ID + ", " + - SmartDialDbColumns.NUMBER + ", " + - SmartDialDbColumns.CONTACT_ID + ", " + - SmartDialDbColumns.LOOKUP_KEY + ", " + - SmartDialDbColumns.CARRIER_PRESENCE + - " FROM " + Tables.SMARTDIAL_TABLE + " WHERE " + - SmartDialDbColumns.CONTACT_ID + " IN " + - " (SELECT " + PrefixColumns.CONTACT_ID + - " FROM " + Tables.PREFIX_TABLE + - " WHERE " + Tables.PREFIX_TABLE + "." + PrefixColumns.PREFIX + - " LIKE '" + looseQuery + "')" + - " ORDER BY " + SmartDialSortingOrder.SORT_ORDER, - new String[] {currentTimeStamp}); - if (cursor == null) { - return result; - } - try { - if (DEBUG) { - stopWatch.lap("Prefix query completed"); - } - - /** Gets the column ID from the cursor.*/ - final int columnDataId = 0; - final int columnDisplayNamePrimary = 1; - final int columnPhotoId = 2; - final int columnNumber = 3; - final int columnId = 4; - final int columnLookupKey = 5; - final int columnCarrierPresence = 6; - if (DEBUG) { - stopWatch.lap("Found column IDs"); - } - - final Set<ContactMatch> duplicates = new HashSet<ContactMatch>(); - int counter = 0; - if (DEBUG) { - stopWatch.lap("Moved cursor to start"); - } - /** Iterates the cursor to find top contact suggestions without duplication.*/ - while ((cursor.moveToNext()) && (counter < MAX_ENTRIES)) { - final long dataID = cursor.getLong(columnDataId); - final String displayName = cursor.getString(columnDisplayNamePrimary); - final String phoneNumber = cursor.getString(columnNumber); - final long id = cursor.getLong(columnId); - final long photoId = cursor.getLong(columnPhotoId); - final String lookupKey = cursor.getString(columnLookupKey); - final int carrierPresence = cursor.getInt(columnCarrierPresence); - - /** If a contact already exists and another phone number of the contact is being - * processed, skip the second instance. - */ - final ContactMatch contactMatch = new ContactMatch(lookupKey, id); - if (duplicates.contains(contactMatch)) { - continue; - } - - /** - * If the contact has either the name or number that matches the query, add to the - * result. - */ - final boolean nameMatches = nameMatcher.matches(displayName); - final boolean numberMatches = - (nameMatcher.matchesNumber(phoneNumber, query) != null); - if (nameMatches || numberMatches) { - /** If a contact has not been added, add it to the result and the hash set.*/ - duplicates.add(contactMatch); - result.add(new ContactNumber(id, dataID, displayName, phoneNumber, lookupKey, - photoId, carrierPresence)); - counter++; - if (DEBUG) { - stopWatch.lap("Added one result: Name: " + displayName); - } - } - } - - if (DEBUG) { - stopWatch.stopAndLog(TAG + "Finished loading cursor", 0); - } - } finally { - cursor.close(); - } - return result; - } -} diff --git a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java deleted file mode 100644 index 68a2e85d5..000000000 --- a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java +++ /dev/null @@ -1,273 +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.database; - -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabaseCorruptException; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; - -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberContract.FilteredNumber; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; - -public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { - private static final int NO_TOKEN = 0; - - public FilteredNumberAsyncQueryHandler(ContentResolver cr) { - super(cr); - } - - /** - * Methods for FilteredNumberAsyncQueryHandler result returns. - */ - private static abstract class Listener { - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - } - protected void onInsertComplete(int token, Object cookie, Uri uri) { - } - protected void onUpdateComplete(int token, Object cookie, int result) { - } - protected void onDeleteComplete(int token, Object cookie, int result) { - } - } - - public interface OnCheckBlockedListener { - /** - * Invoked after querying if a number is blocked. - * @param id The ID of the row if blocked, null otherwise. - */ - void onCheckComplete(Integer id); - } - - public interface OnBlockNumberListener { - /** - * Invoked after inserting a blocked number. - * @param uri The uri of the newly created row. - */ - void onBlockComplete(Uri uri); - } - - public interface OnUnblockNumberListener { - /** - * Invoked after removing a blocked number - * @param rows The number of rows affected (expected value 1). - * @param values The deleted data (used for restoration). - */ - void onUnblockComplete(int rows, ContentValues values); - } - - public interface OnHasBlockedNumbersListener { - /** - * @param hasBlockedNumbers {@code true} if any blocked numbers are stored. - * {@code false} otherwise. - */ - void onHasBlockedNumbers(boolean hasBlockedNumbers); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if (cookie != null) { - ((Listener) cookie).onQueryComplete(token, cookie, cursor); - } - } - - @Override - protected void onInsertComplete(int token, Object cookie, Uri uri) { - if (cookie != null) { - ((Listener) cookie).onInsertComplete(token, cookie, uri); - } - } - - @Override - protected void onUpdateComplete(int token, Object cookie, int result) { - if (cookie != null) { - ((Listener) cookie).onUpdateComplete(token, cookie, result); - } - } - - @Override - protected void onDeleteComplete(int token, Object cookie, int result) { - if (cookie != null) { - ((Listener) cookie).onDeleteComplete(token, cookie, result); - } - } - - public final void incrementFilteredCount(Integer id) { - // No concept of counts with new filtering - if (FilteredNumberCompat.useNewFiltering()) { - return; - } - startUpdate(NO_TOKEN, null, - ContentUris.withAppendedId(FilteredNumber.CONTENT_URI_INCREMENT_FILTERED_COUNT, id), - null, null, null); - } - - public void hasBlockedNumbers(final OnHasBlockedNumbersListener listener) { - startQuery(NO_TOKEN, - new Listener() { - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - listener.onHasBlockedNumbers(cursor != null && cursor.getCount() > 0); - } - }, - FilteredNumberCompat.getContentUri(null), - new String[]{ FilteredNumberCompat.getIdColumnName() }, - FilteredNumberCompat.useNewFiltering() ? null : FilteredNumberColumns.TYPE - + "=" + FilteredNumberTypes.BLOCKED_NUMBER, - null, - null); - } - - /** - * Check if this number has been blocked. - * - * @return {@code false} if the number was invalid and couldn't be checked, - * {@code true} otherwise, - */ - public boolean isBlockedNumber( - final OnCheckBlockedListener listener, String number, String countryIso) { - final String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); - if (TextUtils.isEmpty(e164Number)) { - return false; - } - - startQuery(NO_TOKEN, - new Listener() { - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - /* - * In the frameworking blocking, numbers can be blocked in both e164 format - * and not, resulting in multiple rows being returned for this query. For - * example, both '16502530000' and '6502530000' can exist at the same time - * and will be returned by this query. - */ - if (cursor == null || cursor.getCount() == 0) { - listener.onCheckComplete(null); - return; - } - cursor.moveToFirst(); - // New filtering doesn't have a concept of type - if (!FilteredNumberCompat.useNewFiltering() - && cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns.TYPE)) - != FilteredNumberTypes.BLOCKED_NUMBER) { - listener.onCheckComplete(null); - return; - } - listener.onCheckComplete( - cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID))); - } - }, - FilteredNumberCompat.getContentUri(null), - FilteredNumberCompat.filter(new String[]{FilteredNumberCompat.getIdColumnName(), - FilteredNumberCompat.getTypeColumnName()}), - FilteredNumberCompat.getE164NumberColumnName() + " = ?", - new String[]{e164Number}, - null); - - return true; - } - - public void blockNumber( - final OnBlockNumberListener listener, String number, @Nullable String countryIso) { - blockNumber(listener, null, number, countryIso); - } - - /** - * Add a number manually blocked by the user. - */ - public void blockNumber( - final OnBlockNumberListener listener, - @Nullable String normalizedNumber, - String number, - @Nullable String countryIso) { - blockNumber(listener, FilteredNumberCompat.newBlockNumberContentValues(number, - normalizedNumber, countryIso)); - } - - /** - * Block a number with specified ContentValues. Can be manually added or a restored row - * from performing the 'undo' action after unblocking. - */ - public void blockNumber(final OnBlockNumberListener listener, ContentValues values) { - startInsert(NO_TOKEN, - new Listener() { - @Override - public void onInsertComplete(int token, Object cookie, Uri uri) { - if (listener != null ) { - listener.onBlockComplete(uri); - } - } - }, FilteredNumberCompat.getContentUri(null), values); - } - - /** - * Unblocks the number with the given id. - * - * @param listener (optional) The {@link OnUnblockNumberListener} called after the number is - * unblocked. - * @param id The id of the number to unblock. - */ - public void unblock(@Nullable final OnUnblockNumberListener listener, Integer id) { - if (id == null) { - throw new IllegalArgumentException("Null id passed into unblock"); - } - unblock(listener, FilteredNumberCompat.getContentUri(id)); - } - - /** - * Removes row from database. - * @param listener (optional) The {@link OnUnblockNumberListener} called after the number is - * unblocked. - * @param uri The uri of row to remove, from - * {@link FilteredNumberAsyncQueryHandler#blockNumber}. - */ - public void unblock(@Nullable final OnUnblockNumberListener listener, final Uri uri) { - startQuery(NO_TOKEN, new Listener() { - @Override - public void onQueryComplete(int token, Object cookie, Cursor cursor) { - int rowsReturned = cursor == null ? 0 : cursor.getCount(); - if (rowsReturned != 1) { - throw new SQLiteDatabaseCorruptException - ("Returned " + rowsReturned + " rows for uri " - + uri + "where 1 expected."); - } - cursor.moveToFirst(); - final ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, values); - values.remove(FilteredNumberCompat.getIdColumnName()); - - startDelete(NO_TOKEN, new Listener() { - @Override - public void onDeleteComplete(int token, Object cookie, int result) { - if (listener != null) { - listener.onUnblockComplete(result, values); - } - } - }, uri, null, null); - } - }, uri, null, null, null, null); - } -} diff --git a/src/com/android/dialer/database/FilteredNumberContract.java b/src/com/android/dialer/database/FilteredNumberContract.java deleted file mode 100644 index f3966816c..000000000 --- a/src/com/android/dialer/database/FilteredNumberContract.java +++ /dev/null @@ -1,163 +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.database; - -import android.net.Uri; -import android.provider.BaseColumns; - -import com.android.dialerbind.ObjectFactory; - -/** - * <p> - * The contract between the filtered number provider and applications. Contains - * definitions for the supported URIs and columns. - * Currently only accessible within Dialer. - * </p> - */ -public final class FilteredNumberContract { - - /** The authority for the filtered numbers provider */ - public static final String AUTHORITY = ObjectFactory.getFilteredNumberProviderAuthority(); - - /** A content:// style uri to the authority for the filtered numbers provider */ - public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); - - /** The type of filtering to be applied, e.g. block the number or whitelist the number. */ - public interface FilteredNumberTypes { - static final int UNDEFINED = 0; - /** - * Dialer will disconnect the call without sending the caller to voicemail. - */ - static final int BLOCKED_NUMBER = 1; - } - - /** The original source of the filtered number, e.g. the user manually added it. */ - public interface FilteredNumberSources { - static final int UNDEFINED = 0; - /** - * The user manually added this number through Dialer (e.g. from the call log or InCallUI). - */ - static final int USER = 1; - } - - public interface FilteredNumberColumns { - // TYPE: INTEGER - static final String _ID = "_id"; - /** - * Represents the number to be filtered, normalized to compare phone numbers for equality. - * - * TYPE: TEXT - */ - static final String NORMALIZED_NUMBER = "normalized_number"; - /** - * Represents the number to be filtered, for formatting and - * used with country iso for contact lookups. - * - * TYPE: TEXT - */ - static final String NUMBER = "number"; - /** - * The country code representing the country detected when - * the phone number was added to the database. - * Most numbers don't have the country code, so a best guess is provided by - * the country detector system. The country iso is also needed in order to format - * phone numbers correctly. - * - * TYPE: TEXT - */ - static final String COUNTRY_ISO = "country_iso"; - /** - * The number of times the number has been filtered by Dialer. - * When this number is incremented, LAST_TIME_FILTERED should also be updated to - * the current time. - * - * TYPE: INTEGER - */ - static final String TIMES_FILTERED = "times_filtered"; - /** - * Set to the current time when the phone number is filtered. - * When this is updated, TIMES_FILTERED should also be incremented. - * - * TYPE: LONG - */ - static final String LAST_TIME_FILTERED = "last_time_filtered"; - // TYPE: LONG - static final String CREATION_TIME = "creation_time"; - /** - * Indicates the type of filtering to be applied. - * - * TYPE: INTEGER - * See {@link FilteredNumberTypes} - */ - static final String TYPE = "type"; - /** - * Integer representing the original source of the filtered number. - * - * TYPE: INTEGER - * See {@link FilteredNumberSources} - */ - static final String SOURCE = "source"; - } - - /** - * <p> - * Constants for the table of filtered numbers. - * </p> - * <h3>Operations</h3> - * <dl> - * <dt><b>Insert</b></dt> - * <dd>Required fields: NUMBER, NORMALIZED_NUMBER, TYPE, SOURCE. - * A default value will be used for the other fields if left null.</dd> - * <dt><b>Update</b></dt> - * <dt><b>Delete</b></dt> - * <dt><b>Query</b></dt> - * <dd>{@link #CONTENT_URI} can be used for any query, append an ID to - * retrieve a specific filtered number entry.</dd> - * </dl> - */ - public static class FilteredNumber implements BaseColumns { - - public static final String FILTERED_NUMBERS_TABLE = "filtered_numbers_table"; - public static final String FILTERED_NUMBERS_INCREMENT_FILTERED_COUNT = - "filtered_numbers_increment_filtered_count"; - - public static final Uri CONTENT_URI = Uri.withAppendedPath( - AUTHORITY_URI, - FILTERED_NUMBERS_TABLE); - - public static final Uri CONTENT_URI_INCREMENT_FILTERED_COUNT = Uri.withAppendedPath( - AUTHORITY_URI, - FILTERED_NUMBERS_INCREMENT_FILTERED_COUNT); - - /** - * This utility class cannot be instantiated. - */ - private FilteredNumber () {} - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * filtered numbers. - */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/filtered_numbers_table"; - - /** - * The MIME type of a {@link #CONTENT_URI} single filtered number. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/filtered_numbers_table"; - } -} diff --git a/src/com/android/dialer/database/FilteredNumberProvider.java b/src/com/android/dialer/database/FilteredNumberProvider.java deleted file mode 100644 index 3b63d4b50..000000000 --- a/src/com/android/dialer/database/FilteredNumberProvider.java +++ /dev/null @@ -1,211 +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.database; - -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.Binder; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.GeoUtil; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialerbind.DatabaseHelperManager; -import com.android.dialerbind.ObjectFactory; -import com.google.common.annotations.VisibleForTesting; - -import java.util.Arrays; - -/** - * Filtered number content provider. - */ -public class FilteredNumberProvider extends ContentProvider { - - private static String TAG = FilteredNumberProvider.class.getSimpleName(); - - private DialerDatabaseHelper mDialerDatabaseHelper; - - private static final int FILTERED_NUMBERS_TABLE = 1; - private static final int FILTERED_NUMBERS_TABLE_ID = 2; - private static final int FILTERED_NUMBERS_INCREMENT_FILTERED_COUNT = 3; - - private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - @Override - public boolean onCreate() { - mDialerDatabaseHelper = getDatabaseHelper(getContext()); - if (mDialerDatabaseHelper == null) { - return false; - } - sUriMatcher.addURI(ObjectFactory.getFilteredNumberProviderAuthority(), - FilteredNumberContract.FilteredNumber.FILTERED_NUMBERS_TABLE, - FILTERED_NUMBERS_TABLE); - sUriMatcher.addURI(ObjectFactory.getFilteredNumberProviderAuthority(), - FilteredNumberContract.FilteredNumber.FILTERED_NUMBERS_TABLE + "/#", - FILTERED_NUMBERS_TABLE_ID); - sUriMatcher.addURI(ObjectFactory.getFilteredNumberProviderAuthority(), - FilteredNumberContract.FilteredNumber.FILTERED_NUMBERS_INCREMENT_FILTERED_COUNT - + "/#", - FILTERED_NUMBERS_INCREMENT_FILTERED_COUNT); - return true; - } - - @VisibleForTesting - protected DialerDatabaseHelper getDatabaseHelper(Context context) { - return DatabaseHelperManager.getDatabaseHelper(context); - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - final SQLiteDatabase db = mDialerDatabaseHelper.getReadableDatabase(); - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(DialerDatabaseHelper.Tables.FILTERED_NUMBER_TABLE); - final int match = sUriMatcher.match(uri); - switch (match) { - case FILTERED_NUMBERS_TABLE: - break; - case FILTERED_NUMBERS_TABLE_ID: - qb.appendWhere(FilteredNumberColumns._ID + "=" + ContentUris.parseId(uri)); - break; - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - final Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null); - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), - FilteredNumberContract.FilteredNumber.CONTENT_URI); - } else { - Log.d(TAG, "CURSOR WAS NULL"); - } - return c; - } - - @Override - public String getType(Uri uri) { - return FilteredNumberContract.FilteredNumber.CONTENT_ITEM_TYPE; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mDialerDatabaseHelper.getWritableDatabase(); - setDefaultValues(values); - long id = db.insert(DialerDatabaseHelper.Tables.FILTERED_NUMBER_TABLE, null, values); - if (id < 0) { - return null; - } - notifyChange(uri); - return ContentUris.withAppendedId(uri, id); - } - - @VisibleForTesting - protected long getCurrentTimeMs() { - return System.currentTimeMillis(); - } - - private void setDefaultValues(ContentValues values) { - if (values.getAsString(FilteredNumberColumns.COUNTRY_ISO) == null) { - values.put(FilteredNumberColumns.COUNTRY_ISO, - GeoUtil.getCurrentCountryIso(getContext())); - } - if (values.getAsInteger(FilteredNumberColumns.TIMES_FILTERED) == null) { - values.put(FilteredNumberContract.FilteredNumberColumns.TIMES_FILTERED, 0); - } - if (values.getAsLong(FilteredNumberColumns.CREATION_TIME) == null) { - values.put(FilteredNumberColumns.CREATION_TIME, getCurrentTimeMs()); - } - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - SQLiteDatabase db = mDialerDatabaseHelper.getWritableDatabase(); - final int match = sUriMatcher.match(uri); - switch (match) { - case FILTERED_NUMBERS_TABLE: - break; - case FILTERED_NUMBERS_TABLE_ID: - selection = getSelectionWithId(selection, ContentUris.parseId(uri)); - break; - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - int rows = db.delete(DialerDatabaseHelper.Tables.FILTERED_NUMBER_TABLE, - selection, - selectionArgs); - if (rows > 0) { - notifyChange(uri); - } - return rows; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - SQLiteDatabase db = mDialerDatabaseHelper.getWritableDatabase(); - final int match = sUriMatcher.match(uri); - switch (match) { - case FILTERED_NUMBERS_TABLE: - break; - case FILTERED_NUMBERS_TABLE_ID: - selection = getSelectionWithId(selection, ContentUris.parseId(uri)); - break; - case FILTERED_NUMBERS_INCREMENT_FILTERED_COUNT: - final long id = ContentUris.parseId(uri); - try { - db.execSQL(" UPDATE " + DialerDatabaseHelper.Tables.FILTERED_NUMBER_TABLE - + " SET" + FilteredNumberColumns.TIMES_FILTERED + "=" - + FilteredNumberColumns.TIMES_FILTERED + "+1," - + FilteredNumberColumns.LAST_TIME_FILTERED + "=" - + getCurrentTimeMs() - + " WHERE " + FilteredNumberColumns._ID + "=" + id); - } catch (SQLException e) { - Log.d(TAG, "Could not update blocked statistics for " + id); - return 0; - } - return 1; - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - int rows = db.update(DialerDatabaseHelper.Tables.FILTERED_NUMBER_TABLE, - values, - selection, - selectionArgs); - if (rows > 0 ) { - notifyChange(uri); - } - return rows; - } - - private String getSelectionWithId(String selection, long id) { - if (TextUtils.isEmpty(selection)) { - return FilteredNumberContract.FilteredNumberColumns._ID + "=" + id; - } else { - return selection + "AND " + FilteredNumberContract.FilteredNumberColumns._ID + "=" + id; - } - } - - private void notifyChange(Uri uri) { - getContext().getContentResolver().notifyChange(uri, null); - } -} diff --git a/src/com/android/dialer/database/VoicemailArchiveContract.java b/src/com/android/dialer/database/VoicemailArchiveContract.java deleted file mode 100644 index f332932c3..000000000 --- a/src/com/android/dialer/database/VoicemailArchiveContract.java +++ /dev/null @@ -1,203 +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.database; - -import android.net.Uri; -import android.provider.BaseColumns; -import android.provider.CallLog; -import android.provider.OpenableColumns; - -import com.android.dialerbind.ObjectFactory; - -/** - * Contains definitions for the supported URIs and columns for the voicemail archive table. - * All the fields excluding MIME_TYPE, _DATA, ARCHIVED, SERVER_ID, mirror the fields in the - * contract provided in {@link CallLog.Calls}. - */ -public final class VoicemailArchiveContract { - - /** The authority used by the voicemail archive provider. */ - public static final String AUTHORITY = ObjectFactory.getVoicemailArchiveProviderAuthority(); - - /** A content:// style uri for the voicemail archive provider */ - public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); - - public static final class VoicemailArchive implements BaseColumns, OpenableColumns { - - public static final String VOICEMAIL_ARCHIVE_TABLE = "voicemail_archive_table"; - - public static final Uri CONTENT_URI = Uri.withAppendedPath( - AUTHORITY_URI, - VOICEMAIL_ARCHIVE_TABLE); - - /** - * @see android.provider.CallLog.Calls#NUMBER - * TYPE: TEXT - */ - public static final String NUMBER = CallLog.Calls.NUMBER; - - /** - * @see android.provider.CallLog.Calls#DATE - * TYPE: LONG - */ - public static final String DATE = CallLog.Calls.DATE; - - /** - * @see android.provider.CallLog.Calls#DURATION - * TYPE: LONG - */ - public static final String DURATION = CallLog.Calls.DURATION; - - /** - * The mime type of the archived voicemail file. - * TYPE: TEXT - */ - public static final String MIME_TYPE = "mime_type"; - - /** - * @see android.provider.CallLog.Calls#COUNTRY_ISO - * TYPE: LONG - */ - public static final String COUNTRY_ISO = CallLog.Calls.COUNTRY_ISO; - - /** - * The path of the archived voicemail file. - * TYPE: TEXT - */ - public static final String _DATA = "_data"; - - /** - * @see android.provider.CallLog.Calls#GEOCODED_LOCATION - * TYPE: TEXT - */ - public static final String GEOCODED_LOCATION = CallLog.Calls.GEOCODED_LOCATION; - - /** - * @see android.provider.CallLog.Calls#CACHED_NAME - * TYPE: TEXT - */ - public static final String CACHED_NAME = CallLog.Calls.CACHED_NAME; - - /** - * @see android.provider.CallLog.Calls#CACHED_NUMBER_TYPE - * TYPE: INTEGER - */ - public static final String CACHED_NUMBER_TYPE = CallLog.Calls.CACHED_NUMBER_TYPE; - - /** - * @see android.provider.CallLog.Calls#CACHED_NUMBER_LABEL - * TYPE: TEXT - */ - public static final String CACHED_NUMBER_LABEL = CallLog.Calls.CACHED_NUMBER_LABEL; - - /** - * @see android.provider.CallLog.Calls#CACHED_LOOKUP_URI - * TYPE: TEXT - */ - public static final String CACHED_LOOKUP_URI = CallLog.Calls.CACHED_LOOKUP_URI; - - /** - * @see android.provider.CallLog.Calls#CACHED_MATCHED_NUMBER - * TYPE: TEXT - */ - public static final String CACHED_MATCHED_NUMBER = CallLog.Calls.CACHED_MATCHED_NUMBER; - - /** - * @see android.provider.CallLog.Calls#CACHED_NORMALIZED_NUMBER - * TYPE: TEXT - */ - public static final String CACHED_NORMALIZED_NUMBER = - CallLog.Calls.CACHED_NORMALIZED_NUMBER; - - /** - * @see android.provider.CallLog.Calls#CACHED_PHOTO_ID - * TYPE: LONG - */ - public static final String CACHED_PHOTO_ID = CallLog.Calls.CACHED_PHOTO_ID; - - /** - * @see android.provider.CallLog.Calls#CACHED_FORMATTED_NUMBER - * TYPE: TEXT - */ - public static final String CACHED_FORMATTED_NUMBER = CallLog.Calls.CACHED_FORMATTED_NUMBER; - - /** - * If the voicemail was archived by the user by pressing the archive button, this is set to - * 1 (true). If the voicemail was archived for the purpose of forwarding to other - * applications, this is set to 0 (false). - * TYPE: INTEGER - */ - public static final String ARCHIVED = "archived_by_user"; - - /** - * @see android.provider.CallLog.Calls#NUMBER_PRESENTATION - * TYPE: INTEGER - */ - public static final String NUMBER_PRESENTATION = CallLog.Calls.NUMBER_PRESENTATION; - - /** - * @see android.provider.CallLog.Calls#PHONE_ACCOUNT_COMPONENT_NAME - * TYPE: TEXT - */ - public static final String ACCOUNT_COMPONENT_NAME = - CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME; - - /** - * @see android.provider.CallLog.Calls#PHONE_ACCOUNT_ID - * TYPE: TEXT - */ - public static final String ACCOUNT_ID = CallLog.Calls.PHONE_ACCOUNT_ID; - - /** - * @see android.provider.CallLog.Calls#FEATURES - * TYPE: INTEGER - */ - public static final String FEATURES = CallLog.Calls.FEATURES; - - /** - * The id of the voicemail on the server. - * TYPE: INTEGER - */ - public static final String SERVER_ID = "server_id"; - - /** - * @see android.provider.CallLog.Calls#TRANSCRIPTION - * TYPE: TEXT - */ - public static final String TRANSCRIPTION = CallLog.Calls.TRANSCRIPTION; - - /** - * @see android.provider.CallLog.Calls#CACHED_PHOTO_URI - * TYPE: TEXT - */ - public static final String CACHED_PHOTO_URI = CallLog.Calls.CACHED_PHOTO_URI; - - /** - * The MIME type of a {@link #CONTENT_URI} single voicemail. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/voicmail_archive_table"; - - public static final Uri buildWithId(int id) { - return Uri.withAppendedPath(CONTENT_URI, Integer.toString(id)); - } - - /** Not instantiable. */ - private VoicemailArchive() { - } - } -} diff --git a/src/com/android/dialer/database/VoicemailArchiveProvider.java b/src/com/android/dialer/database/VoicemailArchiveProvider.java deleted file mode 100644 index b3306bc4c..000000000 --- a/src/com/android/dialer/database/VoicemailArchiveProvider.java +++ /dev/null @@ -1,218 +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.database; - -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.webkit.MimeTypeMap; - -import com.android.dialerbind.DatabaseHelperManager; -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; -import java.io.FileNotFoundException; - -/** - * An implementation of the Voicemail Archive content provider. This class performs - * all database level operations on the voicemail_archive_table. - */ -public class VoicemailArchiveProvider extends ContentProvider { - private static final String TAG = "VMArchiveProvider"; - private static final int VOICEMAIL_ARCHIVE_TABLE = 1; - private static final int VOICEMAIL_ARCHIVE_TABLE_ID = 2; - private static final String VOICEMAIL_FOLDER = "voicemails"; - - private DialerDatabaseHelper mDialerDatabaseHelper; - private final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - @Override - public boolean onCreate() { - mDialerDatabaseHelper = getDatabaseHelper(getContext()); - if (mDialerDatabaseHelper == null) { - return false; - } - mUriMatcher.addURI(VoicemailArchiveContract.AUTHORITY, - VoicemailArchiveContract.VoicemailArchive.VOICEMAIL_ARCHIVE_TABLE, - VOICEMAIL_ARCHIVE_TABLE); - mUriMatcher.addURI(VoicemailArchiveContract.AUTHORITY, - VoicemailArchiveContract.VoicemailArchive.VOICEMAIL_ARCHIVE_TABLE + "/#", - VOICEMAIL_ARCHIVE_TABLE_ID); - return true; - } - - @VisibleForTesting - protected DialerDatabaseHelper getDatabaseHelper(Context context) { - return DatabaseHelperManager.getDatabaseHelper(context); - } - - /** - * Used by the test class because it extends {@link android.test.ProviderTestCase2} in which the - * {@link android.test.IsolatedContext} returns /dev/null when getFilesDir() is called. - * - * @see android.test.IsolatedContext#getFilesDir - */ - @VisibleForTesting - protected File getFilesDir() { - return getContext().getFilesDir(); - } - - @Nullable - @Override - public Cursor query(Uri uri, - @Nullable String[] projection, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String sortOrder) { - SQLiteDatabase db = mDialerDatabaseHelper.getReadableDatabase(); - SQLiteQueryBuilder queryBuilder = getQueryBuilder(uri); - Cursor cursor = queryBuilder - .query(db, projection, selection, selectionArgs, null, null, sortOrder); - if (cursor != null) { - cursor.setNotificationUri(getContext().getContentResolver(), - VoicemailArchiveContract.VoicemailArchive.CONTENT_URI); - } - return cursor; - } - - @Override - public String getType(Uri uri) { - return VoicemailArchiveContract.VoicemailArchive.CONTENT_ITEM_TYPE; - } - - @Nullable - @Override - public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mDialerDatabaseHelper.getWritableDatabase(); - long id = db.insert(DialerDatabaseHelper.Tables.VOICEMAIL_ARCHIVE_TABLE, - null, values); - if (id < 0) { - return null; - } - notifyChange(uri); - // Create the directory for archived voicemails if it doesn't already exist - File directory = new File(getFilesDir(), VOICEMAIL_FOLDER); - directory.mkdirs(); - Uri newUri = ContentUris.withAppendedId(uri, id); - - // Create new file only if path is not provided to one - if (!values.containsKey(VoicemailArchiveContract.VoicemailArchive._DATA)) { - String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType( - values.getAsString(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE)); - File voicemailFile = new File(directory, - TextUtils.isEmpty(fileExtension) ? Long.toString(id) : - id + "." + fileExtension); - values.put(VoicemailArchiveContract.VoicemailArchive._DATA, voicemailFile.getPath()); - } - update(newUri, values, null, null); - return newUri; - } - - - @Override - public int delete(Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { - SQLiteDatabase db = mDialerDatabaseHelper.getWritableDatabase(); - SQLiteQueryBuilder queryBuilder = getQueryBuilder(uri); - Cursor cursor = queryBuilder.query(db, null, selection, selectionArgs, null, null, null); - - // Delete all the voicemail files related to the selected rows - while (cursor.moveToNext()) { - deleteFile(cursor.getString(cursor.getColumnIndex( - VoicemailArchiveContract.VoicemailArchive._DATA))); - } - - int rows = db.delete(DialerDatabaseHelper.Tables.VOICEMAIL_ARCHIVE_TABLE, - getSelectionWithId(selection, uri), - selectionArgs); - if (rows > 0) { - notifyChange(uri); - } - return rows; - } - - @Override - public int update(Uri uri, - ContentValues values, - @Nullable String selection, - @Nullable String[] selectionArgs) { - SQLiteDatabase db = mDialerDatabaseHelper.getWritableDatabase(); - selection = getSelectionWithId(selection, uri); - int rows = db.update(DialerDatabaseHelper.Tables.VOICEMAIL_ARCHIVE_TABLE, - values, - selection, - selectionArgs); - if (rows > 0) { - notifyChange(uri); - } - return rows; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - if (mUriMatcher.match(uri) != VOICEMAIL_ARCHIVE_TABLE_ID) { - throw new IllegalArgumentException("URI Invalid."); - } - return openFileHelper(uri, mode); - } - - private void deleteFile(@Nullable String path) { - if (TextUtils.isEmpty(path)) { - return; - } - File file = new File(path); - if (file.exists()) { - file.delete(); - } - } - - private SQLiteQueryBuilder getQueryBuilder(Uri uri) { - SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(DialerDatabaseHelper.Tables.VOICEMAIL_ARCHIVE_TABLE); - String selectionWithId = getSelectionWithId(null, uri); - if (!TextUtils.isEmpty(selectionWithId)) { - queryBuilder.appendWhere(selectionWithId); - } - return queryBuilder; - } - - private String getSelectionWithId(String selection, Uri uri) { - int match = mUriMatcher.match(uri); - switch (match) { - case VOICEMAIL_ARCHIVE_TABLE: - return selection; - case VOICEMAIL_ARCHIVE_TABLE_ID: - String idStr = VoicemailArchiveContract.VoicemailArchive._ID + "=" + - ContentUris.parseId(uri); - return TextUtils.isEmpty(selection) ? idStr : selection + " AND " + idStr; - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - } - - private void notifyChange(Uri uri) { - getContext().getContentResolver().notifyChange(uri, null); - } -} diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java deleted file mode 100644 index 55d534676..000000000 --- a/src/com/android/dialer/dialpad/DialpadFragment.java +++ /dev/null @@ -1,1695 +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.dialpad; - -import com.google.common.annotations.VisibleForTesting; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.AudioManager; -import android.media.ToneGenerator; -import android.net.Uri; -import android.os.Bundle; -import android.os.Trace; -import android.provider.Contacts.People; -import android.provider.Contacts.Phones; -import android.provider.Contacts.PhonesColumns; -import android.provider.Settings; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.util.Log; -import android.view.HapticFeedbackConstants; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.PopupMenu; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.dialog.CallSubjectDialog; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.common.util.PhoneNumberFormatter; -import com.android.contacts.common.util.StopWatch; -import com.android.contacts.common.widget.FloatingActionButtonController; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.NeededForReflection; -import com.android.dialer.R; -import com.android.dialer.SpecialCharSequenceMgr; -import com.android.dialer.calllog.PhoneAccountUtils; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil.CallIntentBuilder; -import com.android.dialer.util.TelecomUtil; -import com.android.incallui.Call.LogState; -import com.android.phone.common.CallLogAsync; -import com.android.phone.common.animation.AnimUtils; -import com.android.phone.common.dialpad.DialpadKeyButton; -import com.android.phone.common.dialpad.DialpadView; - -import java.util.HashSet; -import java.util.List; - -/** - * Fragment that displays a twelve-key phone dialpad. - */ -public class DialpadFragment extends Fragment - implements View.OnClickListener, - View.OnLongClickListener, View.OnKeyListener, - AdapterView.OnItemClickListener, TextWatcher, - PopupMenu.OnMenuItemClickListener, - DialpadKeyButton.OnPressedListener { - private static final String TAG = "DialpadFragment"; - - /** - * LinearLayout with getter and setter methods for the translationY property using floats, - * for animation purposes. - */ - public static class DialpadSlidingRelativeLayout extends RelativeLayout { - - public DialpadSlidingRelativeLayout(Context context) { - super(context); - } - - public DialpadSlidingRelativeLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public DialpadSlidingRelativeLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @NeededForReflection - public float getYFraction() { - final int height = getHeight(); - if (height == 0) return 0; - return getTranslationY() / height; - } - - @NeededForReflection - public void setYFraction(float yFraction) { - setTranslationY(yFraction * getHeight()); - } - } - - public interface OnDialpadQueryChangedListener { - void onDialpadQueryChanged(String query); - } - - public interface HostInterface { - /** - * Notifies the parent activity that the space above the dialpad has been tapped with - * no query in the dialpad present. In most situations this will cause the dialpad to - * be dismissed, unless there happens to be content showing. - */ - boolean onDialpadSpacerTouchWithEmptyQuery(); - } - - private static final boolean DEBUG = DialtactsActivity.DEBUG; - - // This is the amount of screen the dialpad fragment takes up when fully displayed - private static final float DIALPAD_SLIDE_FRACTION = 0.67f; - - private static final String EMPTY_NUMBER = ""; - private static final char PAUSE = ','; - private static final char WAIT = ';'; - - /** The length of DTMF tones in milliseconds */ - private static final int TONE_LENGTH_MS = 150; - private static final int TONE_LENGTH_INFINITE = -1; - - /** The DTMF tone volume relative to other sounds in the stream */ - private static final int TONE_RELATIVE_VOLUME = 80; - - /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */ - private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF; - - - private OnDialpadQueryChangedListener mDialpadQueryListener; - - private DialpadView mDialpadView; - private EditText mDigits; - private int mDialpadSlideInDuration; - - /** Remembers if we need to clear digits field when the screen is completely gone. */ - private boolean mClearDigitsOnStop; - - private View mOverflowMenuButton; - private PopupMenu mOverflowPopupMenu; - private View mDelete; - private ToneGenerator mToneGenerator; - private final Object mToneGeneratorLock = new Object(); - private View mSpacer; - - private FloatingActionButtonController mFloatingActionButtonController; - - /** - * Set of dialpad keys that are currently being pressed - */ - private final HashSet<View> mPressedDialpadKeys = new HashSet<View>(12); - - private ListView mDialpadChooser; - private DialpadChooserAdapter mDialpadChooserAdapter; - - /** - * Regular expression prohibiting manual phone call. Can be empty, which means "no rule". - */ - private String mProhibitedPhoneNumberRegexp; - - private PseudoEmergencyAnimator mPseudoEmergencyAnimator; - - // Last number dialed, retrieved asynchronously from the call DB - // in onCreate. This number is displayed when the user hits the - // send key and cleared in onPause. - private final CallLogAsync mCallLog = new CallLogAsync(); - private String mLastNumberDialed = EMPTY_NUMBER; - - // determines if we want to playback local DTMF tones. - private boolean mDTMFToneEnabled; - - /** Identifier for the "Add Call" intent extra. */ - private static final String ADD_CALL_MODE_KEY = "add_call_mode"; - - /** - * Identifier for intent extra for sending an empty Flash message for - * CDMA networks. This message is used by the network to simulate a - * press/depress of the "hookswitch" of a landline phone. Aka "empty flash". - * - * TODO: Using an intent extra to tell the phone to send this flash is a - * temporary measure. To be replaced with an Telephony/TelecomManager call in the future. - * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java - * in Phone app until this is replaced with the Telephony/Telecom API. - */ - private static final String EXTRA_SEND_EMPTY_FLASH - = "com.android.phone.extra.SEND_EMPTY_FLASH"; - - private String mCurrentCountryIso; - - private CallStateReceiver mCallStateReceiver; - - private class CallStateReceiver extends BroadcastReceiver { - /** - * Receive call state changes so that we can take down the - * "dialpad chooser" if the phone becomes idle while the - * chooser UI is visible. - */ - @Override - public void onReceive(Context context, Intent intent) { - // Log.i(TAG, "CallStateReceiver.onReceive"); - String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); - if ((TextUtils.equals(state, TelephonyManager.EXTRA_STATE_IDLE) || - TextUtils.equals(state, TelephonyManager.EXTRA_STATE_OFFHOOK)) - && isDialpadChooserVisible()) { - // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down..."); - // Note there's a race condition in the UI here: the - // dialpad chooser could conceivably disappear (on its - // own) at the exact moment the user was trying to select - // one of the choices, which would be confusing. (But at - // least that's better than leaving the dialpad chooser - // onscreen, but useless...) - showDialpadChooser(false); - } - } - } - - private boolean mWasEmptyBeforeTextChange; - - /** - * This field is set to true while processing an incoming DIAL intent, in order to make sure - * that SpecialCharSequenceMgr actions can be triggered by user input but *not* by a - * tel: URI passed by some other app. It will be set to false when all digits are cleared. - */ - private boolean mDigitsFilledByIntent; - - private boolean mStartedFromNewIntent = false; - private boolean mFirstLaunch = false; - private boolean mAnimate = false; - - private static final String PREF_DIGITS_FILLED_BY_INTENT = "pref_digits_filled_by_intent"; - - private TelephonyManager getTelephonyManager() { - return (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); - } - - @Override - public Context getContext() { - return getActivity(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - mWasEmptyBeforeTextChange = TextUtils.isEmpty(s); - } - - @Override - public void onTextChanged(CharSequence input, int start, int before, int changeCount) { - if (mWasEmptyBeforeTextChange != TextUtils.isEmpty(input)) { - final Activity activity = getActivity(); - if (activity != null) { - activity.invalidateOptionsMenu(); - updateMenuOverflowButton(mWasEmptyBeforeTextChange); - } - } - - // DTMF Tones do not need to be played here any longer - - // the DTMF dialer handles that functionality now. - } - - @Override - public void afterTextChanged(Editable input) { - // When DTMF dialpad buttons are being pressed, we delay SpecialCharSequenceMgr sequence, - // since some of SpecialCharSequenceMgr's behavior is too abrupt for the "touch-down" - // behavior. - if (!mDigitsFilledByIntent && - SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) { - // A special sequence was entered, clear the digits - mDigits.getText().clear(); - } - - if (isDigitsEmpty()) { - mDigitsFilledByIntent = false; - mDigits.setCursorVisible(false); - } - - if (mDialpadQueryListener != null) { - mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString()); - } - - updateDeleteButtonEnabledState(); - } - - @Override - public void onCreate(Bundle state) { - Trace.beginSection(TAG + " onCreate"); - super.onCreate(state); - - mFirstLaunch = state == null; - - mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); - - mProhibitedPhoneNumberRegexp = getResources().getString( - R.string.config_prohibited_phone_number_regexp); - - if (state != null) { - mDigitsFilledByIntent = state.getBoolean(PREF_DIGITS_FILLED_BY_INTENT); - } - - mDialpadSlideInDuration = getResources().getInteger(R.integer.dialpad_slide_in_duration); - - if (mCallStateReceiver == null) { - IntentFilter callStateIntentFilter = new IntentFilter( - TelephonyManager.ACTION_PHONE_STATE_CHANGED); - mCallStateReceiver = new CallStateReceiver(); - ((Context) getActivity()).registerReceiver(mCallStateReceiver, callStateIntentFilter); - } - Trace.endSection(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { - Trace.beginSection(TAG + " onCreateView"); - Trace.beginSection(TAG + " inflate view"); - final View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, - false); - Trace.endSection(); - Trace.beginSection(TAG + " buildLayer"); - fragmentView.buildLayer(); - Trace.endSection(); - - Trace.beginSection(TAG + " setup views"); - - mDialpadView = (DialpadView) fragmentView.findViewById(R.id.dialpad_view); - mDialpadView.setCanDigitsBeEdited(true); - mDigits = mDialpadView.getDigits(); - mDigits.setKeyListener(UnicodeDialerKeyListener.INSTANCE); - mDigits.setOnClickListener(this); - mDigits.setOnKeyListener(this); - mDigits.setOnLongClickListener(this); - mDigits.addTextChangedListener(this); - mDigits.setElegantTextHeight(false); - PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits); - // Check for the presence of the keypad - View oneButton = fragmentView.findViewById(R.id.one); - if (oneButton != null) { - configureKeypadListeners(fragmentView); - } - - mDelete = mDialpadView.getDeleteButton(); - - if (mDelete != null) { - mDelete.setOnClickListener(this); - mDelete.setOnLongClickListener(this); - } - - mSpacer = fragmentView.findViewById(R.id.spacer); - mSpacer.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (isDigitsEmpty()) { - if (getActivity() != null) { - return ((HostInterface) getActivity()).onDialpadSpacerTouchWithEmptyQuery(); - } - return true; - } - return false; - } - }); - - mDigits.setCursorVisible(false); - - // Set up the "dialpad chooser" UI; see showDialpadChooser(). - mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser); - mDialpadChooser.setOnItemClickListener(this); - - final View floatingActionButtonContainer = - fragmentView.findViewById(R.id.dialpad_floating_action_button_container); - final ImageButton floatingActionButton = - (ImageButton) fragmentView.findViewById(R.id.dialpad_floating_action_button); - floatingActionButton.setOnClickListener(this); - mFloatingActionButtonController = new FloatingActionButtonController(getActivity(), - floatingActionButtonContainer, floatingActionButton); - Trace.endSection(); - Trace.endSection(); - return fragmentView; - } - - private boolean isLayoutReady() { - return mDigits != null; - } - - @VisibleForTesting - public EditText getDigitsWidget() { - return mDigits; - } - - /** - * @return true when {@link #mDigits} is actually filled by the Intent. - */ - private boolean fillDigitsIfNecessary(Intent intent) { - // Only fills digits from an intent if it is a new intent. - // Otherwise falls back to the previously used number. - if (!mFirstLaunch && !mStartedFromNewIntent) { - return false; - } - - final String action = intent.getAction(); - if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) { - Uri uri = intent.getData(); - if (uri != null) { - if (PhoneAccount.SCHEME_TEL.equals(uri.getScheme())) { - // Put the requested number into the input area - String data = uri.getSchemeSpecificPart(); - // Remember it is filled via Intent. - mDigitsFilledByIntent = true; - final String converted = PhoneNumberUtils.convertKeypadLettersToDigits( - PhoneNumberUtils.replaceUnicodeDigits(data)); - setFormattedDigits(converted, null); - return true; - } else { - if (!PermissionsUtil.hasContactsPermissions(getActivity())) { - return false; - } - String type = intent.getType(); - if (People.CONTENT_ITEM_TYPE.equals(type) - || Phones.CONTENT_ITEM_TYPE.equals(type)) { - // Query the phone number - Cursor c = getActivity().getContentResolver().query(intent.getData(), - new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY}, - null, null, null); - if (c != null) { - try { - if (c.moveToFirst()) { - // Remember it is filled via Intent. - mDigitsFilledByIntent = true; - // Put the number into the input area - setFormattedDigits(c.getString(0), c.getString(1)); - return true; - } - } finally { - c.close(); - } - } - } - } - } - } - return false; - } - - /** - * Determines whether an add call operation is requested. - * - * @param intent The intent. - * @return {@literal true} if add call operation was requested. {@literal false} otherwise. - */ - public static boolean isAddCallMode(Intent intent) { - if (intent == null) { - return false; - } - final String action = intent.getAction(); - if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) { - // see if we are "adding a call" from the InCallScreen; false by default. - return intent.getBooleanExtra(ADD_CALL_MODE_KEY, false); - } else { - return false; - } - } - - /** - * Checks the given Intent and changes dialpad's UI state. For example, if the Intent requires - * the screen to enter "Add Call" mode, this method will show correct UI for the mode. - */ - private void configureScreenFromIntent(Activity parent) { - // If we were not invoked with a DIAL intent, - if (!(parent instanceof DialtactsActivity)) { - setStartedFromNewIntent(false); - return; - } - // See if we were invoked with a DIAL intent. If we were, fill in the appropriate - // digits in the dialer field. - Intent intent = parent.getIntent(); - - if (!isLayoutReady()) { - // This happens typically when parent's Activity#onNewIntent() is called while - // Fragment#onCreateView() isn't called yet, and thus we cannot configure Views at - // this point. onViewCreate() should call this method after preparing layouts, so - // just ignore this call now. - Log.i(TAG, - "Screen configuration is requested before onCreateView() is called. Ignored"); - return; - } - - boolean needToShowDialpadChooser = false; - - // Be sure *not* to show the dialpad chooser if this is an - // explicit "Add call" action, though. - final boolean isAddCallMode = isAddCallMode(intent); - if (!isAddCallMode) { - - // Don't show the chooser when called via onNewIntent() and phone number is present. - // i.e. User clicks a telephone link from gmail for example. - // In this case, we want to show the dialpad with the phone number. - final boolean digitsFilled = fillDigitsIfNecessary(intent); - if (!(mStartedFromNewIntent && digitsFilled)) { - - final String action = intent.getAction(); - if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action) - || Intent.ACTION_MAIN.equals(action)) { - // If there's already an active call, bring up an intermediate UI to - // make the user confirm what they really want to do. - if (isPhoneInUse()) { - needToShowDialpadChooser = true; - } - } - - } - } - showDialpadChooser(needToShowDialpadChooser); - setStartedFromNewIntent(false); - } - - public void setStartedFromNewIntent(boolean value) { - mStartedFromNewIntent = value; - } - - public void clearCallRateInformation() { - setCallRateInformation(null, null); - } - - public void setCallRateInformation(String countryName, String displayRate) { - mDialpadView.setCallRateInformation(countryName, displayRate); - } - - /** - * Sets formatted digits to digits field. - */ - private void setFormattedDigits(String data, String normalizedNumber) { - final String formatted = getFormattedDigits(data, normalizedNumber, mCurrentCountryIso); - if (!TextUtils.isEmpty(formatted)) { - Editable digits = mDigits.getText(); - digits.replace(0, digits.length(), formatted); - // for some reason this isn't getting called in the digits.replace call above.. - // but in any case, this will make sure the background drawable looks right - afterTextChanged(digits); - } - } - - /** - * Format the provided string of digits into one that represents a properly formatted phone - * number. - * - * @param dialString String of characters to format - * @param normalizedNumber the E164 format number whose country code is used if the given - * phoneNumber doesn't have the country code. - * @param countryIso The country code representing the format to use if the provided normalized - * number is null or invalid. - * @return the provided string of digits as a formatted phone number, retaining any - * post-dial portion of the string. - */ - @VisibleForTesting - static String getFormattedDigits(String dialString, String normalizedNumber, String countryIso) { - String number = PhoneNumberUtils.extractNetworkPortion(dialString); - // Also retrieve the post dial portion of the provided data, so that the entire dial - // string can be reconstituted later. - final String postDial = PhoneNumberUtils.extractPostDialPortion(dialString); - - if (TextUtils.isEmpty(number)) { - return postDial; - } - - number = PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso); - - if (TextUtils.isEmpty(postDial)) { - return number; - } - - return number.concat(postDial); - } - - private void configureKeypadListeners(View fragmentView) { - final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five, - R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound}; - - DialpadKeyButton dialpadKey; - - for (int i = 0; i < buttonIds.length; i++) { - dialpadKey = (DialpadKeyButton) fragmentView.findViewById(buttonIds[i]); - dialpadKey.setOnPressedListener(this); - } - - // Long-pressing one button will initiate Voicemail. - final DialpadKeyButton one = (DialpadKeyButton) fragmentView.findViewById(R.id.one); - one.setOnLongClickListener(this); - - // Long-pressing zero button will enter '+' instead. - final DialpadKeyButton zero = (DialpadKeyButton) fragmentView.findViewById(R.id.zero); - zero.setOnLongClickListener(this); - } - - @Override - public void onStart() { - Trace.beginSection(TAG + " onStart"); - super.onStart(); - // if the mToneGenerator creation fails, just continue without it. It is - // a local audio signal, and is not as important as the dtmf tone itself. - final long start = System.currentTimeMillis(); - synchronized (mToneGeneratorLock) { - if (mToneGenerator == null) { - try { - mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME); - } catch (RuntimeException e) { - Log.w(TAG, "Exception caught while creating local tone generator: " + e); - mToneGenerator = null; - } - } - } - final long total = System.currentTimeMillis() - start; - if (total > 50) { - Log.i(TAG, "Time for ToneGenerator creation: " + total); - } - Trace.endSection(); - }; - - @Override - public void onResume() { - Trace.beginSection(TAG + " onResume"); - super.onResume(); - - final DialtactsActivity activity = (DialtactsActivity) getActivity(); - mDialpadQueryListener = activity; - - final StopWatch stopWatch = StopWatch.start("Dialpad.onResume"); - - // Query the last dialed number. Do it first because hitting - // the DB is 'slow'. This call is asynchronous. - queryLastOutgoingCall(); - - stopWatch.lap("qloc"); - - final ContentResolver contentResolver = activity.getContentResolver(); - - // retrieve the DTMF tone play back setting. - mDTMFToneEnabled = Settings.System.getInt(contentResolver, - Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; - - stopWatch.lap("dtwd"); - - stopWatch.lap("hptc"); - - mPressedDialpadKeys.clear(); - - configureScreenFromIntent(getActivity()); - - stopWatch.lap("fdin"); - - if (!isPhoneInUse()) { - // A sanity-check: the "dialpad chooser" UI should not be visible if the phone is idle. - showDialpadChooser(false); - } - - stopWatch.lap("hnt"); - - updateDeleteButtonEnabledState(); - - stopWatch.lap("bes"); - - stopWatch.stopAndLog(TAG, 50); - - // Populate the overflow menu in onResume instead of onCreate, so that if the SMS activity - // is disabled while Dialer is paused, the "Send a text message" option can be correctly - // removed when resumed. - mOverflowMenuButton = mDialpadView.getOverflowMenuButton(); - mOverflowPopupMenu = buildOptionsMenu(mOverflowMenuButton); - mOverflowMenuButton.setOnTouchListener(mOverflowPopupMenu.getDragToOpenListener()); - mOverflowMenuButton.setOnClickListener(this); - mOverflowMenuButton.setVisibility(isDigitsEmpty() ? View.INVISIBLE : View.VISIBLE); - - if (mFirstLaunch) { - // The onHiddenChanged callback does not get called the first time the fragment is - // attached, so call it ourselves here. - onHiddenChanged(false); - } - - mFirstLaunch = false; - Trace.endSection(); - } - - @Override - public void onPause() { - super.onPause(); - - // Make sure we don't leave this activity with a tone still playing. - stopTone(); - mPressedDialpadKeys.clear(); - - // TODO: I wonder if we should not check if the AsyncTask that - // lookup the last dialed number has completed. - mLastNumberDialed = EMPTY_NUMBER; // Since we are going to query again, free stale number. - - SpecialCharSequenceMgr.cleanup(); - } - - @Override - public void onStop() { - super.onStop(); - - synchronized (mToneGeneratorLock) { - if (mToneGenerator != null) { - mToneGenerator.release(); - mToneGenerator = null; - } - } - - if (mClearDigitsOnStop) { - mClearDigitsOnStop = false; - clearDialpad(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(PREF_DIGITS_FILLED_BY_INTENT, mDigitsFilledByIntent); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mPseudoEmergencyAnimator != null) { - mPseudoEmergencyAnimator.destroy(); - mPseudoEmergencyAnimator = null; - } - ((Context) getActivity()).unregisterReceiver(mCallStateReceiver); - } - - private void keyPressed(int keyCode) { - if (getView() == null || getView().getTranslationY() != 0) { - return; - } - switch (keyCode) { - case KeyEvent.KEYCODE_1: - playTone(ToneGenerator.TONE_DTMF_1, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_2: - playTone(ToneGenerator.TONE_DTMF_2, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_3: - playTone(ToneGenerator.TONE_DTMF_3, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_4: - playTone(ToneGenerator.TONE_DTMF_4, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_5: - playTone(ToneGenerator.TONE_DTMF_5, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_6: - playTone(ToneGenerator.TONE_DTMF_6, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_7: - playTone(ToneGenerator.TONE_DTMF_7, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_8: - playTone(ToneGenerator.TONE_DTMF_8, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_9: - playTone(ToneGenerator.TONE_DTMF_9, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_0: - playTone(ToneGenerator.TONE_DTMF_0, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_POUND: - playTone(ToneGenerator.TONE_DTMF_P, TONE_LENGTH_INFINITE); - break; - case KeyEvent.KEYCODE_STAR: - playTone(ToneGenerator.TONE_DTMF_S, TONE_LENGTH_INFINITE); - break; - default: - break; - } - - getView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); - mDigits.onKeyDown(keyCode, event); - - // If the cursor is at the end of the text we hide it. - final int length = mDigits.length(); - if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) { - mDigits.setCursorVisible(false); - } - } - - @Override - public boolean onKey(View view, int keyCode, KeyEvent event) { - if (view.getId() == R.id.digits) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - handleDialButtonPressed(); - return true; - } - - } - return false; - } - - /** - * When a key is pressed, we start playing DTMF tone, do vibration, and enter the digit - * immediately. When a key is released, we stop the tone. Note that the "key press" event will - * be delivered by the system with certain amount of delay, it won't be synced with user's - * actual "touch-down" behavior. - */ - @Override - public void onPressed(View view, boolean pressed) { - if (DEBUG) Log.d(TAG, "onPressed(). view: " + view + ", pressed: " + pressed); - if (pressed) { - int resId = view.getId(); - if (resId == R.id.one) { - keyPressed(KeyEvent.KEYCODE_1); - } else if (resId == R.id.two) { - keyPressed(KeyEvent.KEYCODE_2); - } else if (resId == R.id.three) { - keyPressed(KeyEvent.KEYCODE_3); - } else if (resId == R.id.four) { - keyPressed(KeyEvent.KEYCODE_4); - } else if (resId == R.id.five) { - keyPressed(KeyEvent.KEYCODE_5); - } else if (resId == R.id.six) { - keyPressed(KeyEvent.KEYCODE_6); - } else if (resId == R.id.seven) { - keyPressed(KeyEvent.KEYCODE_7); - } else if (resId == R.id.eight) { - keyPressed(KeyEvent.KEYCODE_8); - } else if (resId == R.id.nine) { - keyPressed(KeyEvent.KEYCODE_9); - } else if (resId == R.id.zero) { - keyPressed(KeyEvent.KEYCODE_0); - } else if (resId == R.id.pound) { - keyPressed(KeyEvent.KEYCODE_POUND); - } else if (resId == R.id.star) { - keyPressed(KeyEvent.KEYCODE_STAR); - } else { - Log.wtf(TAG, "Unexpected onTouch(ACTION_DOWN) event from: " + view); - } - mPressedDialpadKeys.add(view); - } else { - mPressedDialpadKeys.remove(view); - if (mPressedDialpadKeys.isEmpty()) { - stopTone(); - } - } - } - - /** - * Called by the containing Activity to tell this Fragment to build an overflow options - * menu for display by the container when appropriate. - * - * @param invoker the View that invoked the options menu, to act as an anchor location. - */ - private PopupMenu buildOptionsMenu(View invoker) { - final PopupMenu popupMenu = new PopupMenu(getActivity(), invoker) { - @Override - public void show() { - final Menu menu = getMenu(); - - boolean enable = !isDigitsEmpty(); - for (int i = 0; i < menu.size(); i++) { - MenuItem item = menu.getItem(i); - item.setEnabled(enable); - if (item.getItemId() == R.id.menu_call_with_note) { - item.setVisible(CallUtil.isCallWithSubjectSupported(getContext())); - } - } - super.show(); - } - }; - popupMenu.inflate(R.menu.dialpad_options); - popupMenu.setOnMenuItemClickListener(this); - return popupMenu; - } - - @Override - public void onClick(View view) { - int resId = view.getId(); - if (resId == R.id.dialpad_floating_action_button) { - view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - handleDialButtonPressed(); - } else if (resId == R.id.deleteButton) { - keyPressed(KeyEvent.KEYCODE_DEL); - } else if (resId == R.id.digits) { - if (!isDigitsEmpty()) { - mDigits.setCursorVisible(true); - } - } else if (resId == R.id.dialpad_overflow) { - mOverflowPopupMenu.show(); - } else { - Log.wtf(TAG, "Unexpected onClick() event from: " + view); - return; - } - } - - @Override - public boolean onLongClick(View view) { - final Editable digits = mDigits.getText(); - final int id = view.getId(); - if (id == R.id.deleteButton) { - digits.clear(); - return true; - } else if (id == R.id.one) { - if (isDigitsEmpty() || TextUtils.equals(mDigits.getText(), "1")) { - // We'll try to initiate voicemail and thus we want to remove irrelevant string. - removePreviousDigitIfPossible('1'); - - List<PhoneAccountHandle> subscriptionAccountHandles = - PhoneAccountUtils.getSubscriptionPhoneAccounts(getActivity()); - boolean hasUserSelectedDefault = subscriptionAccountHandles.contains( - TelecomUtil.getDefaultOutgoingPhoneAccount(getActivity(), - PhoneAccount.SCHEME_VOICEMAIL)); - boolean needsAccountDisambiguation = subscriptionAccountHandles.size() > 1 - && !hasUserSelectedDefault; - - if (needsAccountDisambiguation || isVoicemailAvailable()) { - // On a multi-SIM phone, if the user has not selected a default - // subscription, initiate a call to voicemail so they can select an account - // from the "Call with" dialog. - callVoicemail(); - } else if (getActivity() != null) { - // Voicemail is unavailable maybe because Airplane mode is turned on. - // Check the current status and show the most appropriate error message. - final boolean isAirplaneModeOn = - Settings.System.getInt(getActivity().getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) != 0; - if (isAirplaneModeOn) { - DialogFragment dialogFragment = ErrorDialogFragment.newInstance( - R.string.dialog_voicemail_airplane_mode_message); - dialogFragment.show(getFragmentManager(), - "voicemail_request_during_airplane_mode"); - } else { - DialogFragment dialogFragment = ErrorDialogFragment.newInstance( - R.string.dialog_voicemail_not_ready_message); - dialogFragment.show(getFragmentManager(), "voicemail_not_ready"); - } - } - return true; - } - return false; - } else if (id == R.id.zero) { - if (mPressedDialpadKeys.contains(view)) { - // If the zero key is currently pressed, then the long press occurred by touch - // (and not via other means like certain accessibility input methods). - // Remove the '0' that was input when the key was first pressed. - removePreviousDigitIfPossible('0'); - } - keyPressed(KeyEvent.KEYCODE_PLUS); - stopTone(); - mPressedDialpadKeys.remove(view); - return true; - } else if (id == R.id.digits) { - mDigits.setCursorVisible(true); - return false; - } - return false; - } - - /** - * Remove the digit just before the current position of the cursor, iff the following conditions - * are true: - * 1) The cursor is not positioned at index 0. - * 2) The digit before the current cursor position matches the current digit. - * - * @param digit to remove from the digits view. - */ - private void removePreviousDigitIfPossible(char digit) { - final int currentPosition = mDigits.getSelectionStart(); - if (currentPosition > 0 && digit == mDigits.getText().charAt(currentPosition - 1)) { - mDigits.setSelection(currentPosition); - mDigits.getText().delete(currentPosition - 1, currentPosition); - } - } - - public void callVoicemail() { - DialerUtils.startActivityWithErrorToast(getActivity(), - new CallIntentBuilder(CallUtil.getVoicemailUri()) - .setCallInitiationType(LogState.INITIATION_DIALPAD) - .build()); - hideAndClearDialpad(false); - } - - private void hideAndClearDialpad(boolean animate) { - ((DialtactsActivity) getActivity()).hideDialpadFragment(animate, true); - } - - public static class ErrorDialogFragment extends DialogFragment { - private int mTitleResId; - private int mMessageResId; - - private static final String ARG_TITLE_RES_ID = "argTitleResId"; - private static final String ARG_MESSAGE_RES_ID = "argMessageResId"; - - public static ErrorDialogFragment newInstance(int messageResId) { - return newInstance(0, messageResId); - } - - public static ErrorDialogFragment newInstance(int titleResId, int messageResId) { - final ErrorDialogFragment fragment = new ErrorDialogFragment(); - final Bundle args = new Bundle(); - args.putInt(ARG_TITLE_RES_ID, titleResId); - args.putInt(ARG_MESSAGE_RES_ID, messageResId); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mTitleResId = getArguments().getInt(ARG_TITLE_RES_ID); - mMessageResId = getArguments().getInt(ARG_MESSAGE_RES_ID); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - if (mTitleResId != 0) { - builder.setTitle(mTitleResId); - } - if (mMessageResId != 0) { - builder.setMessage(mMessageResId); - } - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - } - }); - return builder.create(); - } - } - - /** - * In most cases, when the dial button is pressed, there is a - * number in digits area. Pack it in the intent, start the - * outgoing call broadcast as a separate task and finish this - * activity. - * - * When there is no digit and the phone is CDMA and off hook, - * we're sending a blank flash for CDMA. CDMA networks use Flash - * messages when special processing needs to be done, mainly for - * 3-way or call waiting scenarios. Presumably, here we're in a - * special 3-way scenario where the network needs a blank flash - * before being able to add the new participant. (This is not the - * case with all 3-way calls, just certain CDMA infrastructures.) - * - * Otherwise, there is no digit, display the last dialed - * number. Don't finish since the user may want to edit it. The - * user needs to press the dial button again, to dial it (general - * case described above). - */ - private void handleDialButtonPressed() { - if (isDigitsEmpty()) { // No number entered. - handleDialButtonClickWithEmptyDigits(); - } else { - final String number = mDigits.getText().toString(); - - // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated - // test equipment. - // TODO: clean it up. - if (number != null - && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp) - && number.matches(mProhibitedPhoneNumberRegexp)) { - Log.i(TAG, "The phone number is prohibited explicitly by a rule."); - if (getActivity() != null) { - DialogFragment dialogFragment = ErrorDialogFragment.newInstance( - R.string.dialog_phone_call_prohibited_message); - dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog"); - } - - // Clear the digits just in case. - clearDialpad(); - } else { - final Intent intent = new CallIntentBuilder(number). - setCallInitiationType(LogState.INITIATION_DIALPAD) - .build(); - DialerUtils.startActivityWithErrorToast(getActivity(), intent); - hideAndClearDialpad(false); - } - } - } - - public void clearDialpad() { - if (mDigits != null) { - mDigits.getText().clear(); - } - } - - private void handleDialButtonClickWithEmptyDigits() { - if (phoneIsCdma() && isPhoneInUse()) { - // TODO: Move this logic into services/Telephony - // - // This is really CDMA specific. On GSM is it possible - // to be off hook and wanted to add a 3rd party using - // the redial feature. - startActivity(newFlashIntent()); - } else { - if (!TextUtils.isEmpty(mLastNumberDialed)) { - // Recall the last number dialed. - mDigits.setText(mLastNumberDialed); - - // ...and move the cursor to the end of the digits string, - // so you'll be able to delete digits using the Delete - // button (just as if you had typed the number manually.) - // - // Note we use mDigits.getText().length() here, not - // mLastNumberDialed.length(), since the EditText widget now - // contains a *formatted* version of mLastNumberDialed (due to - // mTextWatcher) and its length may have changed. - mDigits.setSelection(mDigits.getText().length()); - } else { - // There's no "last number dialed" or the - // background query is still running. There's - // nothing useful for the Dial button to do in - // this case. Note: with a soft dial button, this - // can never happens since the dial button is - // disabled under these conditons. - playTone(ToneGenerator.TONE_PROP_NACK); - } - } - } - - /** - * Plays the specified tone for TONE_LENGTH_MS milliseconds. - */ - private void playTone(int tone) { - playTone(tone, TONE_LENGTH_MS); - } - - /** - * Play the specified tone for the specified milliseconds - * - * The tone is played locally, using the audio stream for phone calls. - * Tones are played only if the "Audible touch tones" user preference - * is checked, and are NOT played if the device is in silent mode. - * - * The tone length can be -1, meaning "keep playing the tone." If the caller does so, it should - * call stopTone() afterward. - * - * @param tone a tone code from {@link ToneGenerator} - * @param durationMs tone length. - */ - private void playTone(int tone, int durationMs) { - // if local tone playback is disabled, just return. - if (!mDTMFToneEnabled) { - return; - } - - // Also do nothing if the phone is in silent mode. - // We need to re-check the ringer mode for *every* playTone() - // call, rather than keeping a local flag that's updated in - // onResume(), since it's possible to toggle silent mode without - // leaving the current activity (via the ENDCALL-longpress menu.) - AudioManager audioManager = - (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); - int ringerMode = audioManager.getRingerMode(); - if ((ringerMode == AudioManager.RINGER_MODE_SILENT) - || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) { - return; - } - - synchronized (mToneGeneratorLock) { - if (mToneGenerator == null) { - Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone); - return; - } - - // Start the new tone (will stop any playing tone) - mToneGenerator.startTone(tone, durationMs); - } - } - - /** - * Stop the tone if it is played. - */ - private void stopTone() { - // if local tone playback is disabled, just return. - if (!mDTMFToneEnabled) { - return; - } - synchronized (mToneGeneratorLock) { - if (mToneGenerator == null) { - Log.w(TAG, "stopTone: mToneGenerator == null"); - return; - } - mToneGenerator.stopTone(); - } - } - - /** - * Brings up the "dialpad chooser" UI in place of the usual Dialer - * elements (the textfield/button and the dialpad underneath). - * - * We show this UI if the user brings up the Dialer while a call is - * already in progress, since there's a good chance we got here - * accidentally (and the user really wanted the in-call dialpad instead). - * So in this situation we display an intermediate UI that lets the user - * explicitly choose between the in-call dialpad ("Use touch tone - * keypad") and the regular Dialer ("Add call"). (Or, the option "Return - * to call in progress" just goes back to the in-call UI with no dialpad - * at all.) - * - * @param enabled If true, show the "dialpad chooser" instead - * of the regular Dialer UI - */ - private void showDialpadChooser(boolean enabled) { - if (getActivity() == null) { - return; - } - // Check if onCreateView() is already called by checking one of View objects. - if (!isLayoutReady()) { - return; - } - - if (enabled) { - Log.d(TAG, "Showing dialpad chooser!"); - if (mDialpadView != null) { - mDialpadView.setVisibility(View.GONE); - } - - mFloatingActionButtonController.setVisible(false); - mDialpadChooser.setVisibility(View.VISIBLE); - - // Instantiate the DialpadChooserAdapter and hook it up to the - // ListView. We do this only once. - if (mDialpadChooserAdapter == null) { - mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity()); - } - mDialpadChooser.setAdapter(mDialpadChooserAdapter); - } else { - Log.d(TAG, "Displaying normal Dialer UI."); - if (mDialpadView != null) { - mDialpadView.setVisibility(View.VISIBLE); - } else { - mDigits.setVisibility(View.VISIBLE); - } - - mFloatingActionButtonController.setVisible(true); - mDialpadChooser.setVisibility(View.GONE); - } - } - - /** - * @return true if we're currently showing the "dialpad chooser" UI. - */ - private boolean isDialpadChooserVisible() { - return mDialpadChooser.getVisibility() == View.VISIBLE; - } - - /** - * Simple list adapter, binding to an icon + text label - * for each item in the "dialpad chooser" list. - */ - private static class DialpadChooserAdapter extends BaseAdapter { - private LayoutInflater mInflater; - - // Simple struct for a single "choice" item. - static class ChoiceItem { - String text; - Bitmap icon; - int id; - - public ChoiceItem(String s, Bitmap b, int i) { - text = s; - icon = b; - id = i; - } - } - - // IDs for the possible "choices": - static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101; - static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102; - static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103; - - private static final int NUM_ITEMS = 3; - private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS]; - - public DialpadChooserAdapter(Context context) { - // Cache the LayoutInflate to avoid asking for a new one each time. - mInflater = LayoutInflater.from(context); - - // Initialize the possible choices. - // TODO: could this be specified entirely in XML? - - // - "Use touch tone keypad" - mChoiceItems[0] = new ChoiceItem( - context.getString(R.string.dialer_useDtmfDialpad), - BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_dialer_fork_tt_keypad), - DIALPAD_CHOICE_USE_DTMF_DIALPAD); - - // - "Return to call in progress" - mChoiceItems[1] = new ChoiceItem( - context.getString(R.string.dialer_returnToInCallScreen), - BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_dialer_fork_current_call), - DIALPAD_CHOICE_RETURN_TO_CALL); - - // - "Add call" - mChoiceItems[2] = new ChoiceItem( - context.getString(R.string.dialer_addAnotherCall), - BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_dialer_fork_add_call), - DIALPAD_CHOICE_ADD_NEW_CALL); - } - - @Override - public int getCount() { - return NUM_ITEMS; - } - - /** - * Return the ChoiceItem for a given position. - */ - @Override - public Object getItem(int position) { - return mChoiceItems[position]; - } - - /** - * Return a unique ID for each possible choice. - */ - @Override - public long getItemId(int position) { - return position; - } - - /** - * Make a view for each row. - */ - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // When convertView is non-null, we can reuse it (there's no need - // to reinflate it.) - if (convertView == null) { - convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null); - } - - TextView text = (TextView) convertView.findViewById(R.id.text); - text.setText(mChoiceItems[position].text); - - ImageView icon = (ImageView) convertView.findViewById(R.id.icon); - icon.setImageBitmap(mChoiceItems[position].icon); - - return convertView; - } - } - - /** - * Handle clicks from the dialpad chooser. - */ - @Override - public void onItemClick(AdapterView<?> parent, View v, int position, long id) { - DialpadChooserAdapter.ChoiceItem item = - (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position); - int itemId = item.id; - if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD) {// Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD"); - // Fire off an intent to go back to the in-call UI - // with the dialpad visible. - returnToInCallScreen(true); - } else if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL) {// Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL"); - // Fire off an intent to go back to the in-call UI - // (with the dialpad hidden). - returnToInCallScreen(false); - } else if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL) {// Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL"); - // Ok, guess the user really did want to be here (in the - // regular Dialer) after all. Bring back the normal Dialer UI. - showDialpadChooser(false); - } else { - Log.w(TAG, "onItemClick: unexpected itemId: " + itemId); - } - } - - /** - * Returns to the in-call UI (where there's presumably a call in - * progress) in response to the user selecting "use touch tone keypad" - * or "return to call" from the dialpad chooser. - */ - private void returnToInCallScreen(boolean showDialpad) { - TelecomUtil.showInCallScreen(getActivity(), showDialpad); - - // Finally, finish() ourselves so that we don't stay on the - // activity stack. - // Note that we do this whether or not the showCallScreenWithDialpad() - // call above had any effect or not! (That call is a no-op if the - // phone is idle, which can happen if the current call ends while - // the dialpad chooser is up. In this case we can't show the - // InCallScreen, and there's no point staying here in the Dialer, - // so we just take the user back where he came from...) - getActivity().finish(); - } - - /** - * @return true if the phone is "in use", meaning that at least one line - * is active (ie. off hook or ringing or dialing, or on hold). - */ - private boolean isPhoneInUse() { - final Context context = getActivity(); - if (context != null) { - return TelecomUtil.isInCall(context); - } - return false; - } - - /** - * @return true if the phone is a CDMA phone type - */ - private boolean phoneIsCdma() { - return getTelephonyManager().getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA; - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - int resId = item.getItemId(); - if (resId == R.id.menu_2s_pause) { - updateDialString(PAUSE); - return true; - } else if (resId == R.id.menu_add_wait) { - updateDialString(WAIT); - return true; - } else if (resId == R.id.menu_call_with_note) { - CallSubjectDialog.start(getActivity(), mDigits.getText().toString()); - hideAndClearDialpad(false); - return true; - } else { - return false; - } - } - - /** - * Updates the dial string (mDigits) after inserting a Pause character (,) - * or Wait character (;). - */ - private void updateDialString(char newDigit) { - if (newDigit != WAIT && newDigit != PAUSE) { - throw new IllegalArgumentException( - "Not expected for anything other than PAUSE & WAIT"); - } - - int selectionStart; - int selectionEnd; - - // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText()); - int anchor = mDigits.getSelectionStart(); - int point = mDigits.getSelectionEnd(); - - selectionStart = Math.min(anchor, point); - selectionEnd = Math.max(anchor, point); - - if (selectionStart == -1) { - selectionStart = selectionEnd = mDigits.length(); - } - - Editable digits = mDigits.getText(); - - if (canAddDigit(digits, selectionStart, selectionEnd, newDigit)) { - digits.replace(selectionStart, selectionEnd, Character.toString(newDigit)); - - if (selectionStart != selectionEnd) { - // Unselect: back to a regular cursor, just pass the character inserted. - mDigits.setSelection(selectionStart + 1); - } - } - } - - /** - * Update the enabledness of the "Dial" and "Backspace" buttons if applicable. - */ - private void updateDeleteButtonEnabledState() { - if (getActivity() == null) { - return; - } - final boolean digitsNotEmpty = !isDigitsEmpty(); - mDelete.setEnabled(digitsNotEmpty); - } - - /** - * Handle transitions for the menu button depending on the state of the digits edit text. - * Transition out when going from digits to no digits and transition in when the first digit - * is pressed. - * @param transitionIn True if transitioning in, False if transitioning out - */ - private void updateMenuOverflowButton(boolean transitionIn) { - mOverflowMenuButton = mDialpadView.getOverflowMenuButton(); - if (transitionIn) { - AnimUtils.fadeIn(mOverflowMenuButton, AnimUtils.DEFAULT_DURATION); - } else { - AnimUtils.fadeOut(mOverflowMenuButton, AnimUtils.DEFAULT_DURATION); - } - } - - /** - * Check if voicemail is enabled/accessible. - * - * @return true if voicemail is enabled and accessible. Note that this can be false - * "temporarily" after the app boot. - */ - private boolean isVoicemailAvailable() { - try { - PhoneAccountHandle defaultUserSelectedAccount = - TelecomUtil.getDefaultOutgoingPhoneAccount(getActivity(), - PhoneAccount.SCHEME_VOICEMAIL); - if (defaultUserSelectedAccount == null) { - // In a single-SIM phone, there is no default outgoing phone account selected by - // the user, so just call TelephonyManager#getVoicemailNumber directly. - return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber()); - } else { - return !TextUtils.isEmpty(TelecomUtil.getVoicemailNumber(getActivity(), - defaultUserSelectedAccount)); - } - } catch (SecurityException se) { - // Possibly no READ_PHONE_STATE privilege. - Log.w(TAG, "SecurityException is thrown. Maybe privilege isn't sufficient."); - } - return false; - } - - /** - * Returns true of the newDigit parameter can be added at the current selection - * point, otherwise returns false. - * Only prevents input of WAIT and PAUSE digits at an unsupported position. - * Fails early if start == -1 or start is larger than end. - */ - @VisibleForTesting - /* package */ static boolean canAddDigit(CharSequence digits, int start, int end, - char newDigit) { - if(newDigit != WAIT && newDigit != PAUSE) { - throw new IllegalArgumentException( - "Should not be called for anything other than PAUSE & WAIT"); - } - - // False if no selection, or selection is reversed (end < start) - if (start == -1 || end < start) { - return false; - } - - // unsupported selection-out-of-bounds state - if (start > digits.length() || end > digits.length()) return false; - - // Special digit cannot be the first digit - if (start == 0) return false; - - if (newDigit == WAIT) { - // preceding char is ';' (WAIT) - if (digits.charAt(start - 1) == WAIT) return false; - - // next char is ';' (WAIT) - if ((digits.length() > end) && (digits.charAt(end) == WAIT)) return false; - } - - return true; - } - - /** - * @return true if the widget with the phone number digits is empty. - */ - private boolean isDigitsEmpty() { - return mDigits.length() == 0; - } - - /** - * Starts the asyn query to get the last dialed/outgoing - * number. When the background query finishes, mLastNumberDialed - * is set to the last dialed number or an empty string if none - * exists yet. - */ - private void queryLastOutgoingCall() { - mLastNumberDialed = EMPTY_NUMBER; - if (!PermissionsUtil.hasPhonePermissions(getActivity())) { - return; - } - CallLogAsync.GetLastOutgoingCallArgs lastCallArgs = - new CallLogAsync.GetLastOutgoingCallArgs( - getActivity(), - new CallLogAsync.OnLastOutgoingCallComplete() { - @Override - public void lastOutgoingCall(String number) { - // TODO: Filter out emergency numbers if - // the carrier does not want redial for - // these. - // If the fragment has already been detached since the last time - // we called queryLastOutgoingCall in onResume there is no point - // doing anything here. - if (getActivity() == null) return; - mLastNumberDialed = number; - updateDeleteButtonEnabledState(); - } - }); - mCallLog.getLastOutgoingCall(lastCallArgs); - } - - private Intent newFlashIntent() { - final Intent intent = new CallIntentBuilder(EMPTY_NUMBER).build(); - intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true); - return intent; - } - - @Override - public void onHiddenChanged(boolean hidden) { - super.onHiddenChanged(hidden); - final DialtactsActivity activity = (DialtactsActivity) getActivity(); - final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view); - if (activity == null) return; - if (!hidden && !isDialpadChooserVisible()) { - if (mAnimate) { - dialpadView.animateShow(); - } - mFloatingActionButtonController.setVisible(false); - mFloatingActionButtonController.scaleIn(mAnimate ? mDialpadSlideInDuration : 0); - activity.onDialpadShown(); - mDigits.requestFocus(); - } - if (hidden) { - if (mAnimate) { - mFloatingActionButtonController.scaleOut(); - } else { - mFloatingActionButtonController.setVisible(false); - } - } - } - - public void setAnimate(boolean value) { - mAnimate = value; - } - - public boolean getAnimate() { - return mAnimate; - } - - public void setYFraction(float yFraction) { - ((DialpadSlidingRelativeLayout) getView()).setYFraction(yFraction); - } - - public int getDialpadHeight() { - if (mDialpadView == null) { - return 0; - } - return mDialpadView.getHeight(); - } - - public void process_quote_emergency_unquote(String query) { - if (PseudoEmergencyAnimator.PSEUDO_EMERGENCY_NUMBER.equals(query)) { - if (mPseudoEmergencyAnimator == null) { - mPseudoEmergencyAnimator = new PseudoEmergencyAnimator( - new PseudoEmergencyAnimator.ViewProvider() { - @Override - public View getView() { - return DialpadFragment.this.getView(); - } - }); - } - mPseudoEmergencyAnimator.start(); - } else { - if (mPseudoEmergencyAnimator != null) { - mPseudoEmergencyAnimator.end(); - } - } - } - -} diff --git a/src/com/android/dialer/dialpad/LatinSmartDialMap.java b/src/com/android/dialer/dialpad/LatinSmartDialMap.java deleted file mode 100644 index ef1ec0adc..000000000 --- a/src/com/android/dialer/dialpad/LatinSmartDialMap.java +++ /dev/null @@ -1,413 +0,0 @@ -package com.android.dialer.dialpad; - -public class LatinSmartDialMap implements SmartDialMap { - - private static final char[] LATIN_LETTERS_TO_DIGITS = { - '2', '2', '2', // A,B,C -> 2 - '3', '3', '3', // D,E,F -> 3 - '4', '4', '4', // G,H,I -> 4 - '5', '5', '5', // J,K,L -> 5 - '6', '6', '6', // M,N,O -> 6 - '7', '7', '7', '7', // P,Q,R,S -> 7 - '8', '8', '8', // T,U,V -> 8 - '9', '9', '9', '9' // W,X,Y,Z -> 9 - }; - - @Override - public boolean isValidDialpadAlphabeticChar(char ch) { - return (ch >= 'a' && ch <= 'z'); - } - - @Override - public boolean isValidDialpadNumericChar(char ch) { - return (ch >= '0' && ch <= '9'); - } - - @Override - public boolean isValidDialpadCharacter(char ch) { - return (isValidDialpadAlphabeticChar(ch) || isValidDialpadNumericChar(ch)); - } - - /* - * The switch statement in this function was generated using the python code: - * from unidecode import unidecode - * for i in range(192, 564): - * char = unichr(i) - * decoded = unidecode(char) - * # Unicode characters that decompose into multiple characters i.e. - * # into ss are not supported for now - * if (len(decoded) == 1 and decoded.isalpha()): - * print "case '" + char + "': return '" + unidecode(char) + "';" - * - * This gives us a way to map characters containing accents/diacritics to their - * alphabetic equivalents. The unidecode library can be found at: - * http://pypi.python.org/pypi/Unidecode/0.04.1 - * - * Also remaps all upper case latin characters to their lower case equivalents. - */ - @Override - public char normalizeCharacter(char ch) { - switch (ch) { - case 'À': return 'a'; - case 'Á': return 'a'; - case 'Â': return 'a'; - case 'Ã': return 'a'; - case 'Ä': return 'a'; - case 'Å': return 'a'; - case 'Ç': return 'c'; - case 'È': return 'e'; - case 'É': return 'e'; - case 'Ê': return 'e'; - case 'Ë': return 'e'; - case 'Ì': return 'i'; - case 'Í': return 'i'; - case 'Î': return 'i'; - case 'Ï': return 'i'; - case 'Ð': return 'd'; - case 'Ñ': return 'n'; - case 'Ò': return 'o'; - case 'Ó': return 'o'; - case 'Ô': return 'o'; - case 'Õ': return 'o'; - case 'Ö': return 'o'; - case '×': return 'x'; - case 'Ø': return 'o'; - case 'Ù': return 'u'; - case 'Ú': return 'u'; - case 'Û': return 'u'; - case 'Ü': return 'u'; - case 'Ý': return 'u'; - case 'à': return 'a'; - case 'á': return 'a'; - case 'â': return 'a'; - case 'ã': return 'a'; - case 'ä': return 'a'; - case 'å': return 'a'; - case 'ç': return 'c'; - case 'è': return 'e'; - case 'é': return 'e'; - case 'ê': return 'e'; - case 'ë': return 'e'; - case 'ì': return 'i'; - case 'í': return 'i'; - case 'î': return 'i'; - case 'ï': return 'i'; - case 'ð': return 'd'; - case 'ñ': return 'n'; - case 'ò': return 'o'; - case 'ó': return 'o'; - case 'ô': return 'o'; - case 'õ': return 'o'; - case 'ö': return 'o'; - case 'ø': return 'o'; - case 'ù': return 'u'; - case 'ú': return 'u'; - case 'û': return 'u'; - case 'ü': return 'u'; - case 'ý': return 'y'; - case 'ÿ': return 'y'; - case 'Ā': return 'a'; - case 'ā': return 'a'; - case 'Ă': return 'a'; - case 'ă': return 'a'; - case 'Ą': return 'a'; - case 'ą': return 'a'; - case 'Ć': return 'c'; - case 'ć': return 'c'; - case 'Ĉ': return 'c'; - case 'ĉ': return 'c'; - case 'Ċ': return 'c'; - case 'ċ': return 'c'; - case 'Č': return 'c'; - case 'č': return 'c'; - case 'Ď': return 'd'; - case 'ď': return 'd'; - case 'Đ': return 'd'; - case 'đ': return 'd'; - case 'Ē': return 'e'; - case 'ē': return 'e'; - case 'Ĕ': return 'e'; - case 'ĕ': return 'e'; - case 'Ė': return 'e'; - case 'ė': return 'e'; - case 'Ę': return 'e'; - case 'ę': return 'e'; - case 'Ě': return 'e'; - case 'ě': return 'e'; - case 'Ĝ': return 'g'; - case 'ĝ': return 'g'; - case 'Ğ': return 'g'; - case 'ğ': return 'g'; - case 'Ġ': return 'g'; - case 'ġ': return 'g'; - case 'Ģ': return 'g'; - case 'ģ': return 'g'; - case 'Ĥ': return 'h'; - case 'ĥ': return 'h'; - case 'Ħ': return 'h'; - case 'ħ': return 'h'; - case 'Ĩ': return 'i'; - case 'ĩ': return 'i'; - case 'Ī': return 'i'; - case 'ī': return 'i'; - case 'Ĭ': return 'i'; - case 'ĭ': return 'i'; - case 'Į': return 'i'; - case 'į': return 'i'; - case 'İ': return 'i'; - case 'ı': return 'i'; - case 'Ĵ': return 'j'; - case 'ĵ': return 'j'; - case 'Ķ': return 'k'; - case 'ķ': return 'k'; - case 'ĸ': return 'k'; - case 'Ĺ': return 'l'; - case 'ĺ': return 'l'; - case 'Ļ': return 'l'; - case 'ļ': return 'l'; - case 'Ľ': return 'l'; - case 'ľ': return 'l'; - case 'Ŀ': return 'l'; - case 'ŀ': return 'l'; - case 'Ł': return 'l'; - case 'ł': return 'l'; - case 'Ń': return 'n'; - case 'ń': return 'n'; - case 'Ņ': return 'n'; - case 'ņ': return 'n'; - case 'Ň': return 'n'; - case 'ň': return 'n'; - case 'Ō': return 'o'; - case 'ō': return 'o'; - case 'Ŏ': return 'o'; - case 'ŏ': return 'o'; - case 'Ő': return 'o'; - case 'ő': return 'o'; - case 'Ŕ': return 'r'; - case 'ŕ': return 'r'; - case 'Ŗ': return 'r'; - case 'ŗ': return 'r'; - case 'Ř': return 'r'; - case 'ř': return 'r'; - case 'Ś': return 's'; - case 'ś': return 's'; - case 'Ŝ': return 's'; - case 'ŝ': return 's'; - case 'Ş': return 's'; - case 'ş': return 's'; - case 'Š': return 's'; - case 'š': return 's'; - case 'Ţ': return 't'; - case 'ţ': return 't'; - case 'Ť': return 't'; - case 'ť': return 't'; - case 'Ŧ': return 't'; - case 'ŧ': return 't'; - case 'Ũ': return 'u'; - case 'ũ': return 'u'; - case 'Ū': return 'u'; - case 'ū': return 'u'; - case 'Ŭ': return 'u'; - case 'ŭ': return 'u'; - case 'Ů': return 'u'; - case 'ů': return 'u'; - case 'Ű': return 'u'; - case 'ű': return 'u'; - case 'Ų': return 'u'; - case 'ų': return 'u'; - case 'Ŵ': return 'w'; - case 'ŵ': return 'w'; - case 'Ŷ': return 'y'; - case 'ŷ': return 'y'; - case 'Ÿ': return 'y'; - case 'Ź': return 'z'; - case 'ź': return 'z'; - case 'Ż': return 'z'; - case 'ż': return 'z'; - case 'Ž': return 'z'; - case 'ž': return 'z'; - case 'ſ': return 's'; - case 'ƀ': return 'b'; - case 'Ɓ': return 'b'; - case 'Ƃ': return 'b'; - case 'ƃ': return 'b'; - case 'Ɔ': return 'o'; - case 'Ƈ': return 'c'; - case 'ƈ': return 'c'; - case 'Ɖ': return 'd'; - case 'Ɗ': return 'd'; - case 'Ƌ': return 'd'; - case 'ƌ': return 'd'; - case 'ƍ': return 'd'; - case 'Ɛ': return 'e'; - case 'Ƒ': return 'f'; - case 'ƒ': return 'f'; - case 'Ɠ': return 'g'; - case 'Ɣ': return 'g'; - case 'Ɩ': return 'i'; - case 'Ɨ': return 'i'; - case 'Ƙ': return 'k'; - case 'ƙ': return 'k'; - case 'ƚ': return 'l'; - case 'ƛ': return 'l'; - case 'Ɯ': return 'w'; - case 'Ɲ': return 'n'; - case 'ƞ': return 'n'; - case 'Ɵ': return 'o'; - case 'Ơ': return 'o'; - case 'ơ': return 'o'; - case 'Ƥ': return 'p'; - case 'ƥ': return 'p'; - case 'ƫ': return 't'; - case 'Ƭ': return 't'; - case 'ƭ': return 't'; - case 'Ʈ': return 't'; - case 'Ư': return 'u'; - case 'ư': return 'u'; - case 'Ʊ': return 'y'; - case 'Ʋ': return 'v'; - case 'Ƴ': return 'y'; - case 'ƴ': return 'y'; - case 'Ƶ': return 'z'; - case 'ƶ': return 'z'; - case 'ƿ': return 'w'; - case 'Ǎ': return 'a'; - case 'ǎ': return 'a'; - case 'Ǐ': return 'i'; - case 'ǐ': return 'i'; - case 'Ǒ': return 'o'; - case 'ǒ': return 'o'; - case 'Ǔ': return 'u'; - case 'ǔ': return 'u'; - case 'Ǖ': return 'u'; - case 'ǖ': return 'u'; - case 'Ǘ': return 'u'; - case 'ǘ': return 'u'; - case 'Ǚ': return 'u'; - case 'ǚ': return 'u'; - case 'Ǜ': return 'u'; - case 'ǜ': return 'u'; - case 'Ǟ': return 'a'; - case 'ǟ': return 'a'; - case 'Ǡ': return 'a'; - case 'ǡ': return 'a'; - case 'Ǥ': return 'g'; - case 'ǥ': return 'g'; - case 'Ǧ': return 'g'; - case 'ǧ': return 'g'; - case 'Ǩ': return 'k'; - case 'ǩ': return 'k'; - case 'Ǫ': return 'o'; - case 'ǫ': return 'o'; - case 'Ǭ': return 'o'; - case 'ǭ': return 'o'; - case 'ǰ': return 'j'; - case 'Dz': return 'd'; - case 'Ǵ': return 'g'; - case 'ǵ': return 'g'; - case 'Ƿ': return 'w'; - case 'Ǹ': return 'n'; - case 'ǹ': return 'n'; - case 'Ǻ': return 'a'; - case 'ǻ': return 'a'; - case 'Ǿ': return 'o'; - case 'ǿ': return 'o'; - case 'Ȁ': return 'a'; - case 'ȁ': return 'a'; - case 'Ȃ': return 'a'; - case 'ȃ': return 'a'; - case 'Ȅ': return 'e'; - case 'ȅ': return 'e'; - case 'Ȇ': return 'e'; - case 'ȇ': return 'e'; - case 'Ȉ': return 'i'; - case 'ȉ': return 'i'; - case 'Ȋ': return 'i'; - case 'ȋ': return 'i'; - case 'Ȍ': return 'o'; - case 'ȍ': return 'o'; - case 'Ȏ': return 'o'; - case 'ȏ': return 'o'; - case 'Ȑ': return 'r'; - case 'ȑ': return 'r'; - case 'Ȓ': return 'r'; - case 'ȓ': return 'r'; - case 'Ȕ': return 'u'; - case 'ȕ': return 'u'; - case 'Ȗ': return 'u'; - case 'ȗ': return 'u'; - case 'Ș': return 's'; - case 'ș': return 's'; - case 'Ț': return 't'; - case 'ț': return 't'; - case 'Ȝ': return 'y'; - case 'ȝ': return 'y'; - case 'Ȟ': return 'h'; - case 'ȟ': return 'h'; - case 'Ȥ': return 'z'; - case 'ȥ': return 'z'; - case 'Ȧ': return 'a'; - case 'ȧ': return 'a'; - case 'Ȩ': return 'e'; - case 'ȩ': return 'e'; - case 'Ȫ': return 'o'; - case 'ȫ': return 'o'; - case 'Ȭ': return 'o'; - case 'ȭ': return 'o'; - case 'Ȯ': return 'o'; - case 'ȯ': return 'o'; - case 'Ȱ': return 'o'; - case 'ȱ': return 'o'; - case 'Ȳ': return 'y'; - case 'ȳ': return 'y'; - case 'A': return 'a'; - case 'B': return 'b'; - case 'C': return 'c'; - case 'D': return 'd'; - case 'E': return 'e'; - case 'F': return 'f'; - case 'G': return 'g'; - case 'H': return 'h'; - case 'I': return 'i'; - case 'J': return 'j'; - case 'K': return 'k'; - case 'L': return 'l'; - case 'M': return 'm'; - case 'N': return 'n'; - case 'O': return 'o'; - case 'P': return 'p'; - case 'Q': return 'q'; - case 'R': return 'r'; - case 'S': return 's'; - case 'T': return 't'; - case 'U': return 'u'; - case 'V': return 'v'; - case 'W': return 'w'; - case 'X': return 'x'; - case 'Y': return 'y'; - case 'Z': return 'z'; - default: - return ch; - } - } - - @Override - public byte getDialpadIndex(char ch) { - if (ch >= '0' && ch <= '9') { - return (byte) (ch - '0'); - } else if (ch >= 'a' && ch <= 'z') { - return (byte) (LATIN_LETTERS_TO_DIGITS[ch - 'a'] - '0'); - } else { - return -1; - } - } - - @Override - public char getDialpadNumericCharacter(char ch) { - if (ch >= 'a' && ch <= 'z') { - return LATIN_LETTERS_TO_DIGITS[ch - 'a']; - } - return ch; - } - -} diff --git a/src/com/android/dialer/dialpad/PseudoEmergencyAnimator.java b/src/com/android/dialer/dialpad/PseudoEmergencyAnimator.java deleted file mode 100644 index d4f32b5d4..000000000 --- a/src/com/android/dialer/dialpad/PseudoEmergencyAnimator.java +++ /dev/null @@ -1,160 +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.dialpad; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.LightingColorFilter; -import android.os.Handler; -import android.os.Vibrator; -import android.view.View; - -import com.android.dialer.R; - -/** - * Animates the dial button on "emergency" phone numbers. - */ -public class PseudoEmergencyAnimator { - public interface ViewProvider { - View getView(); - } - - public static final String PSEUDO_EMERGENCY_NUMBER = "01189998819991197253"; - - private static final int VIBRATE_LENGTH_MILLIS = 200; - private static final int ITERATION_LENGTH_MILLIS = 1000; - private static final int ANIMATION_ITERATION_COUNT = 6; - - private ViewProvider mViewProvider; - private ValueAnimator mPseudoEmergencyColorAnimator; - - PseudoEmergencyAnimator(ViewProvider viewProvider) { - mViewProvider = viewProvider; - } - - public void destroy() { - end(); - mViewProvider = null; - } - - public void start() { - if (mPseudoEmergencyColorAnimator == null) { - Integer colorFrom = Color.BLUE; - Integer colorTo = Color.RED; - mPseudoEmergencyColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); - - mPseudoEmergencyColorAnimator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - try { - int color = (int) animator.getAnimatedValue(); - ColorFilter colorFilter = - new LightingColorFilter(Color.BLACK, color); - - View floatingActionButtonContainer = getView().findViewById( - R.id.dialpad_floating_action_button_container); - if (floatingActionButtonContainer != null) { - floatingActionButtonContainer.getBackground().setColorFilter( - colorFilter); - } - } catch (Exception e) { - animator.cancel(); - } - } - }); - - mPseudoEmergencyColorAnimator.addListener(new AnimatorListener() { - @Override - public void onAnimationCancel(Animator animation) { } - - @Override - public void onAnimationRepeat(Animator animation) { - try { - vibrate(VIBRATE_LENGTH_MILLIS); - } catch (Exception e) { - animation.cancel(); - } - } - - @Override - public void onAnimationStart(Animator animation) { } - - @Override - public void onAnimationEnd(Animator animation) { - try { - View floatingActionButtonContainer = getView().findViewById( - R.id.dialpad_floating_action_button_container); - if (floatingActionButtonContainer != null) { - floatingActionButtonContainer.getBackground().clearColorFilter(); - } - - new Handler().postDelayed(new Runnable() { - @Override public void run() { - try { - vibrate(VIBRATE_LENGTH_MILLIS); - } catch (Exception e) { - // ignored - } - } - }, ITERATION_LENGTH_MILLIS); - } catch (Exception e) { - animation.cancel(); - } - } - }); - - mPseudoEmergencyColorAnimator.setDuration(VIBRATE_LENGTH_MILLIS); - mPseudoEmergencyColorAnimator.setRepeatMode(ValueAnimator.REVERSE); - mPseudoEmergencyColorAnimator.setRepeatCount(ANIMATION_ITERATION_COUNT); - } - if (!mPseudoEmergencyColorAnimator.isStarted()) { - mPseudoEmergencyColorAnimator.start(); - } - } - - public void end() { - if (mPseudoEmergencyColorAnimator != null && mPseudoEmergencyColorAnimator.isStarted()) { - mPseudoEmergencyColorAnimator.end(); - } - } - - private View getView() { - return mViewProvider == null ? null : mViewProvider.getView(); - } - - private Context getContext() { - View view = getView(); - return view != null ? view.getContext() : null; - } - - private void vibrate(long milliseconds) { - Context context = getContext(); - if (context != null) { - Vibrator vibrator = - (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator != null) { - vibrator.vibrate(milliseconds); - } - } - } -} diff --git a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java deleted file mode 100644 index 93b649b6d..000000000 --- a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java +++ /dev/null @@ -1,193 +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.dialpad; - -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.content.Loader.ForceLoadContentObserver; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.util.Log; - -import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.database.DialerDatabaseHelper; -import com.android.dialer.database.DialerDatabaseHelper.ContactNumber; -import com.android.dialerbind.DatabaseHelperManager; - -import java.util.ArrayList; - -/** - * Implements a Loader<Cursor> class to asynchronously load SmartDial search results. - */ -public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> { - - private final String TAG = SmartDialCursorLoader.class.getSimpleName(); - private final boolean DEBUG = false; - - private final Context mContext; - - private Cursor mCursor; - - private String mQuery; - private SmartDialNameMatcher mNameMatcher; - - private ForceLoadContentObserver mObserver; - - public SmartDialCursorLoader(Context context) { - super(context); - mContext = context; - } - - /** - * Configures the query string to be used to find SmartDial matches. - * @param query The query string user typed. - */ - public void configureQuery(String query) { - if (DEBUG) { - Log.v(TAG, "Configure new query to be " + query); - } - mQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialPrefix.getMap()); - - /** Constructs a name matcher object for matching names. */ - mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap()); - } - - /** - * Queries the SmartDial database and loads results in background. - * @return Cursor of contacts that matches the SmartDial query. - */ - @Override - public Cursor loadInBackground() { - if (DEBUG) { - Log.v(TAG, "Load in background " + mQuery); - } - - if (!PermissionsUtil.hasContactsPermissions(mContext)) { - return new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY); - } - - /** Loads results from the database helper. */ - final DialerDatabaseHelper dialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper( - mContext); - final ArrayList<ContactNumber> allMatches = dialerDatabaseHelper.getLooseMatches(mQuery, - mNameMatcher); - - if (DEBUG) { - Log.v(TAG, "Loaded matches " + String.valueOf(allMatches.size())); - } - - /** Constructs a cursor for the returned array of results. */ - final MatrixCursor cursor = new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY); - Object[] row = new Object[PhoneQuery.PROJECTION_PRIMARY.length]; - for (ContactNumber contact : allMatches) { - row[PhoneQuery.PHONE_ID] = contact.dataId; - row[PhoneQuery.PHONE_NUMBER] = contact.phoneNumber; - row[PhoneQuery.CONTACT_ID] = contact.id; - row[PhoneQuery.LOOKUP_KEY] = contact.lookupKey; - row[PhoneQuery.PHOTO_ID] = contact.photoId; - row[PhoneQuery.DISPLAY_NAME] = contact.displayName; - row[PhoneQuery.CARRIER_PRESENCE] = contact.carrierPresence; - cursor.addRow(row); - } - return cursor; - } - - @Override - public void deliverResult(Cursor cursor) { - if (isReset()) { - /** The Loader has been reset; ignore the result and invalidate the data. */ - releaseResources(cursor); - return; - } - - /** Hold a reference to the old data so it doesn't get garbage collected. */ - Cursor oldCursor = mCursor; - mCursor = cursor; - - if (mObserver == null) { - mObserver = new ForceLoadContentObserver(); - mContext.getContentResolver().registerContentObserver( - DialerDatabaseHelper.SMART_DIAL_UPDATED_URI, true, mObserver); - } - - if (isStarted()) { - /** If the Loader is in a started state, deliver the results to the client. */ - super.deliverResult(cursor); - } - - /** Invalidate the old data as we don't need it any more. */ - if (oldCursor != null && oldCursor != cursor) { - releaseResources(oldCursor); - } - } - - @Override - protected void onStartLoading() { - if (mCursor != null) { - /** Deliver any previously loaded data immediately. */ - deliverResult(mCursor); - } - if (mCursor == null) { - /** Force loads every time as our results change with queries. */ - forceLoad(); - } - } - - @Override - protected void onStopLoading() { - /** The Loader is in a stopped state, so we should attempt to cancel the current load. */ - cancelLoad(); - } - - @Override - protected void onReset() { - /** Ensure the loader has been stopped. */ - onStopLoading(); - - if (mObserver != null) { - mContext.getContentResolver().unregisterContentObserver(mObserver); - mObserver = null; - } - - /** Release all previously saved query results. */ - if (mCursor != null) { - releaseResources(mCursor); - mCursor = null; - } - } - - @Override - public void onCanceled(Cursor cursor) { - super.onCanceled(cursor); - - if (mObserver != null) { - mContext.getContentResolver().unregisterContentObserver(mObserver); - mObserver = null; - } - - /** The load has been canceled, so we should release the resources associated with 'data'.*/ - releaseResources(cursor); - } - - private void releaseResources(Cursor cursor) { - if (cursor != null) { - cursor.close(); - } - } -} diff --git a/src/com/android/dialer/dialpad/SmartDialMap.java b/src/com/android/dialer/dialpad/SmartDialMap.java deleted file mode 100644 index b51891a8c..000000000 --- a/src/com/android/dialer/dialpad/SmartDialMap.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.android.dialer.dialpad; - -/** - * Note: These methods currently take characters as arguments. For future planned language support, - * they will need to be changed to use codepoints instead of characters. - * - * http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#codePointAt(int) - * - * If/when this change is made, LatinSmartDialMap(which operates on chars) will continue to work - * by simply casting from a codepoint to a character. - */ -public interface SmartDialMap { - /* - * Returns true if the provided character can be mapped to a key on the dialpad - */ - public boolean isValidDialpadCharacter(char ch); - - /* - * Returns true if the provided character is a letter, and can be mapped to a key on the dialpad - */ - public boolean isValidDialpadAlphabeticChar(char ch); - - /* - * Returns true if the provided character is a digit, and can be mapped to a key on the dialpad - */ - public boolean isValidDialpadNumericChar(char ch); - - /* - * Get the index of the key on the dialpad which the character corresponds to - */ - public byte getDialpadIndex(char ch); - - /* - * Get the actual numeric character on the dialpad which the character corresponds to - */ - public char getDialpadNumericCharacter(char ch); - - /* - * Converts uppercase characters to lower case ones, and on a best effort basis, strips accents - * from accented characters. - */ - public char normalizeCharacter(char ch); -} diff --git a/src/com/android/dialer/dialpad/SmartDialMatchPosition.java b/src/com/android/dialer/dialpad/SmartDialMatchPosition.java deleted file mode 100644 index bab2c50d8..000000000 --- a/src/com/android/dialer/dialpad/SmartDialMatchPosition.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2012 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.dialpad; - -import android.util.Log; - -import java.util.ArrayList; - -/** - * Stores information about a range of characters matched in a display name The integers - * start and end indicate that the range start to end (exclusive) correspond to some characters - * in the query. Used to highlight certain parts of the contact's display name to indicate that - * those ranges matched the user's query. - */ -public class SmartDialMatchPosition { - private static final String TAG = SmartDialMatchPosition.class.getSimpleName(); - - public int start; - public int end; - - public SmartDialMatchPosition(int start, int end) { - this.start = start; - this.end = end; - } - - private void advance(int toAdvance) { - this.start += toAdvance; - this.end += toAdvance; - } - - /** - * Used by {@link SmartDialNameMatcher} to advance the positions of a match position found in - * a sub query. - * - * @param inList ArrayList of SmartDialMatchPositions to modify. - * @param toAdvance Offset to modify by. - */ - public static void advanceMatchPositions(ArrayList<SmartDialMatchPosition> inList, - int toAdvance) { - for (int i = 0; i < inList.size(); i++) { - inList.get(i).advance(toAdvance); - } - } - - /** - * Used mainly for debug purposes. Displays contents of an ArrayList of SmartDialMatchPositions. - * - * @param list ArrayList of SmartDialMatchPositions to print out in a human readable fashion. - */ - public static void print(ArrayList<SmartDialMatchPosition> list) { - for (int i = 0; i < list.size(); i ++) { - SmartDialMatchPosition m = list.get(i); - Log.d(TAG, "[" + m.start + "," + m.end + "]"); - } - } -} diff --git a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java deleted file mode 100644 index a54fe1618..000000000 --- a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright (C) 2012 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.dialpad; - -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.android.dialer.dialpad.SmartDialPrefix.PhoneNumberTokens; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; - -import java.util.ArrayList; - -/** - * {@link #SmartDialNameMatcher} contains utility functions to remove accents from accented - * characters and normalize a phone number. It also contains the matching logic that determines if - * a contact's display name matches a numeric query. The boolean variable - * {@link #ALLOW_INITIAL_MATCH} controls the behavior of the matching logic and determines - * whether we allow matches like 57 - (J)ohn (S)mith. - */ -public class SmartDialNameMatcher { - - private String mQuery; - - // Whether or not we allow matches like 57 - (J)ohn (S)mith - private static final boolean ALLOW_INITIAL_MATCH = true; - - // The maximum length of the initial we will match - typically set to 1 to minimize false - // positives - private static final int INITIAL_LENGTH_LIMIT = 1; - - private final ArrayList<SmartDialMatchPosition> mMatchPositions = Lists.newArrayList(); - - public static final SmartDialMap LATIN_SMART_DIAL_MAP = new LatinSmartDialMap(); - - private final SmartDialMap mMap; - - private String mNameMatchMask = ""; - private String mPhoneNumberMatchMask = ""; - - @VisibleForTesting - public SmartDialNameMatcher(String query) { - this(query, LATIN_SMART_DIAL_MAP); - } - - public SmartDialNameMatcher(String query, SmartDialMap map) { - mQuery = query; - mMap = map; - } - - /** - * Constructs empty highlight mask. Bit 0 at a position means there is no match, Bit 1 means - * there is a match and should be highlighted in the TextView. - * @param builder StringBuilder object - * @param length Length of the desired mask. - */ - private void constructEmptyMask(StringBuilder builder, int length) { - for (int i = 0; i < length; ++i) { - builder.append("0"); - } - } - - /** - * Replaces the 0-bit at a position with 1-bit, indicating that there is a match. - * @param builder StringBuilder object. - * @param matchPos Match Positions to mask as 1. - */ - private void replaceBitInMask(StringBuilder builder, SmartDialMatchPosition matchPos) { - for (int i = matchPos.start; i < matchPos.end; ++i) { - builder.replace(i, i + 1, "1"); - } - } - - /** - * Strips a phone number of unnecessary characters (spaces, dashes, etc.) - * - * @param number Phone number we want to normalize - * @return Phone number consisting of digits from 0-9 - */ - public static String normalizeNumber(String number, SmartDialMap map) { - return normalizeNumber(number, 0, map); - } - - /** - * Strips a phone number of unnecessary characters (spaces, dashes, etc.) - * - * @param number Phone number we want to normalize - * @param offset Offset to start from - * @return Phone number consisting of digits from 0-9 - */ - public static String normalizeNumber(String number, int offset, SmartDialMap map) { - final StringBuilder s = new StringBuilder(); - for (int i = offset; i < number.length(); i++) { - char ch = number.charAt(i); - if (map.isValidDialpadNumericChar(ch)) { - s.append(ch); - } - } - return s.toString(); - } - - /** - * Matches a phone number against a query. Let the test application overwrite the NANP setting. - * - * @param phoneNumber - Raw phone number - * @param query - Normalized query (only contains numbers from 0-9) - * @param useNanp - Overwriting nanp setting boolean, used for testing. - * @return {@literal null} if the number and the query don't match, a valid - * SmartDialMatchPosition with the matching positions otherwise - */ - @VisibleForTesting - @Nullable - public SmartDialMatchPosition matchesNumber(String phoneNumber, String query, boolean useNanp) { - if (TextUtils.isEmpty(phoneNumber)) { - return null; - } - StringBuilder builder = new StringBuilder(); - constructEmptyMask(builder, phoneNumber.length()); - mPhoneNumberMatchMask = builder.toString(); - - // Try matching the number as is - SmartDialMatchPosition matchPos = matchesNumberWithOffset(phoneNumber, query, 0); - if (matchPos == null) { - final PhoneNumberTokens phoneNumberTokens = - SmartDialPrefix.parsePhoneNumber(phoneNumber); - - if (phoneNumberTokens == null) { - return matchPos; - } - if (phoneNumberTokens.countryCodeOffset != 0) { - matchPos = matchesNumberWithOffset(phoneNumber, query, - phoneNumberTokens.countryCodeOffset); - } - if (matchPos == null && phoneNumberTokens.nanpCodeOffset != 0 && useNanp) { - matchPos = matchesNumberWithOffset(phoneNumber, query, - phoneNumberTokens.nanpCodeOffset); - } - } - if (matchPos != null) { - replaceBitInMask(builder, matchPos); - mPhoneNumberMatchMask = builder.toString(); - } - return matchPos; - } - - /** - * Matches a phone number against the saved query, taking care of formatting characters and also - * taking into account country code prefixes and special NANP number treatment. - * - * @param phoneNumber - Raw phone number - * @return {@literal null} if the number and the query don't match, a valid - * SmartDialMatchPosition with the matching positions otherwise - */ - public SmartDialMatchPosition matchesNumber(String phoneNumber) { - return matchesNumber(phoneNumber, mQuery, true); - } - - /** - * Matches a phone number against a query, taking care of formatting characters and also - * taking into account country code prefixes and special NANP number treatment. - * - * @param phoneNumber - Raw phone number - * @param query - Normalized query (only contains numbers from 0-9) - * @return {@literal null} if the number and the query don't match, a valid - * SmartDialMatchPosition with the matching positions otherwise - */ - public SmartDialMatchPosition matchesNumber(String phoneNumber, String query) { - return matchesNumber(phoneNumber, query, true); - } - - /** - * Matches a phone number against a query, taking care of formatting characters - * - * @param phoneNumber - Raw phone number - * @param query - Normalized query (only contains numbers from 0-9) - * @param offset - The position in the number to start the match against (used to ignore - * leading prefixes/country codes) - * @return {@literal null} if the number and the query don't match, a valid - * SmartDialMatchPosition with the matching positions otherwise - */ - private SmartDialMatchPosition matchesNumberWithOffset(String phoneNumber, String query, - int offset) { - if (TextUtils.isEmpty(phoneNumber) || TextUtils.isEmpty(query)) { - return null; - } - int queryAt = 0; - int numberAt = offset; - for (int i = offset; i < phoneNumber.length(); i++) { - if (queryAt == query.length()) { - break; - } - char ch = phoneNumber.charAt(i); - if (mMap.isValidDialpadNumericChar(ch)) { - if (ch != query.charAt(queryAt)) { - return null; - } - queryAt++; - } else { - if (queryAt == 0) { - // Found a separator before any part of the query was matched, so advance the - // offset to avoid prematurely highlighting separators before the rest of the - // query. - // E.g. don't highlight the first '-' if we're matching 1-510-111-1111 with - // '510'. - // However, if the current offset is 0, just include the beginning separators - // anyway, otherwise the highlighting ends up looking weird. - // E.g. if we're matching (510)-111-1111 with '510', we should include the - // first '('. - if (offset != 0) { - offset++; - } - } - } - numberAt++; - } - return new SmartDialMatchPosition(0 + offset, numberAt); - } - - /** - * This function iterates through each token in the display name, trying to match the query - * to the numeric equivalent of the token. - * - * A token is defined as a range in the display name delimited by characters that have no - * latin alphabet equivalents (e.g. spaces - ' ', periods - ',', underscores - '_' or chinese - * characters - '王'). Transliteration from non-latin characters to latin character will be - * done on a best effort basis - e.g. 'Ü' - 'u'. - * - * For example, - * the display name "Phillips Thomas Jr" contains three tokens: "phillips", "thomas", and "jr". - * - * A match must begin at the start of a token. - * For example, typing 846(Tho) would match "Phillips Thomas", but 466(hom) would not. - * - * Also, a match can extend across tokens. - * For example, typing 37337(FredS) would match (Fred S)mith. - * - * @param displayName The normalized(no accented characters) display name we intend to match - * against. - * @param query The string of digits that we want to match the display name to. - * @param matchList An array list of {@link SmartDialMatchPosition}s that we add matched - * positions to. - * @return Returns true if a combination of the tokens in displayName match the query - * string contained in query. If the function returns true, matchList will contain an - * ArrayList of match positions (multiple matches correspond to initial matches). - */ - @VisibleForTesting - boolean matchesCombination(String displayName, String query, - ArrayList<SmartDialMatchPosition> matchList) { - StringBuilder builder = new StringBuilder(); - constructEmptyMask(builder, displayName.length()); - mNameMatchMask = builder.toString(); - final int nameLength = displayName.length(); - final int queryLength = query.length(); - - if (nameLength < queryLength) { - return false; - } - - if (queryLength == 0) { - return false; - } - - // The current character index in displayName - // E.g. 3 corresponds to 'd' in "Fred Smith" - int nameStart = 0; - - // The current character in the query we are trying to match the displayName against - int queryStart = 0; - - // The start position of the current token we are inspecting - int tokenStart = 0; - - // The number of non-alphabetic characters we've encountered so far in the current match. - // E.g. if we've currently matched 3733764849 to (Fred Smith W)illiam, then the - // seperatorCount should be 2. This allows us to correctly calculate offsets for the match - // positions - int seperatorCount = 0; - - ArrayList<SmartDialMatchPosition> partial = new ArrayList<SmartDialMatchPosition>(); - // Keep going until we reach the end of displayName - while (nameStart < nameLength && queryStart < queryLength) { - char ch = displayName.charAt(nameStart); - // Strip diacritics from accented characters if any - ch = mMap.normalizeCharacter(ch); - if (mMap.isValidDialpadCharacter(ch)) { - if (mMap.isValidDialpadAlphabeticChar(ch)) { - ch = mMap.getDialpadNumericCharacter(ch); - } - if (ch != query.charAt(queryStart)) { - // Failed to match the current character in the query. - - // Case 1: Failed to match the first character in the query. Skip to the next - // token since there is no chance of this token matching the query. - - // Case 2: Previous characters in the query matched, but the current character - // failed to match. This happened in the middle of a token. Skip to the next - // token since there is no chance of this token matching the query. - - // Case 3: Previous characters in the query matched, but the current character - // failed to match. This happened right at the start of the current token. In - // this case, we should restart the query and try again with the current token. - // Otherwise, we would fail to match a query like "964"(yog) against a name - // Yo-Yoghurt because the query match would fail on the 3rd character, and - // then skip to the end of the "Yoghurt" token. - - if (queryStart == 0 || mMap.isValidDialpadCharacter(mMap.normalizeCharacter( - displayName.charAt(nameStart - 1)))) { - // skip to the next token, in the case of 1 or 2. - while (nameStart < nameLength && - mMap.isValidDialpadCharacter(mMap.normalizeCharacter( - displayName.charAt(nameStart)))) { - nameStart++; - } - nameStart++; - } - - // Restart the query and set the correct token position - queryStart = 0; - seperatorCount = 0; - tokenStart = nameStart; - } else { - if (queryStart == queryLength - 1) { - - // As much as possible, we prioritize a full token match over a sub token - // one so if we find a full token match, we can return right away - matchList.add(new SmartDialMatchPosition( - tokenStart, queryLength + tokenStart + seperatorCount)); - for (SmartDialMatchPosition match : matchList) { - replaceBitInMask(builder, match); - } - mNameMatchMask = builder.toString(); - return true; - } else if (ALLOW_INITIAL_MATCH && queryStart < INITIAL_LENGTH_LIMIT) { - // we matched the first character. - // branch off and see if we can find another match with the remaining - // characters in the query string and the remaining tokens - // find the next separator in the query string - int j; - for (j = nameStart; j < nameLength; j++) { - if (!mMap.isValidDialpadCharacter(mMap.normalizeCharacter( - displayName.charAt(j)))) { - break; - } - } - // this means there is at least one character left after the separator - if (j < nameLength - 1) { - final String remainder = displayName.substring(j + 1); - final ArrayList<SmartDialMatchPosition> partialTemp = - Lists.newArrayList(); - if (matchesCombination( - remainder, query.substring(queryStart + 1), partialTemp)) { - - // store the list of possible match positions - SmartDialMatchPosition.advanceMatchPositions(partialTemp, j + 1); - partialTemp.add(0, - new SmartDialMatchPosition(nameStart, nameStart + 1)); - // we found a partial token match, store the data in a - // temp buffer and return it if we end up not finding a full - // token match - partial = partialTemp; - } - } - } - nameStart++; - queryStart++; - // we matched the current character in the name against one in the query, - // continue and see if the rest of the characters match - } - } else { - // found a separator, we skip this character and continue to the next one - nameStart++; - if (queryStart == 0) { - // This means we found a separator before the start of a token, - // so we should increment the token's start position to reflect its true - // start position - tokenStart = nameStart; - } else { - // Otherwise this separator was found in the middle of a token being matched, - // so increase the separator count - seperatorCount++; - } - } - } - // if we have no complete match at this point, then we attempt to fall back to the partial - // token match(if any). If we don't allow initial matching (ALLOW_INITIAL_MATCH = false) - // then partial will always be empty. - if (!partial.isEmpty()) { - matchList.addAll(partial); - for (SmartDialMatchPosition match : matchList) { - replaceBitInMask(builder, match); - } - mNameMatchMask = builder.toString(); - return true; - } - return false; - } - - public boolean matches(String displayName) { - mMatchPositions.clear(); - return matchesCombination(displayName, mQuery, mMatchPositions); - } - - public ArrayList<SmartDialMatchPosition> getMatchPositions() { - // Return a clone of mMatchPositions so that the caller can use it without - // worrying about it changing - return new ArrayList<SmartDialMatchPosition>(mMatchPositions); - } - - public void setQuery(String query) { - mQuery = query; - } - - public String getNameMatchPositionsInString() { - return mNameMatchMask; - } - - public String getNumberMatchPositionsInString() { - return mPhoneNumberMatchMask; - } - - public String getQuery() { - return mQuery; - } -} diff --git a/src/com/android/dialer/dialpad/SmartDialPrefix.java b/src/com/android/dialer/dialpad/SmartDialPrefix.java deleted file mode 100644 index a0b51ebb5..000000000 --- a/src/com/android/dialer/dialpad/SmartDialPrefix.java +++ /dev/null @@ -1,608 +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.dialpad; - -import android.content.Context; - -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * Smart Dial utility class to find prefixes of contacts. It contains both methods to find supported - * prefix combinations for contact names, and also methods to find supported prefix combinations for - * contacts' phone numbers. Each contact name is separated into several tokens, such as first name, - * middle name, family name etc. Each phone number is also separated into country code, NANP area - * code, and local number if such separation is possible. - */ -public class SmartDialPrefix { - - /** The number of starting and ending tokens in a contact's name considered for initials. - * For example, if both constants are set to 2, and a contact's name is - * "Albert Ben Charles Daniel Ed Foster", the first two tokens "Albert" "Ben", and last two - * tokens "Ed" "Foster" can be replaced by their initials in contact name matching. - * Users can look up this contact by combinations of his initials such as "AF" "BF" "EF" "ABF" - * "BEF" "ABEF" etc, but can not use combinations such as "CF" "DF" "ACF" "ADF" etc. - */ - private static final int LAST_TOKENS_FOR_INITIALS = 2; - private static final int FIRST_TOKENS_FOR_INITIALS = 2; - - /** The country code of the user's sim card obtained by calling getSimCountryIso*/ - private static final String PREF_USER_SIM_COUNTRY_CODE = - "DialtactsActivity_user_sim_country_code"; - private static final String PREF_USER_SIM_COUNTRY_CODE_DEFAULT = null; - private static String sUserSimCountryCode = PREF_USER_SIM_COUNTRY_CODE_DEFAULT; - - /** Indicates whether user is in NANP regions.*/ - private static boolean sUserInNanpRegion = false; - - /** Set of country names that use NANP code.*/ - private static Set<String> sNanpCountries = null; - - /** Set of supported country codes in front of the phone number. */ - private static Set<String> sCountryCodes = null; - - /** Dialpad mapping. */ - private static final SmartDialMap mMap = new LatinSmartDialMap(); - - private static boolean sNanpInitialized = false; - - /** Initializes the Nanp settings, and finds out whether user is in a NANP region.*/ - public static void initializeNanpSettings(Context context){ - final TelephonyManager manager = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - if (manager != null) { - sUserSimCountryCode = manager.getSimCountryIso(); - } - - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - if (sUserSimCountryCode != null) { - /** Updates shared preferences with the latest country obtained from getSimCountryIso.*/ - prefs.edit().putString(PREF_USER_SIM_COUNTRY_CODE, sUserSimCountryCode).apply(); - } else { - /** Uses previously stored country code if loading fails. */ - sUserSimCountryCode = prefs.getString(PREF_USER_SIM_COUNTRY_CODE, - PREF_USER_SIM_COUNTRY_CODE_DEFAULT); - } - /** Queries the NANP country list to find out whether user is in a NANP region.*/ - sUserInNanpRegion = isCountryNanp(sUserSimCountryCode); - sNanpInitialized = true; - } - - /** - * Explicitly setting the user Nanp to the given boolean - */ - @VisibleForTesting - public static void setUserInNanpRegion(boolean userInNanpRegion) { - sUserInNanpRegion = userInNanpRegion; - } - - /** - * Class to record phone number parsing information. - */ - public static class PhoneNumberTokens { - /** Country code of the phone number. */ - final String countryCode; - - /** Offset of national number after the country code. */ - final int countryCodeOffset; - - /** Offset of local number after NANP area code.*/ - final int nanpCodeOffset; - - public PhoneNumberTokens(String countryCode, int countryCodeOffset, int nanpCodeOffset) { - this.countryCode = countryCode; - this.countryCodeOffset = countryCodeOffset; - this.nanpCodeOffset = nanpCodeOffset; - } - } - - /** - * Parses a contact's name into a list of separated tokens. - * - * @param contactName Contact's name stored in string. - * @return A list of name tokens, for example separated first names, last name, etc. - */ - public static ArrayList<String> parseToIndexTokens(String contactName) { - final int length = contactName.length(); - final ArrayList<String> result = Lists.newArrayList(); - char c; - final StringBuilder currentIndexToken = new StringBuilder(); - /** - * Iterates through the whole name string. If the current character is a valid character, - * append it to the current token. If the current character is not a valid character, for - * example space " ", mark the current token as complete and add it to the list of tokens. - */ - for (int i = 0; i < length; i++) { - c = mMap.normalizeCharacter(contactName.charAt(i)); - if (mMap.isValidDialpadCharacter(c)) { - /** Converts a character into the number on dialpad that represents the character.*/ - currentIndexToken.append(mMap.getDialpadIndex(c)); - } else { - if (currentIndexToken.length() != 0) { - result.add(currentIndexToken.toString()); - } - currentIndexToken.delete(0, currentIndexToken.length()); - } - } - - /** Adds the last token in case it has not been added.*/ - if (currentIndexToken.length() != 0) { - result.add(currentIndexToken.toString()); - } - return result; - } - - /** - * Generates a list of strings that any prefix of any string in the list can be used to look - * up the contact's name. - * - * @param index The contact's name in string. - * @return A List of strings, whose prefix can be used to look up the contact. - */ - public static ArrayList<String> generateNamePrefixes(String index) { - final ArrayList<String> result = Lists.newArrayList(); - - /** Parses the name into a list of tokens.*/ - final ArrayList<String> indexTokens = parseToIndexTokens(index); - - if (indexTokens.size() > 0) { - /** Adds the full token combinations to the list. For example, a contact with name - * "Albert Ben Ed Foster" can be looked up by any prefix of the following strings - * "Foster" "EdFoster" "BenEdFoster" and "AlbertBenEdFoster". This covers all cases of - * look up that contains only one token, and that spans multiple continuous tokens. - */ - final StringBuilder fullNameToken = new StringBuilder(); - for (int i = indexTokens.size() - 1; i >= 0; i--) { - fullNameToken.insert(0, indexTokens.get(i)); - result.add(fullNameToken.toString()); - } - - /** Adds initial combinations to the list, with the number of initials restricted by - * {@link #LAST_TOKENS_FOR_INITIALS} and {@link #FIRST_TOKENS_FOR_INITIALS}. - * For example, a contact with name "Albert Ben Ed Foster" can be looked up by any - * prefix of the following strings "EFoster" "BFoster" "BEFoster" "AFoster" "ABFoster" - * "AEFoster" and "ABEFoster". This covers all cases of initial lookup. - */ - ArrayList<String> fullNames = Lists.newArrayList(); - fullNames.add(indexTokens.get(indexTokens.size() - 1)); - final int recursiveNameStart = result.size(); - int recursiveNameEnd = result.size(); - String initial = ""; - for (int i = indexTokens.size() - 2; i >= 0; i--) { - if ((i >= indexTokens.size() - LAST_TOKENS_FOR_INITIALS) || - (i < FIRST_TOKENS_FOR_INITIALS)) { - initial = indexTokens.get(i).substring(0, 1); - - /** Recursively adds initial combinations to the list.*/ - for (int j = 0; j < fullNames.size(); ++j) { - result.add(initial + fullNames.get(j)); - } - for (int j = recursiveNameStart; j < recursiveNameEnd; ++j) { - result.add(initial + result.get(j)); - } - recursiveNameEnd = result.size(); - final String currentFullName = fullNames.get(fullNames.size() - 1); - fullNames.add(indexTokens.get(i) + currentFullName); - } - } - } - - return result; - } - - /** - * Computes a list of number strings based on tokens of a given phone number. Any prefix - * of any string in the list can be used to look up the phone number. The list include the - * full phone number, the national number if there is a country code in the phone number, and - * the local number if there is an area code in the phone number following the NANP format. - * For example, if a user has phone number +41 71 394 8392, the list will contain 41713948392 - * and 713948392. Any prefix to either of the strings can be used to look up the phone number. - * If a user has a phone number +1 555-302-3029 (NANP format), the list will contain - * 15553023029, 5553023029, and 3023029. - * - * @param number String of user's phone number. - * @return A list of strings where any prefix of any entry can be used to look up the number. - */ - public static ArrayList<String> parseToNumberTokens(String number) { - final ArrayList<String> result = Lists.newArrayList(); - if (!TextUtils.isEmpty(number)) { - /** Adds the full number to the list.*/ - result.add(SmartDialNameMatcher.normalizeNumber(number, mMap)); - - final PhoneNumberTokens phoneNumberTokens = parsePhoneNumber(number); - if (phoneNumberTokens == null) { - return result; - } - - if (phoneNumberTokens.countryCodeOffset != 0) { - result.add(SmartDialNameMatcher.normalizeNumber(number, - phoneNumberTokens.countryCodeOffset, mMap)); - } - - if (phoneNumberTokens.nanpCodeOffset != 0) { - result.add(SmartDialNameMatcher.normalizeNumber(number, - phoneNumberTokens.nanpCodeOffset, mMap)); - } - } - return result; - } - - /** - * Parses a phone number to find out whether it has country code and NANP area code. - * - * @param number Raw phone number. - * @return a PhoneNumberToken instance with country code, NANP code information. - */ - public static PhoneNumberTokens parsePhoneNumber(String number) { - String countryCode = ""; - int countryCodeOffset = 0; - int nanpNumberOffset = 0; - - if (!TextUtils.isEmpty(number)) { - String normalizedNumber = SmartDialNameMatcher.normalizeNumber(number, mMap); - if (number.charAt(0) == '+') { - /** If the number starts with '+', tries to find valid country code. */ - for (int i = 1; i <= 1 + 3; i++) { - if (number.length() <= i) { - break; - } - countryCode = number.substring(1, i); - if (isValidCountryCode(countryCode)) { - countryCodeOffset = i; - break; - } - } - } else { - /** If the number does not start with '+', finds out whether it is in NANP - * format and has '1' preceding the number. - */ - if ((normalizedNumber.length() == 11) && (normalizedNumber.charAt(0) == '1') && - (sUserInNanpRegion)) { - countryCode = "1"; - countryCodeOffset = number.indexOf(normalizedNumber.charAt(1)); - if (countryCodeOffset == -1) { - countryCodeOffset = 0; - } - } - } - - /** If user is in NANP region, finds out whether a number is in NANP format.*/ - if (sUserInNanpRegion) { - String areaCode = ""; - if (countryCode.equals("") && normalizedNumber.length() == 10){ - /** if the number has no country code but fits the NANP format, extracts the - * NANP area code, and finds out offset of the local number. - */ - areaCode = normalizedNumber.substring(0, 3); - } else if (countryCode.equals("1") && normalizedNumber.length() == 11) { - /** If the number has country code '1', finds out area code and offset of the - * local number. - */ - areaCode = normalizedNumber.substring(1, 4); - } - if (!areaCode.equals("")) { - final int areaCodeIndex = number.indexOf(areaCode); - if (areaCodeIndex != -1) { - nanpNumberOffset = number.indexOf(areaCode) + 3; - } - } - } - } - return new PhoneNumberTokens(countryCode, countryCodeOffset, nanpNumberOffset); - } - - /** - * Checkes whether a country code is valid. - */ - private static boolean isValidCountryCode(String countryCode) { - if (sCountryCodes == null) { - sCountryCodes = initCountryCodes(); - } - return sCountryCodes.contains(countryCode); - } - - private static Set<String> initCountryCodes() { - final HashSet<String> result = new HashSet<String>(); - result.add("1"); - result.add("7"); - result.add("20"); - result.add("27"); - result.add("30"); - result.add("31"); - result.add("32"); - result.add("33"); - result.add("34"); - result.add("36"); - result.add("39"); - result.add("40"); - result.add("41"); - result.add("43"); - result.add("44"); - result.add("45"); - result.add("46"); - result.add("47"); - result.add("48"); - result.add("49"); - result.add("51"); - result.add("52"); - result.add("53"); - result.add("54"); - result.add("55"); - result.add("56"); - result.add("57"); - result.add("58"); - result.add("60"); - result.add("61"); - result.add("62"); - result.add("63"); - result.add("64"); - result.add("65"); - result.add("66"); - result.add("81"); - result.add("82"); - result.add("84"); - result.add("86"); - result.add("90"); - result.add("91"); - result.add("92"); - result.add("93"); - result.add("94"); - result.add("95"); - result.add("98"); - result.add("211"); - result.add("212"); - result.add("213"); - result.add("216"); - result.add("218"); - result.add("220"); - result.add("221"); - result.add("222"); - result.add("223"); - result.add("224"); - result.add("225"); - result.add("226"); - result.add("227"); - result.add("228"); - result.add("229"); - result.add("230"); - result.add("231"); - result.add("232"); - result.add("233"); - result.add("234"); - result.add("235"); - result.add("236"); - result.add("237"); - result.add("238"); - result.add("239"); - result.add("240"); - result.add("241"); - result.add("242"); - result.add("243"); - result.add("244"); - result.add("245"); - result.add("246"); - result.add("247"); - result.add("248"); - result.add("249"); - result.add("250"); - result.add("251"); - result.add("252"); - result.add("253"); - result.add("254"); - result.add("255"); - result.add("256"); - result.add("257"); - result.add("258"); - result.add("260"); - result.add("261"); - result.add("262"); - result.add("263"); - result.add("264"); - result.add("265"); - result.add("266"); - result.add("267"); - result.add("268"); - result.add("269"); - result.add("290"); - result.add("291"); - result.add("297"); - result.add("298"); - result.add("299"); - result.add("350"); - result.add("351"); - result.add("352"); - result.add("353"); - result.add("354"); - result.add("355"); - result.add("356"); - result.add("357"); - result.add("358"); - result.add("359"); - result.add("370"); - result.add("371"); - result.add("372"); - result.add("373"); - result.add("374"); - result.add("375"); - result.add("376"); - result.add("377"); - result.add("378"); - result.add("379"); - result.add("380"); - result.add("381"); - result.add("382"); - result.add("385"); - result.add("386"); - result.add("387"); - result.add("389"); - result.add("420"); - result.add("421"); - result.add("423"); - result.add("500"); - result.add("501"); - result.add("502"); - result.add("503"); - result.add("504"); - result.add("505"); - result.add("506"); - result.add("507"); - result.add("508"); - result.add("509"); - result.add("590"); - result.add("591"); - result.add("592"); - result.add("593"); - result.add("594"); - result.add("595"); - result.add("596"); - result.add("597"); - result.add("598"); - result.add("599"); - result.add("670"); - result.add("672"); - result.add("673"); - result.add("674"); - result.add("675"); - result.add("676"); - result.add("677"); - result.add("678"); - result.add("679"); - result.add("680"); - result.add("681"); - result.add("682"); - result.add("683"); - result.add("685"); - result.add("686"); - result.add("687"); - result.add("688"); - result.add("689"); - result.add("690"); - result.add("691"); - result.add("692"); - result.add("800"); - result.add("808"); - result.add("850"); - result.add("852"); - result.add("853"); - result.add("855"); - result.add("856"); - result.add("870"); - result.add("878"); - result.add("880"); - result.add("881"); - result.add("882"); - result.add("883"); - result.add("886"); - result.add("888"); - result.add("960"); - result.add("961"); - result.add("962"); - result.add("963"); - result.add("964"); - result.add("965"); - result.add("966"); - result.add("967"); - result.add("968"); - result.add("970"); - result.add("971"); - result.add("972"); - result.add("973"); - result.add("974"); - result.add("975"); - result.add("976"); - result.add("977"); - result.add("979"); - result.add("992"); - result.add("993"); - result.add("994"); - result.add("995"); - result.add("996"); - result.add("998"); - return result; - } - - public static SmartDialMap getMap() { - return mMap; - } - - /** - * Indicates whether the given country uses NANP numbers - * @see <a href="https://en.wikipedia.org/wiki/North_American_Numbering_Plan"> - * https://en.wikipedia.org/wiki/North_American_Numbering_Plan</a> - * - * @param country ISO 3166 country code (case doesn't matter) - * @return True if country uses NANP numbers (e.g. US, Canada), false otherwise - */ - @VisibleForTesting - public static boolean isCountryNanp(String country) { - if (TextUtils.isEmpty(country)) { - return false; - } - if (sNanpCountries == null) { - sNanpCountries = initNanpCountries(); - } - return sNanpCountries.contains(country.toUpperCase()); - } - - private static Set<String> initNanpCountries() { - final HashSet<String> result = new HashSet<String>(); - result.add("US"); // United States - result.add("CA"); // Canada - result.add("AS"); // American Samoa - result.add("AI"); // Anguilla - result.add("AG"); // Antigua and Barbuda - result.add("BS"); // Bahamas - result.add("BB"); // Barbados - result.add("BM"); // Bermuda - result.add("VG"); // British Virgin Islands - result.add("KY"); // Cayman Islands - result.add("DM"); // Dominica - result.add("DO"); // Dominican Republic - result.add("GD"); // Grenada - result.add("GU"); // Guam - result.add("JM"); // Jamaica - result.add("PR"); // Puerto Rico - result.add("MS"); // Montserrat - result.add("MP"); // Northern Mariana Islands - result.add("KN"); // Saint Kitts and Nevis - result.add("LC"); // Saint Lucia - result.add("VC"); // Saint Vincent and the Grenadines - result.add("TT"); // Trinidad and Tobago - result.add("TC"); // Turks and Caicos Islands - result.add("VI"); // U.S. Virgin Islands - return result; - } - - /** - * Returns whether the user is in a region that uses Nanp format based on the sim location. - * - * @return Whether user is in Nanp region. - */ - public static boolean getUserInNanpRegion() { - return sUserInNanpRegion; - } -} diff --git a/src/com/android/dialer/dialpad/UnicodeDialerKeyListener.java b/src/com/android/dialer/dialpad/UnicodeDialerKeyListener.java deleted file mode 100644 index 740b4566f..000000000 --- a/src/com/android/dialer/dialpad/UnicodeDialerKeyListener.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2012 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.dialpad; - -import android.telephony.PhoneNumberUtils; -import android.text.Spanned; -import android.text.method.DialerKeyListener; - -/** - * {@link DialerKeyListener} with Unicode support. Converts any Unicode(e.g. Arabic) characters - * that represent digits into digits before filtering the results so that we can support - * pasted digits from Unicode languages. - */ -public class UnicodeDialerKeyListener extends DialerKeyListener { - public static final UnicodeDialerKeyListener INSTANCE = new UnicodeDialerKeyListener(); - - @Override - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - final String converted = PhoneNumberUtils.convertKeypadLettersToDigits( - PhoneNumberUtils.replaceUnicodeDigits(source.toString())); - // PhoneNumberUtils.replaceUnicodeDigits performs a character for character replacement, - // so we can assume that start and end positions should remain unchanged. - CharSequence result = super.filter(converted, start, end, dest, dstart, dend); - if (result == null) { - if (source.equals(converted)) { - // There was no conversion or filtering performed. Just return null according to - // the behavior of DialerKeyListener. - return null; - } else { - // filter returns null if the charsequence is to be returned unchanged/unfiltered. - // But in this case we do want to return a modified character string (even if - // none of the characters in the modified string are filtered). So if - // result == null we return the unfiltered but converted numeric string instead. - return converted.subSequence(start, end); - } - } - return result; - } -} diff --git a/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java b/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java deleted file mode 100644 index 3c60a967b..000000000 --- a/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java +++ /dev/null @@ -1,320 +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.filterednumber; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentManager; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.net.Uri; -import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.text.TextUtils; -import android.view.View; -import android.widget.Toast; - -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.R; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnBlockNumberListener; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnUnblockNumberListener; -import com.android.dialer.voicemail.VisualVoicemailEnabledChecker; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; - -/** - * Fragment for confirming and enacting blocking/unblocking a number. Also invokes snackbar - * providing undo functionality. - */ -public class BlockNumberDialogFragment extends DialogFragment { - - /** - * Use a callback interface to update UI after success/undo. Favor this approach over other - * more standard paradigms because of the variety of scenarios in which the DialogFragment - * can be invoked (by an Activity, by a fragment, by an adapter, by an adapter list item). - * Because of this, we do NOT support retaining state on rotation, and will dismiss the dialog - * upon rotation instead. - */ - public interface Callback { - /** - * Called when a number is successfully added to the set of filtered numbers - */ - void onFilterNumberSuccess(); - - /** - * Called when a number is successfully removed from the set of filtered numbers - */ - void onUnfilterNumberSuccess(); - - /** - * Called when the action of filtering or unfiltering a number is undone - */ - void onChangeFilteredNumberUndo(); - } - - private static final String BLOCK_DIALOG_FRAGMENT = "BlockNumberDialog"; - - private static final String ARG_BLOCK_ID = "argBlockId"; - private static final String ARG_NUMBER = "argNumber"; - private static final String ARG_COUNTRY_ISO = "argCountryIso"; - private static final String ARG_DISPLAY_NUMBER = "argDisplayNumber"; - private static final String ARG_PARENT_VIEW_ID = "parentViewId"; - - private String mNumber; - private String mDisplayNumber; - private String mCountryIso; - - private FilteredNumberAsyncQueryHandler mHandler; - private View mParentView; - private VisualVoicemailEnabledChecker mVoicemailEnabledChecker; - private Callback mCallback; - - public static void show( - Integer blockId, - String number, - String countryIso, - String displayNumber, - Integer parentViewId, - FragmentManager fragmentManager, - Callback callback) { - final BlockNumberDialogFragment newFragment = BlockNumberDialogFragment.newInstance( - blockId, number, countryIso, displayNumber, parentViewId); - - newFragment.setCallback(callback); - newFragment.show(fragmentManager, BlockNumberDialogFragment.BLOCK_DIALOG_FRAGMENT); - } - - private static BlockNumberDialogFragment newInstance( - Integer blockId, - String number, - String countryIso, - String displayNumber, - Integer parentViewId) { - final BlockNumberDialogFragment fragment = new BlockNumberDialogFragment(); - final Bundle args = new Bundle(); - if (blockId != null) { - args.putInt(ARG_BLOCK_ID, blockId.intValue()); - } - if (parentViewId != null) { - args.putInt(ARG_PARENT_VIEW_ID, parentViewId.intValue()); - } - args.putString(ARG_NUMBER, number); - args.putString(ARG_COUNTRY_ISO, countryIso); - args.putString(ARG_DISPLAY_NUMBER, displayNumber); - fragment.setArguments(args); - return fragment; - } - - @Override - public Context getContext() { - return getActivity(); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreateDialog(savedInstanceState); - final boolean isBlocked = getArguments().containsKey(ARG_BLOCK_ID); - - mNumber = getArguments().getString(ARG_NUMBER); - mDisplayNumber = getArguments().getString(ARG_DISPLAY_NUMBER); - mCountryIso = getArguments().getString(ARG_COUNTRY_ISO); - - if (TextUtils.isEmpty(mDisplayNumber)) { - mDisplayNumber = mNumber; - } - - mHandler = new FilteredNumberAsyncQueryHandler(getContext().getContentResolver()); - mVoicemailEnabledChecker = new VisualVoicemailEnabledChecker(getActivity(), null); - /** - * Choose not to update VoicemailEnabledChecker, as checks should already been done in - * all current use cases. - */ - mParentView = getActivity().findViewById(getArguments().getInt(ARG_PARENT_VIEW_ID)); - - CharSequence title; - String okText; - String message; - if (isBlocked) { - title = null; - okText = getString(R.string.unblock_number_ok); - message = ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(), - R.string.unblock_number_confirmation_title, - mDisplayNumber).toString(); - } else { - title = ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(), - R.string.block_number_confirmation_title, - mDisplayNumber); - okText = getString(R.string.block_number_ok); - if (FilteredNumberCompat.useNewFiltering()) { - message = getString(R.string.block_number_confirmation_message_new_filtering); - } else if (mVoicemailEnabledChecker.isVisualVoicemailEnabled()) { - message = getString(R.string.block_number_confirmation_message_vvm); - } else { - message = getString(R.string.block_number_confirmation_message_no_vvm); - } - } - - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(title) - .setMessage(message) - .setPositiveButton(okText, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - if (isBlocked) { - unblockNumber(); - } else { - blockNumber(); - } - } - }) - .setNegativeButton(android.R.string.cancel, null); - return builder.create(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (!FilteredNumbersUtil.canBlockNumber(getActivity(), mNumber, mCountryIso)) { - dismiss(); - Toast.makeText(getContext(), - ContactDisplayUtils.getTtsSpannedPhoneNumber( - getResources(), R.string.invalidNumber, mDisplayNumber), - Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onPause() { - // Dismiss on rotation. - dismiss(); - mCallback = null; - - super.onPause(); - } - - public void setCallback(Callback callback) { - mCallback = callback; - } - - private CharSequence getBlockedMessage() { - return ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(), - R.string.snackbar_number_blocked, mDisplayNumber); - } - - private CharSequence getUnblockedMessage() { - return ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(), - R.string.snackbar_number_unblocked, mDisplayNumber); - } - - private int getActionTextColor() { - return getContext().getResources().getColor(R.color.dialer_snackbar_action_text_color); - } - - private void blockNumber() { - final CharSequence message = getBlockedMessage(); - final CharSequence undoMessage = getUnblockedMessage(); - final Callback callback = mCallback; - final int actionTextColor = getActionTextColor(); - final Context context = getContext(); - - final OnUnblockNumberListener onUndoListener = new OnUnblockNumberListener() { - @Override - public void onUnblockComplete(int rows, ContentValues values) { - Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show(); - if (callback != null) { - callback.onChangeFilteredNumberUndo(); - } - } - }; - - final OnBlockNumberListener onBlockNumberListener = new OnBlockNumberListener() { - @Override - public void onBlockComplete(final Uri uri) { - final View.OnClickListener undoListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - // Delete the newly created row on 'undo'. - Logger.logInteraction(InteractionEvent.UNDO_BLOCK_NUMBER); - mHandler.unblock(onUndoListener, uri); - } - }; - - Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG) - .setAction(R.string.block_number_undo, undoListener) - .setActionTextColor(actionTextColor) - .show(); - - if (callback != null) { - callback.onFilterNumberSuccess(); - } - - if (context != null && FilteredNumbersUtil.hasRecentEmergencyCall(context)) { - FilteredNumbersUtil.maybeNotifyCallBlockingDisabled(context); - } - } - }; - - mHandler.blockNumber( - onBlockNumberListener, - mNumber, - mCountryIso); - } - - private void unblockNumber() { - final CharSequence message = getUnblockedMessage(); - final CharSequence undoMessage = getBlockedMessage(); - final Callback callback = mCallback; - final int actionTextColor = getActionTextColor(); - - final OnBlockNumberListener onUndoListener = new OnBlockNumberListener() { - @Override - public void onBlockComplete(final Uri uri) { - Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show(); - if (callback != null) { - callback.onChangeFilteredNumberUndo(); - } - } - }; - - mHandler.unblock(new OnUnblockNumberListener() { - @Override - public void onUnblockComplete(int rows, final ContentValues values) { - final View.OnClickListener undoListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - // Re-insert the row on 'undo', with a new ID. - Logger.logInteraction(InteractionEvent.UNDO_UNBLOCK_NUMBER); - mHandler.blockNumber(onUndoListener, values); - } - }; - - Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG) - .setAction(R.string.block_number_undo, undoListener) - .setActionTextColor(actionTextColor) - .show(); - - if (callback != null) { - callback.onUnfilterNumberSuccess(); - } - } - }, getArguments().getInt(ARG_BLOCK_ID)); - } -} diff --git a/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.java b/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.java deleted file mode 100644 index 10a4f5abd..000000000 --- a/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.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.filterednumber; - -import android.app.FragmentManager; -import android.content.Context; -import android.database.Cursor; -import android.telephony.PhoneNumberUtils; -import android.view.View; - -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.GeoUtil; -import com.android.dialer.R; -import com.android.dialer.calllog.ContactInfoHelper; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; - -public class BlockedNumbersAdapter extends NumbersAdapter { - - private BlockedNumbersAdapter( - Context context, - FragmentManager fragmentManager, - ContactInfoHelper contactInfoHelper, - ContactPhotoManager contactPhotoManager) { - super(context, fragmentManager, contactInfoHelper, contactPhotoManager); - } - - public static BlockedNumbersAdapter newBlockedNumbersAdapter( - Context context, FragmentManager fragmentManager) { - return new BlockedNumbersAdapter( - context, - fragmentManager, - new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)), - ContactPhotoManager.getInstance(context)); - } - - @Override - public void bindView(View view, final Context context, Cursor cursor) { - super.bindView(view, context, cursor); - final Integer id = cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID)); - final String countryIso = cursor.getString(cursor.getColumnIndex( - FilteredNumberColumns.COUNTRY_ISO)); - final String number = cursor.getString(cursor.getColumnIndex(FilteredNumberColumns.NUMBER)); - final String normalizedNumber = cursor.getString(cursor.getColumnIndex( - FilteredNumberColumns.NORMALIZED_NUMBER)); - - final View deleteButton = view.findViewById(R.id.delete_button); - deleteButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - BlockNumberDialogFragment.show( - id, - number, - countryIso, - PhoneNumberUtils.formatNumber(number, countryIso), - R.id.blocked_numbers_activity_container, - getFragmentManager(), - new BlockNumberDialogFragment.Callback() { - @Override - public void onFilterNumberSuccess() {} - - @Override - public void onUnfilterNumberSuccess() { - Logger.logInteraction( - InteractionEvent.UNBLOCK_NUMBER_MANAGEMENT_SCREEN); - } - - @Override - public void onChangeFilteredNumberUndo() {} - }); - } - }); - - updateView(view, number, countryIso); - } - - @Override - public boolean isEmpty() { - // Always return false, so that the header with blocking-related options always shows. - return false; - } -} diff --git a/src/com/android/dialer/filterednumber/BlockedNumbersAutoMigrator.java b/src/com/android/dialer/filterednumber/BlockedNumbersAutoMigrator.java deleted file mode 100644 index ed0faabbe..000000000 --- a/src/com/android/dialer/filterednumber/BlockedNumbersAutoMigrator.java +++ /dev/null @@ -1,101 +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.filterednumber; - -import com.google.common.base.Preconditions; - -import android.content.SharedPreferences; -import android.util.Log; - -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnHasBlockedNumbersListener; - -/** - * Class responsible for checking if the user can be auto-migrated to {@link - * android.provider.BlockedNumberContract} blocking. In order for this to happen, the user cannot - * have any numbers that are blocked in the Dialer solution. - */ -public class BlockedNumbersAutoMigrator { - - private static final String TAG = "BlockedNumbersAuto"; - - private static final String HAS_CHECKED_AUTO_MIGRATE_KEY = "checkedAutoMigrate"; - - private final SharedPreferences sharedPreferences; - private final FilteredNumberAsyncQueryHandler queryHandler; - - /** - * Constructs the BlockedNumbersAutoMigrator with the given {@link SharedPreferences} and {@link - * FilteredNumberAsyncQueryHandler}. - * - * @param sharedPreferences The SharedPreferences used to persist information. - * @param queryHandler The FilteredNumberAsyncQueryHandler used to determine if there are - * blocked numbers. - * @throws NullPointerException if sharedPreferences or queryHandler are null. - */ - public BlockedNumbersAutoMigrator(SharedPreferences sharedPreferences, - FilteredNumberAsyncQueryHandler queryHandler) { - this.sharedPreferences = Preconditions.checkNotNull(sharedPreferences); - this.queryHandler = Preconditions.checkNotNull(queryHandler); - } - - /** - * Attempts to perform the auto-migration. Auto-migration will only be attempted once and can be - * performed only when the user has no blocked numbers. As a result of this method, the user - * will be migrated to the framework blocking solution, as determined by {@link - * FilteredNumberCompat#hasMigratedToNewBlocking()}. - */ - public void autoMigrate() { - if (!shouldAttemptAutoMigrate()) { - return; - } - - Log.i(TAG, "Attempting to auto-migrate."); - queryHandler.hasBlockedNumbers(new OnHasBlockedNumbersListener() { - @Override - public void onHasBlockedNumbers(boolean hasBlockedNumbers) { - if (hasBlockedNumbers) { - Log.i(TAG, "Not auto-migrating: blocked numbers exist."); - return; - } - Log.i(TAG, "Auto-migrating: no blocked numbers."); - FilteredNumberCompat.setHasMigratedToNewBlocking(true); - } - }); - } - - private boolean shouldAttemptAutoMigrate() { - if (sharedPreferences.contains(HAS_CHECKED_AUTO_MIGRATE_KEY)) { - Log.d(TAG, "Not attempting auto-migrate: already checked once."); - return false; - } - Log.i(TAG, "Updating state as already checked for auto-migrate."); - sharedPreferences.edit().putBoolean(HAS_CHECKED_AUTO_MIGRATE_KEY, true).apply(); - - if (!FilteredNumberCompat.canUseNewFiltering()) { - Log.i(TAG, "Not attempting auto-migrate: not available."); - return false; - } - - if (FilteredNumberCompat.hasMigratedToNewBlocking()) { - Log.i(TAG, "Not attempting auto-migrate: already migrated."); - return false; - } - return true; - } -} diff --git a/src/com/android/dialer/filterednumber/BlockedNumbersFragment.java b/src/com/android/dialer/filterednumber/BlockedNumbersFragment.java deleted file mode 100644 index b64f18691..000000000 --- a/src/com/android/dialer/filterednumber/BlockedNumbersFragment.java +++ /dev/null @@ -1,264 +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.filterednumber; - -import com.google.common.base.MoreObjects; - -import android.app.ListFragment; -import android.app.LoaderManager; -import android.content.Context; -import android.content.CursorLoader; -import android.content.Loader; -import android.database.Cursor; -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.contacts.common.lettertiles.LetterTileDrawable; -import com.android.contacts.common.testing.NeededForTesting; -import com.android.dialer.R; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberContract; -import com.android.dialer.filterednumber.BlockedNumbersMigrator.Listener; -import com.android.dialer.filterednumber.FilteredNumbersUtil.CheckForSendToVoicemailContactListener; -import com.android.dialer.filterednumber.FilteredNumbersUtil.ImportSendToVoicemailContactsListener; -import com.android.dialer.voicemail.VisualVoicemailEnabledChecker; - -public class BlockedNumbersFragment extends ListFragment - implements LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener, - VisualVoicemailEnabledChecker.Callback { - private static final char ADD_BLOCKED_NUMBER_ICON_LETTER = '+'; - - private BlockedNumbersMigrator blockedNumbersMigratorForTest; - protected View migratePromoView; - private TextView blockedNumbersText; - private TextView footerText; - private BlockedNumbersAdapter mAdapter; - private VisualVoicemailEnabledChecker mVoicemailEnabledChecker; - private View mImportSettings; - private View mBlockedNumbersDisabledForEmergency; - private View mBlockedNumberListDivider; - - @Override - public Context getContext() { - return getActivity(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - LayoutInflater inflater = - (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null)); - getListView().addFooterView(inflater.inflate(R.layout.blocked_number_footer, null)); - //replace the icon for add number with LetterTileDrawable(), so it will have identical style - ImageView addNumberIcon = (ImageView) getActivity().findViewById(R.id.add_number_icon); - LetterTileDrawable drawable = new LetterTileDrawable(getResources()); - drawable.setLetter(ADD_BLOCKED_NUMBER_ICON_LETTER); - drawable.setColor(ActivityCompat.getColor(getActivity(), - R.color.add_blocked_number_icon_color)); - drawable.setIsCircular(true); - addNumberIcon.setImageDrawable(drawable); - - if (mAdapter == null) { - mAdapter = BlockedNumbersAdapter.newBlockedNumbersAdapter( - getContext(), getActivity().getFragmentManager()); - } - setListAdapter(mAdapter); - - blockedNumbersText = (TextView) getListView().findViewById(R.id.blocked_number_text_view); - migratePromoView = getListView().findViewById(R.id.migrate_promo); - getListView().findViewById(R.id.migrate_promo_allow_button).setOnClickListener(this); - mImportSettings = getListView().findViewById(R.id.import_settings); - mBlockedNumbersDisabledForEmergency = - getListView().findViewById(R.id.blocked_numbers_disabled_for_emergency); - mBlockedNumberListDivider = getActivity().findViewById(R.id.blocked_number_list_divider); - getListView().findViewById(R.id.import_button).setOnClickListener(this); - getListView().findViewById(R.id.view_numbers_button).setOnClickListener(this); - getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(this); - - footerText = (TextView) getActivity().findViewById( - R.id.blocked_number_footer_textview); - mVoicemailEnabledChecker = new VisualVoicemailEnabledChecker(getContext(),this); - mVoicemailEnabledChecker.asyncUpdate(); - updateActiveVoicemailProvider(); - } - - @Override - public void onDestroy() { - setListAdapter(null); - super.onDestroy(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getLoaderManager().initLoader(0, null, this); - } - - @Override - public void onResume() { - super.onResume(); - - ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - ColorDrawable backgroundDrawable = new ColorDrawable( - ActivityCompat.getColor(getActivity(), R.color.dialer_theme_color)); - actionBar.setBackgroundDrawable(backgroundDrawable); - actionBar.setDisplayShowCustomEnabled(false); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setTitle(R.string.manage_blocked_numbers_label); - - // If the device can use the framework blocking solution, users should not be able to add - // new blocked numbers from the Blocked Management UI. They will be shown a promo card - // asking them to migrate to new blocking instead. - if (FilteredNumberCompat.canUseNewFiltering()) { - migratePromoView.setVisibility(View.VISIBLE); - blockedNumbersText.setVisibility(View.GONE); - getListView().findViewById(R.id.add_number_linear_layout).setVisibility(View.GONE); - getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(null); - mBlockedNumberListDivider.setVisibility(View.GONE); - mImportSettings.setVisibility(View.GONE); - getListView().findViewById(R.id.import_button).setOnClickListener(null); - getListView().findViewById(R.id.view_numbers_button).setOnClickListener(null); - mBlockedNumbersDisabledForEmergency.setVisibility(View.GONE); - footerText.setVisibility(View.GONE); - } else { - FilteredNumbersUtil.checkForSendToVoicemailContact( - getActivity(), new CheckForSendToVoicemailContactListener() { - @Override - public void onComplete(boolean hasSendToVoicemailContact) { - final int visibility = - hasSendToVoicemailContact ? View.VISIBLE : View.GONE; - mImportSettings.setVisibility(visibility); - } - }); - } - - // All views except migrate and the block list are hidden when new filtering is available - if (!FilteredNumberCompat.canUseNewFiltering() - && FilteredNumbersUtil.hasRecentEmergencyCall(getContext())) { - mBlockedNumbersDisabledForEmergency.setVisibility(View.VISIBLE); - } else { - mBlockedNumbersDisabledForEmergency.setVisibility(View.GONE); - } - - mVoicemailEnabledChecker.asyncUpdate(); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.blocked_number_fragment, container, false); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - final String[] projection = { - FilteredNumberContract.FilteredNumberColumns._ID, - FilteredNumberContract.FilteredNumberColumns.COUNTRY_ISO, - FilteredNumberContract.FilteredNumberColumns.NUMBER, - FilteredNumberContract.FilteredNumberColumns.NORMALIZED_NUMBER - }; - final String selection = FilteredNumberContract.FilteredNumberColumns.TYPE - + "=" + FilteredNumberContract.FilteredNumberTypes.BLOCKED_NUMBER; - return new CursorLoader( - getContext(), FilteredNumberContract.FilteredNumber.CONTENT_URI, projection, - selection, null, null); - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - mAdapter.swapCursor(data); - if (FilteredNumberCompat.canUseNewFiltering() || data.getCount() == 0) { - mBlockedNumberListDivider.setVisibility(View.INVISIBLE); - } else { - mBlockedNumberListDivider.setVisibility(View.VISIBLE); - } - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - mAdapter.swapCursor(null); - } - - @Override - public void onClick(final View view) { - final BlockedNumbersSettingsActivity activity = - (BlockedNumbersSettingsActivity) getActivity(); - if (activity == null) { - return; - } - - int resId = view.getId(); - if (resId == R.id.add_number_linear_layout) { - activity.showSearchUi(); - } else if (resId == R.id.view_numbers_button) { - activity.showNumbersToImportPreviewUi(); - } else if (resId == R.id.import_button) { - FilteredNumbersUtil.importSendToVoicemailContacts(activity, - new ImportSendToVoicemailContactsListener() { - @Override - public void onImportComplete() { - mImportSettings.setVisibility(View.GONE); - } - }); - } else if (resId == R.id.migrate_promo_allow_button) { - view.setEnabled(false); - MoreObjects.firstNonNull(blockedNumbersMigratorForTest, - new BlockedNumbersMigrator(getContext().getContentResolver())) - .migrate(new Listener() { - @Override - public void onComplete() { - getContext().startActivity( - FilteredNumberCompat.createManageBlockedNumbersIntent(getContext())); - // Remove this activity from the backstack - activity.finish(); - } - }); - } - } - - @Override - public void onVisualVoicemailEnabledStatusChanged(boolean newStatus){ - updateActiveVoicemailProvider(); - } - - private void updateActiveVoicemailProvider(){ - if (getActivity() == null || getActivity().isFinishing()) { - return; - } - if (mVoicemailEnabledChecker.isVisualVoicemailEnabled()) { - footerText.setText(R.string.block_number_footer_message_vvm); - } else { - footerText.setText(R.string.block_number_footer_message_no_vvm); - } - } - - @NeededForTesting - void setBlockedNumbersMigratorForTest(BlockedNumbersMigrator blockedNumbersMigrator) { - blockedNumbersMigratorForTest = blockedNumbersMigrator; - } -} diff --git a/src/com/android/dialer/filterednumber/BlockedNumbersMigrator.java b/src/com/android/dialer/filterednumber/BlockedNumbersMigrator.java deleted file mode 100644 index 373403046..000000000 --- a/src/com/android/dialer/filterednumber/BlockedNumbersMigrator.java +++ /dev/null @@ -1,135 +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.filterednumber; - -import com.google.common.base.Preconditions; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.database.Cursor; -import android.os.AsyncTask; - -import com.android.dialer.compat.BlockedNumbersSdkCompat; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberContract; -import com.android.dialer.database.FilteredNumberContract.FilteredNumber; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.incallui.Log; - -/** - * Class which should be used to migrate numbers from {@link FilteredNumberContract} blocking to - * {@link android.provider.BlockedNumberContract} blocking. - */ -public class BlockedNumbersMigrator { - - private static final String TAG = "BlockedNumbersMigrator"; - - /** - * Listener for the operation to migrate from {@link FilteredNumberContract} blocking to - * {@link android.provider.BlockedNumberContract} blocking. - */ - public interface Listener { - - /** - * Called when the migration operation is finished. - */ - void onComplete(); - } - - private final ContentResolver mContentResolver; - - /** - * Creates a new BlockedNumbersMigrate, using the given {@link ContentResolver} to perform - * queries against the blocked numbers tables. - * - * @param contentResolver The ContentResolver - * @throws NullPointerException if contentResolver is null - */ - public BlockedNumbersMigrator(ContentResolver contentResolver) { - mContentResolver = Preconditions.checkNotNull(contentResolver); - } - - /** - * Copies all of the numbers in the {@link FilteredNumberContract} block list to the - * {@link android.provider.BlockedNumberContract} block list. - * - * @param listener {@link Listener} called once the migration is complete. - * @return {@code true} if the migrate can be attempted, {@code false} otherwise. - * @throws NullPointerException if listener is null - */ - public boolean migrate(final Listener listener) { - Log.i(TAG, "migrate - start"); - if (!FilteredNumberCompat.canUseNewFiltering()) { - Log.i(TAG, "migrate - can't use new filtering"); - return false; - } - Preconditions.checkNotNull(listener); - new AsyncTask<Void, Void, Boolean>() { - @Override - protected Boolean doInBackground(Void... params) { - Log.i(TAG, "migrate - start background migration"); - return migrateToNewBlockingInBackground(mContentResolver); - } - - @Override - protected void onPostExecute(Boolean isSuccessful) { - Log.i(TAG, "migrate - marking migration complete"); - FilteredNumberCompat.setHasMigratedToNewBlocking(isSuccessful); - Log.i(TAG, "migrate - calling listener"); - listener.onComplete(); - } - }.execute(); - return true; - } - - private static boolean migrateToNewBlockingInBackground(ContentResolver resolver) { - try (Cursor cursor = resolver.query(FilteredNumber.CONTENT_URI, - new String[]{FilteredNumberColumns.NUMBER}, null, null, null)) { - if (cursor == null) { - Log.i(TAG, "migrate - cursor was null"); - return false; - } - - Log.i(TAG, "migrate - attempting to migrate " + cursor.getCount() + "numbers"); - - int numMigrated = 0; - while (cursor.moveToNext()) { - String originalNumber = cursor - .getString(cursor.getColumnIndex(FilteredNumberColumns.NUMBER)); - if (isNumberInNewBlocking(resolver, originalNumber)) { - Log.i(TAG, "migrate - number was already blocked in new blocking"); - continue; - } - ContentValues values = new ContentValues(); - values.put(BlockedNumbersSdkCompat.COLUMN_ORIGINAL_NUMBER, originalNumber); - resolver.insert(BlockedNumbersSdkCompat.CONTENT_URI, values); - ++numMigrated; - } - Log.i(TAG, "migrate - migration complete. " + numMigrated + " numbers migrated."); - return true; - } - } - - private static boolean isNumberInNewBlocking(ContentResolver resolver, String originalNumber) { - try (Cursor cursor = resolver.query(BlockedNumbersSdkCompat.CONTENT_URI, - new String[]{BlockedNumbersSdkCompat._ID}, - BlockedNumbersSdkCompat.COLUMN_ORIGINAL_NUMBER + " = ?", - new String[] {originalNumber}, null)) { - return cursor != null && cursor.getCount() != 0; - } - } -} diff --git a/src/com/android/dialer/filterednumber/BlockedNumbersSettingsActivity.java b/src/com/android/dialer/filterednumber/BlockedNumbersSettingsActivity.java deleted file mode 100644 index 5ce9d21f1..000000000 --- a/src/com/android/dialer/filterednumber/BlockedNumbersSettingsActivity.java +++ /dev/null @@ -1,162 +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.filterednumber; - -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.MenuItem; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; -import android.widget.Toast; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.dialog.IndeterminateProgressDialog; -import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -import com.android.dialer.R; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.list.BlockedListSearchAdapter; -import com.android.dialer.list.OnListFragmentScrolledListener; -import com.android.dialer.list.BlockedListSearchFragment; -import com.android.dialer.list.SearchFragment; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; - -public class BlockedNumbersSettingsActivity extends AppCompatActivity - implements SearchFragment.HostInterface { - - private static final String TAG_BLOCKED_MANAGEMENT_FRAGMENT = "blocked_management"; - private static final String TAG_BLOCKED_SEARCH_FRAGMENT = "blocked_search"; - private static final String TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT = "view_numbers_to_import"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.blocked_numbers_activity); - - // If savedInstanceState != null, the Activity will automatically restore the last fragment. - if (savedInstanceState == null) { - showManagementUi(); - } - } - - /** - * Shows fragment with the list of currently blocked numbers and settings related to blocking. - */ - public void showManagementUi() { - BlockedNumbersFragment fragment = (BlockedNumbersFragment) getFragmentManager() - .findFragmentByTag(TAG_BLOCKED_MANAGEMENT_FRAGMENT); - if (fragment == null) { - fragment = new BlockedNumbersFragment(); - } - - getFragmentManager().beginTransaction() - .replace(R.id.blocked_numbers_activity_container, fragment, - TAG_BLOCKED_MANAGEMENT_FRAGMENT) - .commit(); - - Logger.logScreenView(ScreenEvent.BLOCKED_NUMBER_MANAGEMENT, this); - } - - /** - * Shows fragment with search UI for browsing/finding numbers to block. - */ - public void showSearchUi() { - BlockedListSearchFragment fragment = (BlockedListSearchFragment) getFragmentManager() - .findFragmentByTag(TAG_BLOCKED_SEARCH_FRAGMENT); - if (fragment == null) { - fragment = new BlockedListSearchFragment(); - fragment.setHasOptionsMenu(false); - fragment.setShowEmptyListForNullQuery(true); - fragment.setDirectorySearchEnabled(false); - } - - getFragmentManager().beginTransaction() - .replace(R.id.blocked_numbers_activity_container, fragment, - TAG_BLOCKED_SEARCH_FRAGMENT) - .addToBackStack(null) - .commit(); - - Logger.logScreenView(ScreenEvent.BLOCKED_NUMBER_ADD_NUMBER, this); - } - - /** - * Shows fragment with UI to preview the numbers of contacts currently marked as - * send-to-voicemail in Contacts. These numbers can be imported into Dialer's blocked number - * list. - */ - public void showNumbersToImportPreviewUi() { - ViewNumbersToImportFragment fragment = (ViewNumbersToImportFragment) getFragmentManager() - .findFragmentByTag(TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT); - if (fragment == null) { - fragment = new ViewNumbersToImportFragment(); - } - - getFragmentManager().beginTransaction() - .replace(R.id.blocked_numbers_activity_container, fragment, - TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT) - .addToBackStack(null) - .commit(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - return false; - } - - @Override - public void onBackPressed() { - // TODO: Achieve back navigation without overriding onBackPressed. - if (getFragmentManager().getBackStackEntryCount() > 0) { - getFragmentManager().popBackStack(); - } else { - super.onBackPressed(); - } - } - - @Override - public boolean isActionBarShowing() { - return false; - } - - @Override - public boolean isDialpadShown() { - return false; - } - - @Override - public int getDialpadHeight() { - return 0; - } - - @Override - public int getActionBarHideOffset() { - return 0; - } - - @Override - public int getActionBarHeight() { - return 0; - } -} diff --git a/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java deleted file mode 100644 index 35d6f8d25..000000000 --- a/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java +++ /dev/null @@ -1,369 +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.filterednumber; - -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.database.Cursor; -import android.os.AsyncTask; -import android.preference.PreferenceManager; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.provider.Settings; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.widget.Toast; - -import com.android.contacts.common.testing.NeededForTesting; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.R; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnHasBlockedNumbersListener; -import com.android.dialer.database.FilteredNumberContract.FilteredNumber; -import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; - -import java.util.concurrent.TimeUnit; - -/** - * Utility to help with tasks related to filtered numbers. - */ -public class FilteredNumbersUtil { - - // Disable incoming call blocking if there was a call within the past 2 days. - private static final long RECENT_EMERGENCY_CALL_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 2; - - // Pref key for storing the time of end of the last emergency call in milliseconds after epoch. - protected static final String LAST_EMERGENCY_CALL_MS_PREF_KEY = "last_emergency_call_ms"; - - // Pref key for storing whether a notification has been dispatched to notify the user that call - // blocking has been disabled because of a recent emergency call. - protected static final String NOTIFIED_CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_PREF_KEY = - "notified_call_blocking_disabled_by_emergency_call"; - - public static final String CALL_BLOCKING_NOTIFICATION_TAG = "call_blocking"; - public static final int CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_NOTIFICATION_ID = 10; - - /** - * Used for testing to specify that a custom threshold should be used instead of the default. - * This custom threshold will only be used when setting this log tag to VERBOSE: - * - * adb shell setprop log.tag.DebugEmergencyCall VERBOSE - * - */ - @NeededForTesting - private static final String DEBUG_EMERGENCY_CALL_TAG = "DebugEmergencyCall"; - - /** - * Used for testing to specify the custom threshold value, in milliseconds for whether an - * emergency call is "recent". The default value will be used if this custom threshold is less - * than zero. For example, to set this threshold to 60 seconds: - * - * adb shell settings put system dialer_emergency_call_threshold_ms 60000 - * - */ - @NeededForTesting - private static final String RECENT_EMERGENCY_CALL_THRESHOLD_SETTINGS_KEY = - "dialer_emergency_call_threshold_ms"; - - public interface CheckForSendToVoicemailContactListener { - public void onComplete(boolean hasSendToVoicemailContact); - } - - public interface ImportSendToVoicemailContactsListener { - public void onImportComplete(); - } - - private static class ContactsQuery { - static final String[] PROJECTION = { - Contacts._ID - }; - - static final String SELECT_SEND_TO_VOICEMAIL_TRUE = Contacts.SEND_TO_VOICEMAIL + "=1"; - - static final int ID_COLUMN_INDEX = 0; - } - - public static class PhoneQuery { - static final String[] PROJECTION = { - Contacts._ID, - Phone.NORMALIZED_NUMBER, - Phone.NUMBER - }; - - static final int ID_COLUMN_INDEX = 0; - static final int NORMALIZED_NUMBER_COLUMN_INDEX = 1; - static final int NUMBER_COLUMN_INDEX = 2; - - static final String SELECT_SEND_TO_VOICEMAIL_TRUE = Contacts.SEND_TO_VOICEMAIL + "=1"; - } - - /** - * Checks if there exists a contact with {@code Contacts.SEND_TO_VOICEMAIL} set to true. - */ - public static void checkForSendToVoicemailContact( - final Context context, final CheckForSendToVoicemailContactListener listener) { - final AsyncTask task = new AsyncTask<Object, Void, Boolean>() { - @Override - public Boolean doInBackground(Object[] params) { - if (context == null || !PermissionsUtil.hasContactsPermissions(context)) { - return false; - } - - final Cursor cursor = context.getContentResolver().query( - Contacts.CONTENT_URI, - ContactsQuery.PROJECTION, - ContactsQuery.SELECT_SEND_TO_VOICEMAIL_TRUE, - null, - null); - - boolean hasSendToVoicemailContacts = false; - if (cursor != null) { - try { - hasSendToVoicemailContacts = cursor.getCount() > 0; - } finally { - cursor.close(); - } - } - - return hasSendToVoicemailContacts; - } - - @Override - public void onPostExecute(Boolean hasSendToVoicemailContact) { - if (listener != null) { - listener.onComplete(hasSendToVoicemailContact); - } - } - }; - task.execute(); - } - - /** - * Blocks all the phone numbers of any contacts marked as SEND_TO_VOICEMAIL, then clears the - * SEND_TO_VOICEMAIL flag on those contacts. - */ - public static void importSendToVoicemailContacts( - final Context context, final ImportSendToVoicemailContactsListener listener) { - Logger.logInteraction(InteractionEvent.IMPORT_SEND_TO_VOICEMAIL); - final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler = - new FilteredNumberAsyncQueryHandler(context.getContentResolver()); - - final AsyncTask<Object, Void, Boolean> task = new AsyncTask<Object, Void, Boolean>() { - @Override - public Boolean doInBackground(Object[] params) { - if (context == null) { - return false; - } - - // Get the phone number of contacts marked as SEND_TO_VOICEMAIL. - final Cursor phoneCursor = context.getContentResolver().query( - Phone.CONTENT_URI, - PhoneQuery.PROJECTION, - PhoneQuery.SELECT_SEND_TO_VOICEMAIL_TRUE, - null, - null); - - if (phoneCursor == null) { - return false; - } - - try { - while (phoneCursor.moveToNext()) { - final String normalizedNumber = phoneCursor.getString( - PhoneQuery.NORMALIZED_NUMBER_COLUMN_INDEX); - final String number = phoneCursor.getString( - PhoneQuery.NUMBER_COLUMN_INDEX); - if (normalizedNumber != null) { - // Block the phone number of the contact. - mFilteredNumberAsyncQueryHandler.blockNumber( - null, normalizedNumber, number, null); - } - } - } finally { - phoneCursor.close(); - } - - // Clear SEND_TO_VOICEMAIL on all contacts. The setting has been imported to Dialer. - ContentValues newValues = new ContentValues(); - newValues.put(Contacts.SEND_TO_VOICEMAIL, 0); - context.getContentResolver().update( - Contacts.CONTENT_URI, - newValues, - ContactsQuery.SELECT_SEND_TO_VOICEMAIL_TRUE, - null); - - return true; - } - - @Override - public void onPostExecute(Boolean success) { - if (success) { - if (listener != null) { - listener.onImportComplete(); - } - } else if (context != null) { - String toastStr = context.getString(R.string.send_to_voicemail_import_failed); - Toast.makeText(context, toastStr, Toast.LENGTH_SHORT).show(); - } - } - }; - task.execute(); - } - - /** - * WARNING: This method should NOT be executed on the UI thread. - * Use {@code FilteredNumberAsyncQueryHandler} to asynchronously check if a number is blocked. - */ - public static boolean shouldBlockVoicemail( - Context context, String number, String countryIso, long voicemailDateMs) { - final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); - if (TextUtils.isEmpty(normalizedNumber)) { - return false; - } - - if (hasRecentEmergencyCall(context)) { - return false; - } - - final Cursor cursor = context.getContentResolver().query( - FilteredNumber.CONTENT_URI, - new String[] { - FilteredNumberColumns.CREATION_TIME - }, - FilteredNumberColumns.NORMALIZED_NUMBER + "=?", - new String[] { normalizedNumber }, - null); - if (cursor == null) { - return false; - } - try { - /* - * Block if number is found and it was added before this voicemail was received. - * The VVM's date is reported with precision to the minute, even though its - * magnitude is in milliseconds, so we perform the comparison in minutes. - */ - return cursor.moveToFirst() && - TimeUnit.MINUTES.convert(voicemailDateMs, TimeUnit.MILLISECONDS) >= - TimeUnit.MINUTES.convert(cursor.getLong(0), TimeUnit.MILLISECONDS); - } finally { - cursor.close(); - } - } - - public static boolean hasRecentEmergencyCall(Context context) { - if (context == null) { - return false; - } - - Long lastEmergencyCallTime = PreferenceManager.getDefaultSharedPreferences(context) - .getLong(LAST_EMERGENCY_CALL_MS_PREF_KEY, 0); - if (lastEmergencyCallTime == 0) { - return false; - } - - return (System.currentTimeMillis() - lastEmergencyCallTime) - < getRecentEmergencyCallThresholdMs(context); - } - - public static void recordLastEmergencyCallTime(Context context) { - if (context == null) { - return; - } - - PreferenceManager.getDefaultSharedPreferences(context) - .edit() - .putLong(LAST_EMERGENCY_CALL_MS_PREF_KEY, System.currentTimeMillis()) - .putBoolean(NOTIFIED_CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_PREF_KEY, false) - .apply(); - - maybeNotifyCallBlockingDisabled(context); - } - - public static void maybeNotifyCallBlockingDisabled(final Context context) { - // The Dialer is not responsible for this notification after migrating - if (FilteredNumberCompat.useNewFiltering()) { - return; - } - // Skip if the user has already received a notification for the most recent emergency call. - if (PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(NOTIFIED_CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_PREF_KEY, false)) { - return; - } - - // If the user has blocked numbers, notify that call blocking is temporarily disabled. - FilteredNumberAsyncQueryHandler queryHandler = - new FilteredNumberAsyncQueryHandler(context.getContentResolver()); - queryHandler.hasBlockedNumbers(new OnHasBlockedNumbersListener() { - @Override - public void onHasBlockedNumbers(boolean hasBlockedNumbers) { - if (context == null || !hasBlockedNumbers) { - return; - } - - NotificationManager notificationManager = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - Notification.Builder builder = new Notification.Builder(context) - .setSmallIcon(R.drawable.ic_block_24dp) - .setContentTitle(context.getString( - R.string.call_blocking_disabled_notification_title)) - .setContentText(context.getString( - R.string.call_blocking_disabled_notification_text)) - .setAutoCancel(true); - - final Intent contentIntent = - new Intent(context, BlockedNumbersSettingsActivity.class); - builder.setContentIntent(PendingIntent.getActivity( - context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - - notificationManager.notify( - CALL_BLOCKING_NOTIFICATION_TAG, - CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_NOTIFICATION_ID, - builder.build()); - - // Record that the user has been notified for this emergency call. - PreferenceManager.getDefaultSharedPreferences(context) - .edit() - .putBoolean(NOTIFIED_CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_PREF_KEY, true) - .apply(); - } - }); - } - - public static boolean canBlockNumber(Context context, String number, String countryIso) { - final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); - return !TextUtils.isEmpty(normalizedNumber) - && !PhoneNumberUtils.isEmergencyNumber(normalizedNumber); - } - - private static long getRecentEmergencyCallThresholdMs(Context context) { - if (android.util.Log.isLoggable( - DEBUG_EMERGENCY_CALL_TAG, android.util.Log.VERBOSE)) { - long thresholdMs = Settings.System.getLong( - context.getContentResolver(), - RECENT_EMERGENCY_CALL_THRESHOLD_SETTINGS_KEY, 0); - return thresholdMs > 0 ? thresholdMs : RECENT_EMERGENCY_CALL_THRESHOLD_MS; - } else { - return RECENT_EMERGENCY_CALL_THRESHOLD_MS; - } - } -} diff --git a/src/com/android/dialer/filterednumber/MigrateBlockedNumbersDialogFragment.java b/src/com/android/dialer/filterednumber/MigrateBlockedNumbersDialogFragment.java deleted file mode 100644 index 209665292..000000000 --- a/src/com/android/dialer/filterednumber/MigrateBlockedNumbersDialogFragment.java +++ /dev/null @@ -1,110 +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.filterednumber; - -import com.google.common.base.Preconditions; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.content.DialogInterface.OnShowListener; -import android.os.Bundle; -import android.view.View; -import com.android.dialer.R; -import com.android.dialer.filterednumber.BlockedNumbersMigrator.Listener; - -/** - * Dialog fragment shown to users when they need to migrate to use - * {@link android.provider.BlockedNumberContract} for blocking. - */ -public class MigrateBlockedNumbersDialogFragment extends DialogFragment { - - private BlockedNumbersMigrator mBlockedNumbersMigrator; - private BlockedNumbersMigrator.Listener mMigrationListener; - - /** - * Creates a new MigrateBlockedNumbersDialogFragment. - * - * @param blockedNumbersMigrator The {@link BlockedNumbersMigrator} which will be used to - * migrate the numbers. - * @param migrationListener The {@link BlockedNumbersMigrator.Listener} to call when the - * migration is complete. - * @return The new MigrateBlockedNumbersDialogFragment. - * @throws NullPointerException if blockedNumbersMigrator or migrationListener are {@code null}. - */ - public static DialogFragment newInstance(BlockedNumbersMigrator blockedNumbersMigrator, - BlockedNumbersMigrator.Listener migrationListener) { - MigrateBlockedNumbersDialogFragment fragment = new MigrateBlockedNumbersDialogFragment(); - fragment.mBlockedNumbersMigrator = Preconditions.checkNotNull(blockedNumbersMigrator); - fragment.mMigrationListener = Preconditions.checkNotNull(migrationListener); - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreateDialog(savedInstanceState); - AlertDialog dialog = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.migrate_blocked_numbers_dialog_title) - .setMessage(R.string.migrate_blocked_numbers_dialog_message) - .setPositiveButton(R.string.migrate_blocked_numbers_dialog_allow_button, null) - .setNegativeButton(R.string.migrate_blocked_numbers_dialog_cancel_button, null) - .create(); - // The Dialog's buttons aren't available until show is called, so an OnShowListener - // is used to set the positive button callback. - dialog.setOnShowListener(new OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - final AlertDialog alertDialog = (AlertDialog) dialog; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) - .setOnClickListener(newPositiveButtonOnClickListener(alertDialog)); - } - }); - return dialog; - } - - /* - * Creates a new View.OnClickListener to be used as the positive button in this dialog. The - * OnClickListener will grey out the dialog's positive and negative buttons while the migration - * is underway, and close the dialog once the migrate is complete. - */ - private View.OnClickListener newPositiveButtonOnClickListener(final AlertDialog alertDialog) { - return new View.OnClickListener() { - @Override - public void onClick(View v) { - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false); - mBlockedNumbersMigrator.migrate(new Listener() { - @Override - public void onComplete() { - alertDialog.dismiss(); - mMigrationListener.onComplete(); - } - }); - } - }; - } - - @Override - public void onPause() { - // The dialog is dismissed and state is cleaned up onPause, i.e. rotation. - dismiss(); - mBlockedNumbersMigrator = null; - mMigrationListener = null; - super.onPause(); - } -} diff --git a/src/com/android/dialer/filterednumber/NumbersAdapter.java b/src/com/android/dialer/filterednumber/NumbersAdapter.java deleted file mode 100644 index 17d5db343..000000000 --- a/src/com/android/dialer/filterednumber/NumbersAdapter.java +++ /dev/null @@ -1,137 +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.filterednumber; - -import android.app.FragmentManager; -import android.content.Context; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import android.view.View; -import android.widget.QuickContactBadge; -import android.widget.SimpleCursorAdapter; -import android.widget.TextView; - -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.util.UriUtils; -import com.android.dialer.R; -import com.android.dialer.calllog.ContactInfo; -import com.android.dialer.calllog.ContactInfoHelper; -import com.android.dialer.util.PhoneNumberUtil; - -public class NumbersAdapter extends SimpleCursorAdapter { - - private Context mContext; - private FragmentManager mFragmentManager; - private ContactInfoHelper mContactInfoHelper; - private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); - private ContactPhotoManager mContactPhotoManager; - - public NumbersAdapter( - Context context, - FragmentManager fragmentManager, - ContactInfoHelper contactInfoHelper, - ContactPhotoManager contactPhotoManager) { - super(context, R.layout.blocked_number_item, null, new String[]{}, new int[]{}, 0); - mContext = context; - mFragmentManager = fragmentManager; - mContactInfoHelper = contactInfoHelper; - mContactPhotoManager = contactPhotoManager; - } - - public void updateView(View view, String number, String countryIso) { - final TextView callerName = (TextView) view.findViewById(R.id.caller_name); - final TextView callerNumber = (TextView) view.findViewById(R.id.caller_number); - final QuickContactBadge quickContactBadge = - (QuickContactBadge) view.findViewById(R.id.quick_contact_photo); - quickContactBadge.setOverlay(null); - if (CompatUtils.hasPrioritizedMimeType()) { - quickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); - } - - ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso); - if (info == null) { - info = new ContactInfo(); - info.number = number; - } - final CharSequence locationOrType = getNumberTypeOrLocation(info); - final String displayNumber = getDisplayNumber(info); - final String displayNumberStr = mBidiFormatter.unicodeWrap(displayNumber, - TextDirectionHeuristics.LTR); - - String nameForDefaultImage; - if (!TextUtils.isEmpty(info.name)) { - nameForDefaultImage = info.name; - callerName.setText(info.name); - callerNumber.setText(locationOrType + " " + displayNumberStr); - } else { - nameForDefaultImage = displayNumber; - callerName.setText(displayNumberStr); - if (!TextUtils.isEmpty(locationOrType)) { - callerNumber.setText(locationOrType); - callerNumber.setVisibility(View.VISIBLE); - } else { - callerNumber.setVisibility(View.GONE); - } - } - loadContactPhoto(info, nameForDefaultImage, quickContactBadge); - } - - private void loadContactPhoto(ContactInfo info, String displayName, QuickContactBadge badge) { - final String lookupKey = info.lookupUri == null - ? null : UriUtils.getLookupKeyFromUri(info.lookupUri); - final int contactType = mContactInfoHelper.isBusiness(info.sourceType) - ? ContactPhotoManager.TYPE_BUSINESS : ContactPhotoManager.TYPE_DEFAULT; - final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey, - contactType, true /* isCircular */); - badge.assignContactUri(info.lookupUri); - badge.setContentDescription( - mContext.getResources().getString(R.string.description_contact_details, displayName)); - mContactPhotoManager.loadDirectoryPhoto(badge, info.photoUri, - false /* darkTheme */, true /* isCircular */, request); - } - - private String getDisplayNumber(ContactInfo info) { - if (!TextUtils.isEmpty(info.formattedNumber)) { - return info.formattedNumber; - } else if (!TextUtils.isEmpty(info.number)) { - return info.number; - } else { - return ""; - } - } - - private CharSequence getNumberTypeOrLocation(ContactInfo info) { - if (!TextUtils.isEmpty(info.name)) { - return ContactsContract.CommonDataKinds.Phone.getTypeLabel( - mContext.getResources(), info.type, info.label); - } else { - return PhoneNumberUtil.getGeoDescription(mContext, info.number); - } - } - - protected Context getContext() { - return mContext; - } - - protected FragmentManager getFragmentManager() { - return mFragmentManager; - } -} diff --git a/src/com/android/dialer/filterednumber/ViewNumbersToImportAdapter.java b/src/com/android/dialer/filterednumber/ViewNumbersToImportAdapter.java deleted file mode 100644 index 58fe1d46c..000000000 --- a/src/com/android/dialer/filterednumber/ViewNumbersToImportAdapter.java +++ /dev/null @@ -1,57 +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.filterednumber; - -import android.app.FragmentManager; -import android.database.Cursor; -import android.content.Context; -import android.view.View; - -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.GeoUtil; -import com.android.dialer.R; -import com.android.dialer.calllog.ContactInfoHelper; - -public class ViewNumbersToImportAdapter extends NumbersAdapter { - - private ViewNumbersToImportAdapter( - Context context, - FragmentManager fragmentManager, - ContactInfoHelper contactInfoHelper, - ContactPhotoManager contactPhotoManager) { - super(context, fragmentManager, contactInfoHelper, contactPhotoManager); - } - - public static ViewNumbersToImportAdapter newViewNumbersToImportAdapter( - Context context, FragmentManager fragmentManager) { - return new ViewNumbersToImportAdapter( - context, - fragmentManager, - new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)), - ContactPhotoManager.getInstance(context)); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - super.bindView(view, context, cursor); - - final String number = cursor.getString( - FilteredNumbersUtil.PhoneQuery.NUMBER_COLUMN_INDEX); - - view.findViewById(R.id.delete_button).setVisibility(View.GONE); - updateView(view, number, null /* countryIso */); - } -} diff --git a/src/com/android/dialer/filterednumber/ViewNumbersToImportFragment.java b/src/com/android/dialer/filterednumber/ViewNumbersToImportFragment.java deleted file mode 100644 index 8b24c06da..000000000 --- a/src/com/android/dialer/filterednumber/ViewNumbersToImportFragment.java +++ /dev/null @@ -1,133 +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.filterednumber; - -import android.app.ListFragment; -import android.app.LoaderManager; -import android.content.Context; -import android.content.CursorLoader; -import android.content.Loader; -import android.database.Cursor; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.dialer.R; -import com.android.dialer.database.FilteredNumberContract; -import com.android.dialer.filterednumber.FilteredNumbersUtil.ImportSendToVoicemailContactsListener; - -public class ViewNumbersToImportFragment extends ListFragment - implements LoaderManager.LoaderCallbacks<Cursor>, - View.OnClickListener { - - private ViewNumbersToImportAdapter mAdapter; - - @Override - public Context getContext() { - return getActivity(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - if (mAdapter == null) { - mAdapter = ViewNumbersToImportAdapter.newViewNumbersToImportAdapter( - getContext(), getActivity().getFragmentManager()); - } - setListAdapter(mAdapter); - } - - @Override - public void onDestroy() { - setListAdapter(null); - super.onDestroy(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getLoaderManager().initLoader(0, null, this); - } - - @Override - public void onResume() { - super.onResume(); - - ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - actionBar.setTitle(R.string.import_send_to_voicemail_numbers_label); - actionBar.setDisplayShowCustomEnabled(false); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - - getActivity().findViewById(R.id.cancel_button).setOnClickListener(this); - getActivity().findViewById(R.id.import_button).setOnClickListener(this); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.view_numbers_to_import_fragment, container, false); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - final CursorLoader cursorLoader = new CursorLoader( - getContext(), - Phone.CONTENT_URI, - FilteredNumbersUtil.PhoneQuery.PROJECTION, - FilteredNumbersUtil.PhoneQuery.SELECT_SEND_TO_VOICEMAIL_TRUE, - null, - null); - return cursorLoader; - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - mAdapter.swapCursor(data); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - mAdapter.swapCursor(null); - } - - @Override - public void onClick(final View view) { - if (view.getId() == R.id.import_button) { - FilteredNumbersUtil.importSendToVoicemailContacts(getContext(), - new ImportSendToVoicemailContactsListener() { - @Override - public void onImportComplete() { - if (getActivity() != null) { - getActivity().onBackPressed(); - } - } - }); - } else if (view.getId() == R.id.cancel_button) { - getActivity().onBackPressed(); - } - } -} diff --git a/src/com/android/dialer/interactions/PhoneNumberInteraction.java b/src/com/android/dialer/interactions/PhoneNumberInteraction.java deleted file mode 100644 index 0c3ae510a..000000000 --- a/src/com/android/dialer/interactions/PhoneNumberInteraction.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright (C) 2010 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.interactions; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentManager; -import android.content.Context; -import android.content.CursorLoader; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.content.Loader; -import android.content.Loader.OnLoadCompleteListener; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.CommonDataKinds.SipAddress; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.RawContacts; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.contacts.common.Collapser; -import com.android.contacts.common.Collapser.Collapsible; -import com.android.contacts.common.MoreContactUtils; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.R; -import com.android.dialer.TransactionSafeActivity; -import com.android.dialer.contact.ContactUpdateService; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.util.IntentUtil.CallIntentBuilder; -import com.android.incallui.Call.LogState; -import com.android.dialer.util.DialerUtils; - -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.List; - -/** - * Initiates phone calls or a text message. If there are multiple candidates, this class shows a - * dialog to pick one. Creating one of these interactions should be done through the static - * factory methods. - * - * Note that this class initiates not only usual *phone* calls but also *SIP* calls. - * - * TODO: clean up code and documents since it is quite confusing to use "phone numbers" or - * "phone calls" here while they can be SIP addresses or SIP calls (See also issue 5039627). - */ -public class PhoneNumberInteraction implements OnLoadCompleteListener<Cursor> { - private static final String TAG = PhoneNumberInteraction.class.getSimpleName(); - - /** - * A model object for capturing a phone number for a given contact. - */ - @VisibleForTesting - /* package */ static class PhoneItem implements Parcelable, Collapsible<PhoneItem> { - long id; - String phoneNumber; - String accountType; - String dataSet; - long type; - String label; - /** {@link Phone#CONTENT_ITEM_TYPE} or {@link SipAddress#CONTENT_ITEM_TYPE}. */ - String mimeType; - - public PhoneItem() { - } - - private PhoneItem(Parcel in) { - this.id = in.readLong(); - this.phoneNumber = in.readString(); - this.accountType = in.readString(); - this.dataSet = in.readString(); - this.type = in.readLong(); - this.label = in.readString(); - this.mimeType = in.readString(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(id); - dest.writeString(phoneNumber); - dest.writeString(accountType); - dest.writeString(dataSet); - dest.writeLong(type); - dest.writeString(label); - dest.writeString(mimeType); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void collapseWith(PhoneItem phoneItem) { - // Just keep the number and id we already have. - } - - @Override - public boolean shouldCollapseWith(PhoneItem phoneItem, Context context) { - return MoreContactUtils.shouldCollapse(Phone.CONTENT_ITEM_TYPE, phoneNumber, - Phone.CONTENT_ITEM_TYPE, phoneItem.phoneNumber); - } - - @Override - public String toString() { - return phoneNumber; - } - - public static final Parcelable.Creator<PhoneItem> CREATOR - = new Parcelable.Creator<PhoneItem>() { - @Override - public PhoneItem createFromParcel(Parcel in) { - return new PhoneItem(in); - } - - @Override - public PhoneItem[] newArray(int size) { - return new PhoneItem[size]; - } - }; - } - - /** - * A list adapter that populates the list of contact's phone numbers. - */ - private static class PhoneItemAdapter extends ArrayAdapter<PhoneItem> { - private final int mInteractionType; - - public PhoneItemAdapter(Context context, List<PhoneItem> list, - int interactionType) { - super(context, R.layout.phone_disambig_item, android.R.id.text2, list); - mInteractionType = interactionType; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final View view = super.getView(position, convertView, parent); - - final PhoneItem item = getItem(position); - final TextView typeView = (TextView) view.findViewById(android.R.id.text1); - CharSequence value = ContactDisplayUtils.getLabelForCallOrSms((int) item.type, - item.label, mInteractionType, getContext()); - - typeView.setText(value); - return view; - } - } - - /** - * {@link DialogFragment} used for displaying a dialog with a list of phone numbers of which - * one will be chosen to make a call or initiate an sms message. - * - * It is recommended to use - * {@link PhoneNumberInteraction#startInteractionForPhoneCall(TransactionSafeActivity, Uri)} or - * {@link PhoneNumberInteraction#startInteractionForTextMessage(TransactionSafeActivity, Uri)} - * instead of directly using this class, as those methods handle one or multiple data cases - * appropriately. - */ - /* Made public to let the system reach this class */ - public static class PhoneDisambiguationDialogFragment extends DialogFragment - implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener { - - private static final String ARG_PHONE_LIST = "phoneList"; - private static final String ARG_INTERACTION_TYPE = "interactionType"; - private static final String ARG_CALL_INITIATION_TYPE = "callInitiation"; - private static final String ARG_IS_VIDEO_CALL = "is_video_call"; - - private int mInteractionType; - private ListAdapter mPhonesAdapter; - private List<PhoneItem> mPhoneList; - private int mCallInitiationType; - private boolean mIsVideoCall; - - public static void show(FragmentManager fragmentManager, ArrayList<PhoneItem> phoneList, - int interactionType, boolean isVideoCall, int callInitiationType) { - PhoneDisambiguationDialogFragment fragment = new PhoneDisambiguationDialogFragment(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(ARG_PHONE_LIST, phoneList); - bundle.putInt(ARG_INTERACTION_TYPE, interactionType); - bundle.putInt(ARG_CALL_INITIATION_TYPE, callInitiationType); - bundle.putBoolean(ARG_IS_VIDEO_CALL, isVideoCall); - fragment.setArguments(bundle); - fragment.show(fragmentManager, TAG); - } - - public PhoneDisambiguationDialogFragment() { - super(); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); - mPhoneList = getArguments().getParcelableArrayList(ARG_PHONE_LIST); - mInteractionType = getArguments().getInt(ARG_INTERACTION_TYPE); - mCallInitiationType = getArguments().getInt(ARG_CALL_INITIATION_TYPE); - mIsVideoCall = getArguments().getBoolean(ARG_IS_VIDEO_CALL); - - mPhonesAdapter = new PhoneItemAdapter(activity, mPhoneList, mInteractionType); - final LayoutInflater inflater = activity.getLayoutInflater(); - final View setPrimaryView = inflater.inflate(R.layout.set_primary_checkbox, null); - return new AlertDialog.Builder(activity) - .setAdapter(mPhonesAdapter, this) - .setTitle(mInteractionType == ContactDisplayUtils.INTERACTION_SMS - ? R.string.sms_disambig_title : R.string.call_disambig_title) - .setView(setPrimaryView) - .create(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - final Activity activity = getActivity(); - if (activity == null) return; - final AlertDialog alertDialog = (AlertDialog)dialog; - if (mPhoneList.size() > which && which >= 0) { - final PhoneItem phoneItem = mPhoneList.get(which); - final CheckBox checkBox = (CheckBox)alertDialog.findViewById(R.id.setPrimary); - if (checkBox.isChecked()) { - // Request to mark the data as primary in the background. - final Intent serviceIntent = ContactUpdateService.createSetSuperPrimaryIntent( - activity, phoneItem.id); - activity.startService(serviceIntent); - } - - PhoneNumberInteraction.performAction(activity, phoneItem.phoneNumber, - mInteractionType, mIsVideoCall, mCallInitiationType); - } else { - dialog.dismiss(); - } - } - } - - private static final String[] PHONE_NUMBER_PROJECTION = new String[] { - Phone._ID, // 0 - Phone.NUMBER, // 1 - Phone.IS_SUPER_PRIMARY, // 2 - RawContacts.ACCOUNT_TYPE, // 3 - RawContacts.DATA_SET, // 4 - Phone.TYPE, // 5 - Phone.LABEL, // 6 - Phone.MIMETYPE, // 7 - Phone.CONTACT_ID // 8 - }; - - private static final int _ID = 0; - private static final int NUMBER = 1; - private static final int IS_SUPER_PRIMARY = 2; - private static final int ACCOUNT_TYPE = 3; - private static final int DATA_SET = 4; - private static final int TYPE = 5; - private static final int LABEL = 6; - private static final int MIMETYPE = 7; - private static final int CONTACT_ID = 8; - - private static final String PHONE_NUMBER_SELECTION = - Data.MIMETYPE + " IN ('" - + Phone.CONTENT_ITEM_TYPE + "', " - + "'" + SipAddress.CONTENT_ITEM_TYPE + "') AND " - + Data.DATA1 + " NOT NULL"; - - private final Context mContext; - private final OnDismissListener mDismissListener; - private final int mInteractionType; - - private final int mCallInitiationType; - private boolean mUseDefault; - - private static final int UNKNOWN_CONTACT_ID = -1; - private long mContactId = UNKNOWN_CONTACT_ID; - - private CursorLoader mLoader; - private boolean mIsVideoCall; - - /** - * Constructs a new {@link PhoneNumberInteraction}. The constructor takes in a {@link Context} - * instead of a {@link TransactionSafeActivity} for testing purposes to verify the functionality - * of this class. However, all factory methods for creating {@link PhoneNumberInteraction}s - * require a {@link TransactionSafeActivity} (i.e. see {@link #startInteractionForPhoneCall}). - */ - @VisibleForTesting - /* package */ PhoneNumberInteraction(Context context, int interactionType, - DialogInterface.OnDismissListener dismissListener) { - this(context, interactionType, dismissListener, false /*isVideoCall*/, - LogState.INITIATION_UNKNOWN); - } - - private PhoneNumberInteraction(Context context, int interactionType, - DialogInterface.OnDismissListener dismissListener, boolean isVideoCall, - int callInitiationType) { - mContext = context; - mInteractionType = interactionType; - mDismissListener = dismissListener; - mCallInitiationType = callInitiationType; - mIsVideoCall = isVideoCall; - } - - private void performAction(String phoneNumber) { - PhoneNumberInteraction.performAction(mContext, phoneNumber, mInteractionType, mIsVideoCall, - mCallInitiationType); - } - - private static void performAction( - Context context, String phoneNumber, int interactionType, - boolean isVideoCall, int callInitiationType) { - Intent intent; - switch (interactionType) { - case ContactDisplayUtils.INTERACTION_SMS: - intent = new Intent( - Intent.ACTION_SENDTO, Uri.fromParts("sms", phoneNumber, null)); - break; - default: - intent = new CallIntentBuilder(phoneNumber) - .setCallInitiationType(callInitiationType) - .setIsVideoCall(isVideoCall) - .build(); - break; - } - DialerUtils.startActivityWithErrorToast(context, intent); - } - - /** - * Initiates the interaction. This may result in a phone call or sms message started - * or a disambiguation dialog to determine which phone number should be used. If there - * is a primary phone number, it will be automatically used and a disambiguation dialog - * will no be shown. - */ - @VisibleForTesting - /* package */ void startInteraction(Uri uri) { - startInteraction(uri, true); - } - - /** - * Initiates the interaction to result in either a phone call or sms message for a contact. - * @param uri Contact Uri - * @param useDefault Whether or not to use the primary(default) phone number. If true, the - * primary phone number will always be used by default if one is available. If false, a - * disambiguation dialog will be shown regardless of whether or not a primary phone number - * is available. - */ - @VisibleForTesting - /* package */ void startInteraction(Uri uri, boolean useDefault) { - if (mLoader != null) { - mLoader.reset(); - } - mUseDefault = useDefault; - final Uri queryUri; - final String inputUriAsString = uri.toString(); - if (inputUriAsString.startsWith(Contacts.CONTENT_URI.toString())) { - if (!inputUriAsString.endsWith(Contacts.Data.CONTENT_DIRECTORY)) { - queryUri = Uri.withAppendedPath(uri, Contacts.Data.CONTENT_DIRECTORY); - } else { - queryUri = uri; - } - } else if (inputUriAsString.startsWith(Data.CONTENT_URI.toString())) { - queryUri = uri; - } else { - throw new UnsupportedOperationException( - "Input Uri must be contact Uri or data Uri (input: \"" + uri + "\")"); - } - - mLoader = new CursorLoader(mContext, - queryUri, - PHONE_NUMBER_PROJECTION, - PHONE_NUMBER_SELECTION, - null, - null); - mLoader.registerListener(0, this); - mLoader.startLoading(); - } - - @Override - public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) { - if (cursor == null) { - onDismiss(); - return; - } - try { - ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>(); - String primaryPhone = null; - if (!isSafeToCommitTransactions()) { - onDismiss(); - return; - } - while (cursor.moveToNext()) { - if (mContactId == UNKNOWN_CONTACT_ID) { - mContactId = cursor.getLong(CONTACT_ID); - } - - if (mUseDefault && cursor.getInt(IS_SUPER_PRIMARY) != 0) { - // Found super primary, call it. - primaryPhone = cursor.getString(NUMBER); - } - - PhoneItem item = new PhoneItem(); - item.id = cursor.getLong(_ID); - item.phoneNumber = cursor.getString(NUMBER); - item.accountType = cursor.getString(ACCOUNT_TYPE); - item.dataSet = cursor.getString(DATA_SET); - item.type = cursor.getInt(TYPE); - item.label = cursor.getString(LABEL); - item.mimeType = cursor.getString(MIMETYPE); - - phoneList.add(item); - } - - if (mUseDefault && primaryPhone != null) { - performAction(primaryPhone); - onDismiss(); - return; - } - - Collapser.collapseList(phoneList, mContext); - if (phoneList.size() == 0) { - onDismiss(); - } else if (phoneList.size() == 1) { - PhoneItem item = phoneList.get(0); - onDismiss(); - performAction(item.phoneNumber); - } else { - // There are multiple candidates. Let the user choose one. - showDisambiguationDialog(phoneList); - } - } finally { - cursor.close(); - } - } - - private boolean isSafeToCommitTransactions() { - return mContext instanceof TransactionSafeActivity ? - ((TransactionSafeActivity) mContext).isSafeToCommitTransactions() : true; - } - - private void onDismiss() { - if (mDismissListener != null) { - mDismissListener.onDismiss(null); - } - } - - /** - * @param activity that is calling this interaction. This must be of type - * {@link TransactionSafeActivity} because we need to check on the activity state after the - * phone numbers have been queried for. - * @param isVideoCall {@code true} if the call is a video call, {@code false} otherwise. - * @param callInitiationType Indicates the UI affordance that was used to initiate the call. - */ - public static void startInteractionForPhoneCall(TransactionSafeActivity activity, Uri uri, - boolean isVideoCall, int callInitiationType) { - (new PhoneNumberInteraction(activity, ContactDisplayUtils.INTERACTION_CALL, null, - isVideoCall, callInitiationType)).startInteraction(uri, true); - } - - /** - * Start text messaging (a.k.a SMS) action using given contact Uri. If there are multiple - * candidates for the phone call, dialog is automatically shown and the user is asked to choose - * one. - * - * @param activity that is calling this interaction. This must be of type - * {@link TransactionSafeActivity} because we need to check on the activity state after the - * phone numbers have been queried for. - * @param uri contact Uri (built from {@link Contacts#CONTENT_URI}) or data Uri - * (built from {@link Data#CONTENT_URI}). Contact Uri may show the disambiguation dialog while - * data Uri won't. - */ - public static void startInteractionForTextMessage(TransactionSafeActivity activity, Uri uri) { - (new PhoneNumberInteraction(activity, ContactDisplayUtils.INTERACTION_SMS, null)) - .startInteraction(uri, true); - } - - @VisibleForTesting - /* package */ CursorLoader getLoader() { - return mLoader; - } - - @VisibleForTesting - /* package */ void showDisambiguationDialog(ArrayList<PhoneItem> phoneList) { - final Activity activity = (Activity) mContext; - if (activity.isDestroyed()) { - // Check whether the activity is still running - return; - } - try { - PhoneDisambiguationDialogFragment.show(activity.getFragmentManager(), - phoneList, mInteractionType, mIsVideoCall, mCallInitiationType); - } catch (IllegalStateException e) { - // ignore to be safe. Shouldn't happen because we checked the - // activity wasn't destroyed, but to be safe. - } - } -} diff --git a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java deleted file mode 100644 index 172a4efef..000000000 --- a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java +++ /dev/null @@ -1,109 +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.interactions; - -import static android.Manifest.permission.READ_CONTACTS; -import static android.Manifest.permission.WRITE_CONTACTS; - -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.PhoneLookup; -import android.provider.ContactsContract.PinnedPositions; -import android.text.TextUtils; - -import com.android.contacts.common.util.PermissionsUtil; - -/** - * This broadcast receiver is used to listen to outgoing calls and undemote formerly demoted - * contacts if a phone call is made to a phone number belonging to that contact. - * - * NOTE This doesn't work for corp contacts. - */ -public class UndemoteOutgoingCallReceiver extends BroadcastReceiver { - - private static final long NO_CONTACT_FOUND = -1; - - @Override - public void onReceive(final Context context, Intent intent) { - if (!PermissionsUtil.hasPermission(context, READ_CONTACTS) - || !PermissionsUtil.hasPermission(context, WRITE_CONTACTS)) { - return; - } - if (intent != null && Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())) { - final String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - if (TextUtils.isEmpty(number)) { - return; - } - new Thread() { - @Override - public void run() { - final long id = getContactIdFromPhoneNumber(context, number); - if (id != NO_CONTACT_FOUND) { - undemoteContactWithId(context, id); - } - } - }.start(); - } - } - - private void undemoteContactWithId(Context context, long id) { - // If the contact is not demoted, this will not do anything. Otherwise, it will - // restore it to an unpinned position. If it was a frequently called contact, it will - // show up once again show up on the favorites screen. - if (PermissionsUtil.hasPermission(context, WRITE_CONTACTS)) { - try { - PinnedPositions.undemote(context.getContentResolver(), id); - } catch (SecurityException e) { - // Just in case - } - } - } - - private long getContactIdFromPhoneNumber(Context context, String number) { - if (!PermissionsUtil.hasPermission(context, READ_CONTACTS)) { - return NO_CONTACT_FOUND; - } - final Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, - Uri.encode(number)); - final Cursor cursor; - try { - cursor = context.getContentResolver().query(contactUri, new String[] { - PhoneLookup._ID}, null, null, null); - } catch (SecurityException e) { - // Just in case - return NO_CONTACT_FOUND; - } - if (cursor == null) { - return NO_CONTACT_FOUND; - } - try { - if (cursor.moveToFirst()) { - final long id = cursor.getLong(0); - return id; - } else { - return NO_CONTACT_FOUND; - } - } finally { - cursor.close(); - } - } -} diff --git a/src/com/android/dialer/list/AllContactsFragment.java b/src/com/android/dialer/list/AllContactsFragment.java deleted file mode 100644 index 7e76279d9..000000000 --- a/src/com/android/dialer/list/AllContactsFragment.java +++ /dev/null @@ -1,198 +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.list; - -import static android.Manifest.permission.READ_CONTACTS; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.Loader; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.QuickContact; -import android.support.v13.app.FragmentCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.list.ContactEntryListFragment; -import com.android.contacts.common.list.ContactListFilter; -import com.android.contacts.common.list.DefaultContactListAdapter; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.common.util.ViewUtil; -import com.android.dialer.R; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.widget.EmptyContentView; -import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; - -/** - * Fragments to show all contacts with phone numbers. - */ -public class AllContactsFragment extends ContactEntryListFragment<ContactEntryListAdapter> - implements OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - - private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; - - private EmptyContentView mEmptyListView; - - /** - * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS - * permission is granted via the UI in another fragment. - */ - private BroadcastReceiver mReadContactsPermissionGrantedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - reloadData(); - } - }; - - public AllContactsFragment() { - setQuickContactEnabled(false); - setAdjustSelectionBoundsEnabled(true); - setPhotoLoaderEnabled(true); - setSectionHeaderDisplayEnabled(true); - setDarkTheme(false); - setVisibleScrollbarEnabled(true); - } - - @Override - public void onViewCreated(View view, android.os.Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view); - mEmptyListView.setImage(R.drawable.empty_contacts); - mEmptyListView.setDescription(R.string.all_contacts_empty); - mEmptyListView.setActionClickedListener(this); - getListView().setEmptyView(mEmptyListView); - mEmptyListView.setVisibility(View.GONE); - - ViewUtil.addBottomPaddingToListViewForFab(getListView(), getResources()); - } - - @Override - public void onStart() { - super.onStart(); - PermissionsUtil.registerPermissionReceiver(getActivity(), - mReadContactsPermissionGrantedReceiver, READ_CONTACTS); - } - - @Override - public void onStop() { - PermissionsUtil.unregisterPermissionReceiver(getActivity(), - mReadContactsPermissionGrantedReceiver); - super.onStop(); - } - - @Override - protected void startLoading() { - if (PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { - super.startLoading(); - mEmptyListView.setDescription(R.string.all_contacts_empty); - mEmptyListView.setActionLabel(R.string.all_contacts_empty_add_contact_action); - } else { - mEmptyListView.setDescription(R.string.permission_no_contacts); - mEmptyListView.setActionLabel(R.string.permission_single_turn_on); - mEmptyListView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - super.onLoadFinished(loader, data); - - if (data == null || data.getCount() == 0) { - mEmptyListView.setVisibility(View.VISIBLE); - } - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - final DefaultContactListAdapter adapter = new DefaultContactListAdapter(getActivity()) { - @Override - protected void bindView(View itemView, int partition, Cursor cursor, int position) { - super.bindView(itemView, partition, cursor, position); - itemView.setTag(this.getContactUri(partition, cursor)); - } - }; - adapter.setDisplayPhotos(true); - adapter.setFilter(ContactListFilter.createFilterWithType( - ContactListFilter.FILTER_TYPE_DEFAULT)); - adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled()); - return adapter; - } - - @Override - protected View inflateView(LayoutInflater inflater, ViewGroup container) { - return inflater.inflate(R.layout.all_contacts_fragment, null); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Uri uri = (Uri) view.getTag(); - if (uri != null) { - if (CompatUtils.hasPrioritizedMimeType()) { - QuickContact.showQuickContact(getContext(), view, uri, null, - Phone.CONTENT_ITEM_TYPE); - } else { - QuickContact.showQuickContact(getActivity(), view, uri, QuickContact.MODE_LARGE, - null); - } - } - } - - @Override - protected void onItemClick(int position, long id) { - // Do nothing. Implemented to satisfy ContactEntryListFragment. - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - if (!PermissionsUtil.hasPermission(activity, READ_CONTACTS)) { - FragmentCompat.requestPermissions(this, new String[] {READ_CONTACTS}, - READ_CONTACTS_PERMISSION_REQUEST_CODE); - } else { - // Add new contact - DialerUtils.startActivityWithErrorToast(activity, IntentUtil.getNewContactIntent(), - R.string.add_contact_not_available); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == READ_CONTACTS_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. - reloadData(); - } - } - } -} diff --git a/src/com/android/dialer/list/BlockedListSearchAdapter.java b/src/com/android/dialer/list/BlockedListSearchAdapter.java deleted file mode 100644 index 1618826bd..000000000 --- a/src/com/android/dialer/list/BlockedListSearchAdapter.java +++ /dev/null @@ -1,90 +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.list; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Color; -import android.view.View; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.list.ContactListItemView; -import com.android.dialer.R; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; - -/** - * List adapter to display search results for adding a blocked number. - */ -public class BlockedListSearchAdapter extends RegularSearchListAdapter { - - private Resources mResources; - private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - - public BlockedListSearchAdapter(Context context) { - super(context); - mResources = context.getResources(); - disableAllShortcuts(); - setShortcutEnabled(SHORTCUT_BLOCK_NUMBER, true); - - mFilteredNumberAsyncQueryHandler = - new FilteredNumberAsyncQueryHandler(context.getContentResolver()); - } - - @Override - protected boolean isChanged(boolean showNumberShortcuts) { - return setShortcutEnabled(SHORTCUT_BLOCK_NUMBER, showNumberShortcuts || mIsQuerySipAddress); - } - - public void setViewBlocked(ContactListItemView view, Integer id) { - view.setTag(R.id.block_id, id); - final int textColor = mResources.getColor(R.color.blocked_number_block_color); - view.getDataView().setTextColor(textColor); - view.getLabelView().setTextColor(textColor); - //TODO: Add icon - } - - public void setViewUnblocked(ContactListItemView view) { - view.setTag(R.id.block_id, null); - final int textColor = mResources.getColor(R.color.dialtacts_secondary_text_color); - view.getDataView().setTextColor(textColor); - view.getLabelView().setTextColor(textColor); - //TODO: Remove icon - } - - @Override - protected void bindView(View itemView, int partition, Cursor cursor, int position) { - super.bindView(itemView, partition, cursor, position); - - final ContactListItemView view = (ContactListItemView) itemView; - // Reset view state to unblocked. - setViewUnblocked(view); - - final String number = getPhoneNumber(position); - final String countryIso = GeoUtil.getCurrentCountryIso(mContext); - final FilteredNumberAsyncQueryHandler.OnCheckBlockedListener onCheckListener = - new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - if (id != null) { - setViewBlocked(view, id); - } - } - }; - mFilteredNumberAsyncQueryHandler.isBlockedNumber( - onCheckListener, number, countryIso); - } -} diff --git a/src/com/android/dialer/list/BlockedListSearchFragment.java b/src/com/android/dialer/list/BlockedListSearchFragment.java deleted file mode 100644 index da6b42820..000000000 --- a/src/com/android/dialer/list/BlockedListSearchFragment.java +++ /dev/null @@ -1,244 +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.list; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.telephony.PhoneNumberUtils; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Log; -import android.util.TypedValue; -import android.view.View; -import android.widget.AdapterView; -import android.widget.EditText; -import android.widget.Toast; - -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.R; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; -import com.android.dialer.filterednumber.BlockNumberDialogFragment; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; -import com.android.dialer.widget.SearchEditTextLayout; - -public class BlockedListSearchFragment extends RegularSearchFragment - implements BlockNumberDialogFragment.Callback { - private static final String TAG = BlockedListSearchFragment.class.getSimpleName(); - - private static final String KEY_SEARCH_QUERY = "search_query"; - - private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - - private EditText mSearchView; - - private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - setQueryString(s.toString(), false); - } - - @Override - public void afterTextChanged(Editable s) {} - }; - - private final SearchEditTextLayout.Callback mSearchLayoutCallback = - new SearchEditTextLayout.Callback() { - @Override - public void onBackButtonClicked() { - getActivity().onBackPressed(); - } - - @Override - public void onSearchViewClicked() { - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setShowEmptyListForNullQuery(true); - /* - * Pass in the empty string here so ContactEntryListFragment#setQueryString interprets it as - * an empty search query, rather than as an uninitalized value. In the latter case, the - * adapter returned by #createListAdapter is used, which populates the view with contacts. - * Passing in the empty string forces ContactEntryListFragment to interpret it as an empty - * query, which results in showing an empty view - */ - setQueryString(getQueryString() == null ? "" : getQueryString(), false); - mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler( - getContext().getContentResolver()); - } - - @Override - public void onResume() { - super.onResume(); - - ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - actionBar.setCustomView(R.layout.search_edittext); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setDisplayShowHomeEnabled(false); - - final SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar - .getCustomView().findViewById(R.id.search_view_container); - searchEditTextLayout.expand(false, true); - searchEditTextLayout.setCallback(mSearchLayoutCallback); - searchEditTextLayout.setBackgroundDrawable(null); - - mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view); - mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); - mSearchView.setHint(R.string.block_number_search_hint); - - searchEditTextLayout.findViewById(R.id.search_box_expanded) - .setBackgroundColor(getContext().getResources().getColor(android.R.color.white)); - - if (!TextUtils.isEmpty(getQueryString())) { - mSearchView.setText(getQueryString()); - } - - // TODO: Don't set custom text size; use default search text size. - mSearchView.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.blocked_number_search_text_size)); - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - BlockedListSearchAdapter adapter = new BlockedListSearchAdapter(getActivity()); - adapter.setDisplayPhotos(true); - // Don't show SIP addresses. - adapter.setUseCallableUri(false); - // Keep in sync with the queryString set in #onCreate - adapter.setQueryString(getQueryString() == null ? "" : getQueryString()); - return adapter; - } - - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - super.onItemClick(parent, view, position, id); - final int adapterPosition = position - getListView().getHeaderViewsCount(); - final BlockedListSearchAdapter adapter = (BlockedListSearchAdapter) getAdapter(); - final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition); - final Integer blockId = (Integer) view.getTag(R.id.block_id); - final String number; - switch (shortcutType) { - case DialerPhoneNumberListAdapter.SHORTCUT_INVALID: - // Handles click on a search result, either contact or nearby places result. - number = adapter.getPhoneNumber(adapterPosition); - blockContactNumber(number, blockId); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_BLOCK_NUMBER: - // Handles click on 'Block number' shortcut to add the user query as a number. - number = adapter.getQueryString(); - blockNumber(number); - break; - default: - Log.w(TAG, "Ignoring unsupported shortcut type: " + shortcutType); - break; - } - } - - @Override - protected void onItemClick(int position, long id) { - // Prevent SearchFragment.onItemClicked from being called. - } - - private void blockNumber(final String number) { - final String countryIso = GeoUtil.getCurrentCountryIso(getContext()); - final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - if (id == null) { - BlockNumberDialogFragment.show( - id, - number, - countryIso, - PhoneNumberUtils.formatNumber(number, countryIso), - R.id.blocked_numbers_activity_container, - getFragmentManager(), - BlockedListSearchFragment.this); - } else { - Toast.makeText(getContext(), - ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(), - R.string.alreadyBlocked, number), - Toast.LENGTH_SHORT).show(); - } - } - }; - final boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber( - onCheckListener, number, countryIso); - if (!success) { - Toast.makeText(getContext(), - ContactDisplayUtils.getTtsSpannedPhoneNumber( - getResources(), R.string.invalidNumber, number), - Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFilterNumberSuccess() { - Logger.logInteraction(InteractionEvent.BLOCK_NUMBER_MANAGEMENT_SCREEN); - goBack(); - } - - @Override - public void onUnfilterNumberSuccess() { - Log.wtf(TAG, "Unblocked a number from the BlockedListSearchFragment"); - goBack(); - } - - private void goBack() { - Activity activity = getActivity(); - if (activity == null) { - return; - } - activity.onBackPressed(); - } - - @Override - public void onChangeFilteredNumberUndo() { - getAdapter().notifyDataSetChanged(); - } - - private void blockContactNumber(final String number, final Integer blockId) { - if (blockId != null) { - Toast.makeText(getContext(), ContactDisplayUtils.getTtsSpannedPhoneNumber( - getResources(), R.string.alreadyBlocked, number), - Toast.LENGTH_SHORT).show(); - return; - } - - BlockNumberDialogFragment.show( - blockId, - number, - GeoUtil.getCurrentCountryIso(getContext()), - number, - R.id.blocked_numbers_activity_container, - getFragmentManager(), - this); - } -} diff --git a/src/com/android/dialer/list/ContentChangedFilter.java b/src/com/android/dialer/list/ContentChangedFilter.java deleted file mode 100644 index e552aa3f0..000000000 --- a/src/com/android/dialer/list/ContentChangedFilter.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.android.dialer.list; - -import android.view.View; -import android.view.View.AccessibilityDelegate; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -/** - * AccessibilityDelegate that will filter out TYPE_WINDOW_CONTENT_CHANGED - * Used to suppress "Showing items x of y" from firing of ListView whenever it's content changes. - * AccessibilityEvent can only be rejected at a view's parent once it is generated, - * use addToParent() to add this delegate to the parent. - */ -public class ContentChangedFilter extends AccessibilityDelegate { - //the view we don't want TYPE_WINDOW_CONTENT_CHANGED to fire. - private View mView; - - /** - * Add this delegate to the parent of @param view to filter out TYPE_WINDOW_CONTENT_CHANGED - */ - public static void addToParent(View view){ - View parent = (View) view.getParent(); - parent.setAccessibilityDelegate(new ContentChangedFilter(view)); - } - - private ContentChangedFilter(View view){ - super(); - mView = view; - } - @Override - public boolean onRequestSendAccessibilityEvent (ViewGroup host, View child, AccessibilityEvent event){ - if(child == mView){ - if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){ - return false; - } - } - return super.onRequestSendAccessibilityEvent(host,child,event); - } - -} diff --git a/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java b/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java deleted file mode 100644 index 7164de2d7..000000000 --- a/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.android.dialer.list; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.telephony.PhoneNumberUtils; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.list.ContactListItemView; -import com.android.contacts.common.list.PhoneNumberListAdapter; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.R; - -/** - * {@link PhoneNumberListAdapter} with the following added shortcuts, that are displayed as list - * items: - * 1) Directly calling the phone number query - * 2) Adding the phone number query to a contact - * - * These shortcuts can be enabled or disabled to toggle whether or not they show up in the - * list. - */ -public class DialerPhoneNumberListAdapter extends PhoneNumberListAdapter { - - private String mFormattedQueryString; - private String mCountryIso; - - public final static int SHORTCUT_INVALID = -1; - public final static int SHORTCUT_DIRECT_CALL = 0; - public final static int SHORTCUT_CREATE_NEW_CONTACT = 1; - public final static int SHORTCUT_ADD_TO_EXISTING_CONTACT = 2; - public final static int SHORTCUT_SEND_SMS_MESSAGE = 3; - public final static int SHORTCUT_MAKE_VIDEO_CALL = 4; - public final static int SHORTCUT_BLOCK_NUMBER = 5; - - public final static int SHORTCUT_COUNT = 6; - - private final boolean[] mShortcutEnabled = new boolean[SHORTCUT_COUNT]; - - private final BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); - private boolean mVideoCallingEnabled = false; - - public DialerPhoneNumberListAdapter(Context context) { - super(context); - - mCountryIso = GeoUtil.getCurrentCountryIso(context); - mVideoCallingEnabled = CallUtil.isVideoEnabled(context); - } - - @Override - public int getCount() { - return super.getCount() + getShortcutCount(); - } - - /** - * @return The number of enabled shortcuts. Ranges from 0 to a maximum of SHORTCUT_COUNT - */ - public int getShortcutCount() { - int count = 0; - for (int i = 0; i < mShortcutEnabled.length; i++) { - if (mShortcutEnabled[i]) count++; - } - return count; - } - - public void disableAllShortcuts() { - for (int i = 0; i < mShortcutEnabled.length; i++) { - mShortcutEnabled[i] = false; - } - } - - @Override - public int getItemViewType(int position) { - final int shortcut = getShortcutTypeFromPosition(position); - if (shortcut >= 0) { - // shortcutPos should always range from 1 to SHORTCUT_COUNT - return super.getViewTypeCount() + shortcut; - } else { - return super.getItemViewType(position); - } - } - - @Override - public int getViewTypeCount() { - // Number of item view types in the super implementation + 2 for the 2 new shortcuts - return super.getViewTypeCount() + SHORTCUT_COUNT; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final int shortcutType = getShortcutTypeFromPosition(position); - if (shortcutType >= 0) { - if (convertView != null) { - assignShortcutToView((ContactListItemView) convertView, shortcutType); - return convertView; - } else { - final ContactListItemView v = new ContactListItemView(getContext(), null, - mVideoCallingEnabled); - assignShortcutToView(v, shortcutType); - return v; - } - } else { - return super.getView(position, convertView, parent); - } - } - - @Override - protected ContactListItemView newView( - Context context, int partition, Cursor cursor, int position, ViewGroup parent) { - final ContactListItemView view = super.newView(context, partition, cursor, position, - parent); - - view.setSupportVideoCallIcon(mVideoCallingEnabled); - return view; - } - - /** - * @param position The position of the item - * @return The enabled shortcut type matching the given position if the item is a - * shortcut, -1 otherwise - */ - public int getShortcutTypeFromPosition(int position) { - int shortcutCount = position - super.getCount(); - if (shortcutCount >= 0) { - // Iterate through the array of shortcuts, looking only for shortcuts where - // mShortcutEnabled[i] is true - for (int i = 0; shortcutCount >= 0 && i < mShortcutEnabled.length; i++) { - if (mShortcutEnabled[i]) { - shortcutCount--; - if (shortcutCount < 0) return i; - } - } - throw new IllegalArgumentException("Invalid position - greater than cursor count " - + " but not a shortcut."); - } - return SHORTCUT_INVALID; - } - - @Override - public boolean isEmpty() { - return getShortcutCount() == 0 && super.isEmpty(); - } - - @Override - public boolean isEnabled(int position) { - final int shortcutType = getShortcutTypeFromPosition(position); - if (shortcutType >= 0) { - return true; - } else { - return super.isEnabled(position); - } - } - - private void assignShortcutToView(ContactListItemView v, int shortcutType) { - final CharSequence text; - final int drawableId; - final Resources resources = getContext().getResources(); - final String number = getFormattedQueryString(); - switch (shortcutType) { - case SHORTCUT_DIRECT_CALL: - text = ContactDisplayUtils.getTtsSpannedPhoneNumber(resources, - R.string.search_shortcut_call_number, - mBidiFormatter.unicodeWrap(number, TextDirectionHeuristics.LTR)); - drawableId = R.drawable.ic_search_phone; - break; - case SHORTCUT_CREATE_NEW_CONTACT: - text = resources.getString(R.string.search_shortcut_create_new_contact); - drawableId = R.drawable.ic_search_add_contact; - break; - case SHORTCUT_ADD_TO_EXISTING_CONTACT: - text = resources.getString(R.string.search_shortcut_add_to_contact); - drawableId = R.drawable.ic_person_24dp; - break; - case SHORTCUT_SEND_SMS_MESSAGE: - text = resources.getString(R.string.search_shortcut_send_sms_message); - drawableId = R.drawable.ic_message_24dp; - break; - case SHORTCUT_MAKE_VIDEO_CALL: - text = resources.getString(R.string.search_shortcut_make_video_call); - drawableId = R.drawable.ic_videocam; - break; - case SHORTCUT_BLOCK_NUMBER: - text = resources.getString(R.string.search_shortcut_block_number); - drawableId = R.drawable.ic_not_interested_googblue_24dp; - break; - default: - throw new IllegalArgumentException("Invalid shortcut type"); - } - v.setDrawableResource(drawableId); - v.setDisplayName(text); - v.setPhotoPosition(super.getPhotoPosition()); - v.setAdjustSelectionBoundsEnabled(false); - } - - /** - * @return True if the shortcut state (disabled vs enabled) was changed by this operation - */ - public boolean setShortcutEnabled(int shortcutType, boolean visible) { - final boolean changed = mShortcutEnabled[shortcutType] != visible; - mShortcutEnabled[shortcutType] = visible; - return changed; - } - - public String getFormattedQueryString() { - return mFormattedQueryString; - } - - @Override - public void setQueryString(String queryString) { - mFormattedQueryString = PhoneNumberUtils.formatNumber( - PhoneNumberUtils.normalizeNumber(queryString), mCountryIso); - super.setQueryString(queryString); - } -} diff --git a/src/com/android/dialer/list/DragDropController.java b/src/com/android/dialer/list/DragDropController.java deleted file mode 100644 index 66ba513a8..000000000 --- a/src/com/android/dialer/list/DragDropController.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.android.dialer.list; - -import android.util.Log; -import android.view.View; - -import com.android.contacts.common.compat.CompatUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Class that handles and combines drag events generated from multiple views, and then fires - * off events to any OnDragDropListeners that have registered for callbacks. - */ -public class DragDropController { - - private final List<OnDragDropListener> mOnDragDropListeners = - new ArrayList<OnDragDropListener>(); - private final DragItemContainer mDragItemContainer; - private final int[] mLocationOnScreen = new int[2]; - - /** - * Callback interface used to retrieve views based on the current touch coordinates of the - * drag event. The {@link DragItemContainer} houses the draggable views that this - * {@link DragDropController} controls. - */ - public interface DragItemContainer { - public PhoneFavoriteSquareTileView getViewForLocation(int x, int y); - } - - public DragDropController(DragItemContainer dragItemContainer) { - mDragItemContainer = dragItemContainer; - } - - /** - * @return True if the drag is started, false if the drag is cancelled for some reason. - */ - boolean handleDragStarted(View v, int x, int y) { - int screenX = x; - int screenY = y; - // The coordinates in dragEvent of DragEvent.ACTION_DRAG_STARTED before NYC is window-related. - // This is fixed in NYC. - if (CompatUtils.isNCompatible()) { - v.getLocationOnScreen(mLocationOnScreen); - screenX = x + mLocationOnScreen[0]; - screenY = y + mLocationOnScreen[1]; - } - final PhoneFavoriteSquareTileView tileView = mDragItemContainer.getViewForLocation( - screenX, screenY); - if (tileView == null) { - return false; - } - for (int i = 0; i < mOnDragDropListeners.size(); i++) { - mOnDragDropListeners.get(i).onDragStarted(screenX, screenY, tileView); - } - - return true; - } - - public void handleDragHovered(View v, int x, int y) { - v.getLocationOnScreen(mLocationOnScreen); - final int screenX = x + mLocationOnScreen[0]; - final int screenY = y + mLocationOnScreen[1]; - final PhoneFavoriteSquareTileView view = mDragItemContainer.getViewForLocation( - screenX, screenY); - for (int i = 0; i < mOnDragDropListeners.size(); i++) { - mOnDragDropListeners.get(i).onDragHovered(screenX, screenY, view); - } - } - - public void handleDragFinished(int x, int y, boolean isRemoveView) { - if (isRemoveView) { - for (int i = 0; i < mOnDragDropListeners.size(); i++) { - mOnDragDropListeners.get(i).onDroppedOnRemove(); - } - } - - for (int i = 0; i < mOnDragDropListeners.size(); i++) { - mOnDragDropListeners.get(i).onDragFinished(x, y); - } - } - - public void addOnDragDropListener(OnDragDropListener listener) { - if (!mOnDragDropListeners.contains(listener)) { - mOnDragDropListeners.add(listener); - } - } - - public void removeOnDragDropListener(OnDragDropListener listener) { - if (mOnDragDropListeners.contains(listener)) { - mOnDragDropListeners.remove(listener); - } - } - -} diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java deleted file mode 100644 index 52bf3cbb5..000000000 --- a/src/com/android/dialer/list/ListsFragment.java +++ /dev/null @@ -1,487 +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.list; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.os.Bundle; -import android.os.Trace; -import android.preference.PreferenceManager; -import android.provider.CallLog.Calls; -import android.support.v13.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.contacts.common.list.ViewPagerTabs; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.R; -import com.android.dialer.calllog.CallLogFragment; -import com.android.dialer.calllog.CallLogNotificationsHelper; -import com.android.dialer.calllog.CallLogQueryHandler; -import com.android.dialer.calllog.VisualVoicemailCallLogFragment; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.voicemail.VisualVoicemailEnabledChecker; -import com.android.dialer.voicemail.VoicemailStatusHelper; -import com.android.dialer.voicemail.VoicemailStatusHelperImpl; -import com.android.dialer.widget.ActionBarController; - -import java.util.ArrayList; -import java.util.List; - -/** - * Fragment that is used as the main screen of the Dialer. - * - * Contains a ViewPager that contains various contact lists like the Speed Dial list and the - * All Contacts list. This will also eventually contain the logic that allows sliding the - * ViewPager containing the lists up above the search bar and pin it against the top of the - * screen. - */ -public class ListsFragment extends Fragment - implements ViewPager.OnPageChangeListener, CallLogQueryHandler.Listener { - - private static final boolean DEBUG = DialtactsActivity.DEBUG; - private static final String TAG = "ListsFragment"; - - public static final int TAB_INDEX_SPEED_DIAL = 0; - public static final int TAB_INDEX_HISTORY = 1; - public static final int TAB_INDEX_ALL_CONTACTS = 2; - public static final int TAB_INDEX_VOICEMAIL = 3; - - public static final int TAB_COUNT_DEFAULT = 3; - public static final int TAB_COUNT_WITH_VOICEMAIL = 4; - - public interface HostInterface { - public ActionBarController getActionBarController(); - } - - private ActionBar mActionBar; - private ViewPager mViewPager; - private ViewPagerTabs mViewPagerTabs; - private ViewPagerAdapter mViewPagerAdapter; - private RemoveView mRemoveView; - private View mRemoveViewContent; - - private SpeedDialFragment mSpeedDialFragment; - private CallLogFragment mHistoryFragment; - private AllContactsFragment mAllContactsFragment; - private CallLogFragment mVoicemailFragment; - - private SharedPreferences mPrefs; - private boolean mHasActiveVoicemailProvider; - private boolean mHasFetchedVoicemailStatus; - private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched; - - private VoicemailStatusHelper mVoicemailStatusHelper; - private ArrayList<OnPageChangeListener> mOnPageChangeListeners = - new ArrayList<OnPageChangeListener>(); - - private String[] mTabTitles; - private int[] mTabIcons; - - /** - * The position of the currently selected tab. - */ - private int mTabIndex = TAB_INDEX_SPEED_DIAL; - private CallLogQueryHandler mCallLogQueryHandler; - - public class ViewPagerAdapter extends FragmentPagerAdapter { - private final List<Fragment> mFragments = new ArrayList<>(); - - public ViewPagerAdapter(FragmentManager fm) { - super(fm); - for (int i = 0; i < TAB_COUNT_WITH_VOICEMAIL; i++) { - mFragments.add(null); - } - } - - @Override - public long getItemId(int position) { - return getRtlPosition(position); - } - - @Override - public Fragment getItem(int position) { - switch (getRtlPosition(position)) { - case TAB_INDEX_SPEED_DIAL: - mSpeedDialFragment = new SpeedDialFragment(); - return mSpeedDialFragment; - case TAB_INDEX_HISTORY: - mHistoryFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL); - return mHistoryFragment; - case TAB_INDEX_ALL_CONTACTS: - mAllContactsFragment = new AllContactsFragment(); - return mAllContactsFragment; - case TAB_INDEX_VOICEMAIL: - mVoicemailFragment = new VisualVoicemailCallLogFragment(); - return mVoicemailFragment; - } - throw new IllegalStateException("No fragment at position " + position); - } - - @Override - public Fragment instantiateItem(ViewGroup container, int position) { - // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called. - // Copy the fragments that the FragmentManager finds so that we can store them in - // instance variables for later. - final Fragment fragment = - (Fragment) super.instantiateItem(container, position); - if (fragment instanceof SpeedDialFragment) { - mSpeedDialFragment = (SpeedDialFragment) fragment; - } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_HISTORY) { - mHistoryFragment = (CallLogFragment) fragment; - } else if (fragment instanceof AllContactsFragment) { - mAllContactsFragment = (AllContactsFragment) fragment; - } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_VOICEMAIL) { - mVoicemailFragment = (CallLogFragment) fragment; - } - mFragments.set(position, fragment); - return fragment; - } - - /** - * When {@link android.support.v4.view.PagerAdapter#notifyDataSetChanged} is called, - * this method is called on all pages to determine whether they need to be recreated. - * When the voicemail tab is removed, the view needs to be recreated by returning - * POSITION_NONE. If notifyDataSetChanged is called for some other reason, the voicemail - * tab is recreated only if it is active. All other tabs do not need to be recreated - * and POSITION_UNCHANGED is returned. - */ - @Override - public int getItemPosition(Object object) { - return !mHasActiveVoicemailProvider && - mFragments.indexOf(object) == TAB_INDEX_VOICEMAIL ? POSITION_NONE : - POSITION_UNCHANGED; - } - - @Override - public int getCount() { - return mHasActiveVoicemailProvider ? TAB_COUNT_WITH_VOICEMAIL : TAB_COUNT_DEFAULT; - } - - @Override - public CharSequence getPageTitle(int position) { - return mTabTitles[position]; - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - Trace.beginSection(TAG + " onCreate"); - super.onCreate(savedInstanceState); - - mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); - mHasFetchedVoicemailStatus = false; - - mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - mHasActiveVoicemailProvider = mPrefs.getBoolean( - VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false); - - Trace.endSection(); - } - - @Override - public void onResume() { - Trace.beginSection(TAG + " onResume"); - super.onResume(); - - mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - if (getUserVisibleHint()) { - sendScreenViewForCurrentPosition(); - } - - // Fetch voicemail status to determine if we should show the voicemail tab. - mCallLogQueryHandler = - new CallLogQueryHandler(getActivity(), getActivity().getContentResolver(), this); - mCallLogQueryHandler.fetchVoicemailStatus(); - mCallLogQueryHandler.fetchMissedCallsUnreadCount(); - Trace.endSection(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Trace.beginSection(TAG + " onCreateView"); - Trace.beginSection(TAG + " inflate view"); - final View parentView = inflater.inflate(R.layout.lists_fragment, container, false); - Trace.endSection(); - Trace.beginSection(TAG + " setup views"); - mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager); - mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager()); - mViewPager.setAdapter(mViewPagerAdapter); - mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1); - mViewPager.setOnPageChangeListener(this); - showTab(TAB_INDEX_SPEED_DIAL); - - mTabTitles = new String[TAB_COUNT_WITH_VOICEMAIL]; - mTabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial); - mTabTitles[TAB_INDEX_HISTORY] = getResources().getString(R.string.tab_history); - mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts); - mTabTitles[TAB_INDEX_VOICEMAIL] = getResources().getString(R.string.tab_voicemail); - - mTabIcons = new int[TAB_COUNT_WITH_VOICEMAIL]; - mTabIcons[TAB_INDEX_SPEED_DIAL] = R.drawable.ic_grade_24dp; - mTabIcons[TAB_INDEX_HISTORY] = R.drawable.ic_schedule_24dp; - mTabIcons[TAB_INDEX_ALL_CONTACTS] = R.drawable.ic_people_24dp; - mTabIcons[TAB_INDEX_VOICEMAIL] = R.drawable.ic_voicemail_24dp; - - mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header); - mViewPagerTabs.configureTabIcons(mTabIcons); - mViewPagerTabs.setViewPager(mViewPager); - addOnPageChangeListener(mViewPagerTabs); - - mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view); - mRemoveViewContent = parentView.findViewById(R.id.remove_view_content); - - Trace.endSection(); - Trace.endSection(); - return parentView; - } - - public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) { - if (!mOnPageChangeListeners.contains(onPageChangeListener)) { - mOnPageChangeListeners.add(onPageChangeListener); - } - } - - /** - * Shows the tab with the specified index. If the voicemail tab index is specified, but the - * voicemail status hasn't been fetched, it will try to show the tab after the voicemail status - * has been fetched. - */ - public void showTab(int index) { - if (index == TAB_INDEX_VOICEMAIL) { - if (mHasActiveVoicemailProvider) { - mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_VOICEMAIL)); - } else if (!mHasFetchedVoicemailStatus) { - // Try to show the voicemail tab after the voicemail status returns. - mShowVoicemailTabAfterVoicemailStatusIsFetched = true; - } - } else if (index < getTabCount()){ - mViewPager.setCurrentItem(getRtlPosition(index)); - } - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mTabIndex = getRtlPosition(position); - - final int count = mOnPageChangeListeners.size(); - for (int i = 0; i < count; i++) { - mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset, - positionOffsetPixels); - } - } - - @Override - public void onPageSelected(int position) { - mTabIndex = getRtlPosition(position); - - // Show the tab which has been selected instead. - mShowVoicemailTabAfterVoicemailStatusIsFetched = false; - - final int count = mOnPageChangeListeners.size(); - for (int i = 0; i < count; i++) { - mOnPageChangeListeners.get(i).onPageSelected(position); - } - sendScreenViewForCurrentPosition(); - } - - @Override - public void onPageScrollStateChanged(int state) { - final int count = mOnPageChangeListeners.size(); - for (int i = 0; i < count; i++) { - mOnPageChangeListeners.get(i).onPageScrollStateChanged(state); - } - } - - @Override - public void onVoicemailStatusFetched(Cursor statusCursor) { - mHasFetchedVoicemailStatus = true; - - if (getActivity() == null || getActivity().isFinishing()) { - return; - } - - // Update mHasActiveVoicemailProvider, which controls the number of tabs displayed. - boolean hasActiveVoicemailProvider = - mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; - if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) { - mHasActiveVoicemailProvider = hasActiveVoicemailProvider; - mViewPagerAdapter.notifyDataSetChanged(); - - if (hasActiveVoicemailProvider) { - mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL); - } else { - mViewPagerTabs.removeTab(TAB_INDEX_VOICEMAIL); - removeVoicemailFragment(); - } - - mPrefs.edit() - .putBoolean(VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, - hasActiveVoicemailProvider) - .commit(); - } - - if (hasActiveVoicemailProvider) { - mCallLogQueryHandler.fetchVoicemailUnreadCount(); - } - - if (mHasActiveVoicemailProvider && mShowVoicemailTabAfterVoicemailStatusIsFetched) { - mShowVoicemailTabAfterVoicemailStatusIsFetched = false; - showTab(TAB_INDEX_VOICEMAIL); - } - } - - @Override - public void onVoicemailUnreadCountFetched(Cursor cursor) { - if (getActivity() == null || getActivity().isFinishing() || cursor == null) { - return; - } - - int count = 0; - try { - count = cursor.getCount(); - } finally { - cursor.close(); - } - - mViewPagerTabs.setUnreadCount(count, TAB_INDEX_VOICEMAIL); - mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL); - } - - @Override - public void onMissedCallsUnreadCountFetched(Cursor cursor) { - if (getActivity() == null || getActivity().isFinishing() || cursor == null) { - return; - } - - int count = 0; - try { - count = cursor.getCount(); - } finally { - cursor.close(); - } - - mViewPagerTabs.setUnreadCount(count, TAB_INDEX_HISTORY); - mViewPagerTabs.updateTab(TAB_INDEX_HISTORY); - } - - @Override - public boolean onCallsFetched(Cursor statusCursor) { - // Return false; did not take ownership of cursor - return false; - } - - public int getCurrentTabIndex() { - return mTabIndex; - } - - /** - * External method to update unread count because the unread count changes when the user - * expands a voicemail in the call log or when the user expands an unread call in the call - * history tab. - */ - public void updateTabUnreadCounts() { - if (mCallLogQueryHandler != null) { - mCallLogQueryHandler.fetchMissedCallsUnreadCount(); - if (mHasActiveVoicemailProvider) { - mCallLogQueryHandler.fetchVoicemailUnreadCount(); - } - } - } - - /** - * External method to mark all missed calls as read. - */ - public void markMissedCallsAsReadAndRemoveNotifications() { - if (mCallLogQueryHandler != null) { - mCallLogQueryHandler.markMissedCallsAsRead(); - CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); - } - } - - - public void showRemoveView(boolean show) { - mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE); - mRemoveView.setAlpha(show ? 0 : 1); - mRemoveView.animate().alpha(show ? 1 : 0).start(); - } - - public boolean shouldShowActionBar() { - // TODO: Update this based on scroll state. - return mActionBar != null; - } - - public SpeedDialFragment getSpeedDialFragment() { - return mSpeedDialFragment; - } - - public RemoveView getRemoveView() { - return mRemoveView; - } - - public int getTabCount() { - return mViewPagerAdapter.getCount(); - } - - private int getRtlPosition(int position) { - if (DialerUtils.isRtl()) { - return mViewPagerAdapter.getCount() - 1 - position; - } - return position; - } - - public void sendScreenViewForCurrentPosition() { - if (!isResumed()) { - return; - } - - int screenType; - switch (getCurrentTabIndex()) { - case TAB_INDEX_SPEED_DIAL: - screenType = ScreenEvent.SPEED_DIAL; - break; - case TAB_INDEX_HISTORY: - screenType = ScreenEvent.CALL_LOG; - break; - case TAB_INDEX_ALL_CONTACTS: - screenType = ScreenEvent.ALL_CONTACTS; - break; - case TAB_INDEX_VOICEMAIL: - screenType = ScreenEvent.VOICEMAIL_LOG; - default: - return; - } - Logger.logScreenView(screenType, getActivity()); - } - - private void removeVoicemailFragment() { - if (mVoicemailFragment != null) { - getChildFragmentManager().beginTransaction().remove(mVoicemailFragment) - .commitAllowingStateLoss(); - mVoicemailFragment = null; - } - } -} diff --git a/src/com/android/dialer/list/OnDragDropListener.java b/src/com/android/dialer/list/OnDragDropListener.java deleted file mode 100644 index c9ef50b09..000000000 --- a/src/com/android/dialer/list/OnDragDropListener.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.android.dialer.list; - - -/** - * Classes that want to receive callbacks in response to drag events should implement this - * interface. - */ -public interface OnDragDropListener { - /** - * Called when a drag is started. - * @param x X-coordinate of the drag event - * @param y Y-coordinate of the drag event - * @param view The contact tile which the drag was started on - */ - public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view); - - /** - * Called when a drag is in progress and the user moves the dragged contact to a - * location. - * - * @param x X-coordinate of the drag event - * @param y Y-coordinate of the drag event - * @param view Contact tile in the ListView which is currently being displaced - * by the dragged contact - */ - public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view); - - /** - * Called when a drag is completed (whether by dropping it somewhere or simply by dragging - * the contact off the screen) - * @param x X-coordinate of the drag event - * @param y Y-coordinate of the drag event - */ - public void onDragFinished(int x, int y); - - /** - * Called when a contact has been dropped on the remove view, indicating that the user - * wants to remove this contact. - */ - public void onDroppedOnRemove(); -}
\ No newline at end of file diff --git a/src/com/android/dialer/list/OnListFragmentScrolledListener.java b/src/com/android/dialer/list/OnListFragmentScrolledListener.java deleted file mode 100644 index 5ed3a6434..000000000 --- a/src/com/android/dialer/list/OnListFragmentScrolledListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2013 Google Inc. - * Licensed to 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.list; - -/* - * Interface to provide callback to activity when a child fragment is scrolled - */ -public interface OnListFragmentScrolledListener { - public void onListFragmentScrollStateChange(int scrollState); - public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, - int totalItemCount); -} diff --git a/src/com/android/dialer/list/PhoneFavoriteListView.java b/src/com/android/dialer/list/PhoneFavoriteListView.java deleted file mode 100644 index aad8ad58f..000000000 --- a/src/com/android/dialer/list/PhoneFavoriteListView.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * Licensed to 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.list; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.os.Handler; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.DragEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.GridView; -import android.widget.ImageView; - -import com.android.dialer.R; -import com.android.dialer.list.DragDropController.DragItemContainer; - -/** - * Viewgroup that presents the user's speed dial contacts in a grid. - */ -public class PhoneFavoriteListView extends GridView implements OnDragDropListener, - DragItemContainer { - - public static final String LOG_TAG = PhoneFavoriteListView.class.getSimpleName(); - - private float mTouchSlop; - - private int mTopScrollBound; - private int mBottomScrollBound; - private int mLastDragY; - - private Handler mScrollHandler; - private final long SCROLL_HANDLER_DELAY_MILLIS = 5; - private final int DRAG_SCROLL_PX_UNIT = 25; - - private boolean mIsDragScrollerRunning = false; - private int mTouchDownForDragStartX; - private int mTouchDownForDragStartY; - - private Bitmap mDragShadowBitmap; - private ImageView mDragShadowOverlay; - private View mDragShadowParent; - private int mAnimationDuration; - - final int[] mLocationOnScreen = new int[2]; - - // X and Y offsets inside the item from where the user grabbed to the - // child's left coordinate. This is used to aid in the drawing of the drag shadow. - private int mTouchOffsetToChildLeft; - private int mTouchOffsetToChildTop; - - private int mDragShadowLeft; - private int mDragShadowTop; - - private DragDropController mDragDropController = new DragDropController(this); - - private final float DRAG_SHADOW_ALPHA = 0.7f; - - /** - * {@link #mTopScrollBound} and {@link mBottomScrollBound} will be - * offseted to the top / bottom by {@link #getHeight} * {@link #BOUND_GAP_RATIO} pixels. - */ - private final float BOUND_GAP_RATIO = 0.2f; - - private final Runnable mDragScroller = new Runnable() { - @Override - public void run() { - if (mLastDragY <= mTopScrollBound) { - smoothScrollBy(-DRAG_SCROLL_PX_UNIT, (int) SCROLL_HANDLER_DELAY_MILLIS); - } else if (mLastDragY >= mBottomScrollBound) { - smoothScrollBy(DRAG_SCROLL_PX_UNIT, (int) SCROLL_HANDLER_DELAY_MILLIS); - } - mScrollHandler.postDelayed(this, SCROLL_HANDLER_DELAY_MILLIS); - } - }; - - private final AnimatorListenerAdapter mDragShadowOverAnimatorListener = - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mDragShadowBitmap != null) { - mDragShadowBitmap.recycle(); - mDragShadowBitmap = null; - } - mDragShadowOverlay.setVisibility(GONE); - mDragShadowOverlay.setImageBitmap(null); - } - }; - - public PhoneFavoriteListView(Context context) { - this(context, null); - } - - public PhoneFavoriteListView(Context context, AttributeSet attrs) { - this(context, attrs, -1); - } - - public PhoneFavoriteListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mAnimationDuration = context.getResources().getInteger(R.integer.fade_duration); - mTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop(); - mDragDropController.addOnDragDropListener(this); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - } - - /** - * TODO: This is all swipe to remove code (nothing to do with drag to remove). This should - * be cleaned up and removed once drag to remove becomes the only way to remove contacts. - */ - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mTouchDownForDragStartX = (int) ev.getX(); - mTouchDownForDragStartY = (int) ev.getY(); - } - - return super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onDragEvent(DragEvent event) { - final int action = event.getAction(); - final int eX = (int) event.getX(); - final int eY = (int) event.getY(); - switch (action) { - case DragEvent.ACTION_DRAG_STARTED: { - if (!PhoneFavoriteTileView.DRAG_PHONE_FAVORITE_TILE.equals(event.getLocalState())) { - // Ignore any drag events that were not propagated by long pressing - // on a {@link PhoneFavoriteTileView} - return false; - } - if (!mDragDropController.handleDragStarted(this, eX, eY)) { - return false; - } - break; - } - case DragEvent.ACTION_DRAG_LOCATION: - mLastDragY = eY; - mDragDropController.handleDragHovered(this, eX, eY); - // Kick off {@link #mScrollHandler} if it's not started yet. - if (!mIsDragScrollerRunning && - // And if the distance traveled while dragging exceeds the touch slop - (Math.abs(mLastDragY - mTouchDownForDragStartY) >= 4 * mTouchSlop)) { - mIsDragScrollerRunning = true; - ensureScrollHandler(); - mScrollHandler.postDelayed(mDragScroller, SCROLL_HANDLER_DELAY_MILLIS); - } - break; - case DragEvent.ACTION_DRAG_ENTERED: - final int boundGap = (int) (getHeight() * BOUND_GAP_RATIO); - mTopScrollBound = (getTop() + boundGap); - mBottomScrollBound = (getBottom() - boundGap); - break; - case DragEvent.ACTION_DRAG_EXITED: - case DragEvent.ACTION_DRAG_ENDED: - case DragEvent.ACTION_DROP: - ensureScrollHandler(); - mScrollHandler.removeCallbacks(mDragScroller); - mIsDragScrollerRunning = false; - // Either a successful drop or it's ended with out drop. - if (action == DragEvent.ACTION_DROP || action == DragEvent.ACTION_DRAG_ENDED) { - mDragDropController.handleDragFinished(eX, eY, false); - } - break; - default: - break; - } - // This ListView will consume the drag events on behalf of its children. - return true; - } - - public void setDragShadowOverlay(ImageView overlay) { - mDragShadowOverlay = overlay; - mDragShadowParent = (View) mDragShadowOverlay.getParent(); - } - - /** - * Find the view under the pointer. - */ - private View getViewAtPosition(int x, int y) { - final int count = getChildCount(); - View child; - for (int childIdx = 0; childIdx < count; childIdx++) { - child = getChildAt(childIdx); - if (y >= child.getTop() && y <= child.getBottom() && x >= child.getLeft() - && x <= child.getRight()) { - return child; - } - } - return null; - } - - private void ensureScrollHandler() { - if (mScrollHandler == null) { - mScrollHandler = getHandler(); - } - } - - public DragDropController getDragDropController() { - return mDragDropController; - } - - @Override - public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView tileView) { - if (mDragShadowOverlay == null) { - return; - } - - mDragShadowOverlay.clearAnimation(); - mDragShadowBitmap = createDraggedChildBitmap(tileView); - if (mDragShadowBitmap == null) { - return; - } - - tileView.getLocationOnScreen(mLocationOnScreen); - mDragShadowLeft = mLocationOnScreen[0]; - mDragShadowTop = mLocationOnScreen[1]; - - // x and y are the coordinates of the on-screen touch event. Using these - // and the on-screen location of the tileView, calculate the difference between - // the position of the user's finger and the position of the tileView. These will - // be used to offset the location of the drag shadow so that it appears that the - // tileView is positioned directly under the user's finger. - mTouchOffsetToChildLeft = x - mDragShadowLeft; - mTouchOffsetToChildTop = y - mDragShadowTop; - - mDragShadowParent.getLocationOnScreen(mLocationOnScreen); - mDragShadowLeft -= mLocationOnScreen[0]; - mDragShadowTop -= mLocationOnScreen[1]; - - mDragShadowOverlay.setImageBitmap(mDragShadowBitmap); - mDragShadowOverlay.setVisibility(VISIBLE); - mDragShadowOverlay.setAlpha(DRAG_SHADOW_ALPHA); - - mDragShadowOverlay.setX(mDragShadowLeft); - mDragShadowOverlay.setY(mDragShadowTop); - } - - @Override - public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView tileView) { - // Update the drag shadow location. - mDragShadowParent.getLocationOnScreen(mLocationOnScreen); - mDragShadowLeft = x - mTouchOffsetToChildLeft - mLocationOnScreen[0]; - mDragShadowTop = y - mTouchOffsetToChildTop - mLocationOnScreen[1]; - // Draw the drag shadow at its last known location if the drag shadow exists. - if (mDragShadowOverlay != null) { - mDragShadowOverlay.setX(mDragShadowLeft); - mDragShadowOverlay.setY(mDragShadowTop); - } - } - - @Override - public void onDragFinished(int x, int y) { - if (mDragShadowOverlay != null) { - mDragShadowOverlay.clearAnimation(); - mDragShadowOverlay.animate().alpha(0.0f) - .setDuration(mAnimationDuration) - .setListener(mDragShadowOverAnimatorListener) - .start(); - } - } - - @Override - public void onDroppedOnRemove() {} - - private Bitmap createDraggedChildBitmap(View view) { - view.setDrawingCacheEnabled(true); - final Bitmap cache = view.getDrawingCache(); - - Bitmap bitmap = null; - if (cache != null) { - try { - bitmap = cache.copy(Bitmap.Config.ARGB_8888, false); - } catch (final OutOfMemoryError e) { - Log.w(LOG_TAG, "Failed to copy bitmap from Drawing cache", e); - bitmap = null; - } - } - - view.destroyDrawingCache(); - view.setDrawingCacheEnabled(false); - - return bitmap; - } - - @Override - public PhoneFavoriteSquareTileView getViewForLocation(int x, int y) { - getLocationOnScreen(mLocationOnScreen); - // Calculate the X and Y coordinates of the drag event relative to the view - final int viewX = x - mLocationOnScreen[0]; - final int viewY = y - mLocationOnScreen[1]; - final View child = getViewAtPosition(viewX, viewY); - - if (!(child instanceof PhoneFavoriteSquareTileView)) { - return null; - } - - return (PhoneFavoriteSquareTileView) child; - } -} diff --git a/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java b/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java deleted file mode 100644 index 69a230c8a..000000000 --- a/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java +++ /dev/null @@ -1,112 +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.list; - -import android.content.Context; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.QuickContact; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.list.ContactEntry; -import com.android.dialer.R; - -/** - * Displays the contact's picture overlaid with their name and number type in a tile. - */ -public class PhoneFavoriteSquareTileView extends PhoneFavoriteTileView { - private static final String TAG = PhoneFavoriteSquareTileView.class.getSimpleName(); - - private final float mHeightToWidthRatio; - - private ImageButton mSecondaryButton; - - private ContactEntry mContactEntry; - - public PhoneFavoriteSquareTileView(Context context, AttributeSet attrs) { - super(context, attrs); - - mHeightToWidthRatio = getResources().getFraction( - R.dimen.contact_tile_height_to_width_ratio, 1, 1); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - final TextView nameView = (TextView) findViewById(R.id.contact_tile_name); - nameView.setElegantTextHeight(false); - final TextView phoneTypeView = (TextView) findViewById(R.id.contact_tile_phone_type); - phoneTypeView.setElegantTextHeight(false); - mSecondaryButton = (ImageButton) findViewById(R.id.contact_tile_secondary_button); - } - - @Override - protected int getApproximateImageSize() { - // The picture is the full size of the tile (minus some padding, but we can be generous) - return getWidth(); - } - - private void launchQuickContact() { - if (CompatUtils.hasPrioritizedMimeType()) { - QuickContact.showQuickContact(getContext(), PhoneFavoriteSquareTileView.this, - getLookupUri(), null, Phone.CONTENT_ITEM_TYPE); - } else { - QuickContact.showQuickContact(getContext(), PhoneFavoriteSquareTileView.this, - getLookupUri(), QuickContact.MODE_LARGE, null); - } - } - - @Override - public void loadFromContact(ContactEntry entry) { - super.loadFromContact(entry); - if (entry != null) { - mSecondaryButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - launchQuickContact(); - } - }); - } - mContactEntry = entry; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int width = MeasureSpec.getSize(widthMeasureSpec); - final int height = (int) (mHeightToWidthRatio * width); - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - getChildAt(i).measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) - ); - } - setMeasuredDimension(width, height); - } - - @Override - protected String getNameForView(ContactEntry contactEntry) { - return contactEntry.getPreferredDisplayName(); - } - - public ContactEntry getContactEntry() { - return mContactEntry; - } -} diff --git a/src/com/android/dialer/list/PhoneFavoriteTileView.java b/src/com/android/dialer/list/PhoneFavoriteTileView.java deleted file mode 100644 index 56d0b5d22..000000000 --- a/src/com/android/dialer/list/PhoneFavoriteTileView.java +++ /dev/null @@ -1,155 +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.list; - -import android.content.ClipData; -import android.content.Context; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; - -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.MoreContactUtils; -import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; -import com.android.contacts.common.list.ContactEntry; -import com.android.contacts.common.list.ContactTileView; -import com.android.dialer.R; - -/** - * A light version of the {@link com.android.contacts.common.list.ContactTileView} that is used in - * Dialtacts for frequently called contacts. Slightly different behavior from superclass when you - * tap it, you want to call the frequently-called number for the contact, even if that is not the - * default number for that contact. This abstract class is the super class to both the row and tile - * view. - */ -public abstract class PhoneFavoriteTileView extends ContactTileView { - - private static final String TAG = PhoneFavoriteTileView.class.getSimpleName(); - private static final boolean DEBUG = false; - - // These parameters instruct the photo manager to display the default image/letter at 70% of - // its normal size, and vertically offset upwards 12% towards the top of the letter tile, to - // make room for the contact name and number label at the bottom of the image. - private static final float DEFAULT_IMAGE_LETTER_OFFSET = -0.12f; - private static final float DEFAULT_IMAGE_LETTER_SCALE = 0.70f; - - /** View that contains the transparent shadow that is overlaid on top of the contact image. */ - private View mShadowOverlay; - - /** Users' most frequent phone number. */ - private String mPhoneNumberString; - - // Dummy clip data object that is attached to drag shadows so that text views - // don't crash with an NPE if the drag shadow is released in their bounds - private static final ClipData EMPTY_CLIP_DATA = ClipData.newPlainText("", ""); - - // Constant to pass to the drag event so that the drag action only happens when a phone favorite - // tile is long pressed. - static final String DRAG_PHONE_FAVORITE_TILE = "PHONE_FAVORITE_TILE"; - - public PhoneFavoriteTileView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mShadowOverlay = findViewById(R.id.shadow_overlay); - - setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - final PhoneFavoriteTileView view = (PhoneFavoriteTileView) v; - // NOTE The drag shadow is handled in the ListView. - view.startDrag(EMPTY_CLIP_DATA, new View.DragShadowBuilder(), - DRAG_PHONE_FAVORITE_TILE, 0); - return true; - } - }); - } - - @Override - public void loadFromContact(ContactEntry entry) { - super.loadFromContact(entry); - // Set phone number to null in case we're reusing the view. - mPhoneNumberString = null; - if (entry != null) { - // Grab the phone-number to call directly. See {@link onClick()}. - mPhoneNumberString = entry.phoneNumber; - - // If this is a blank entry, don't show anything. - // TODO krelease: Just hide the view for now. For this to truly look like an empty row - // the entire ContactTileRow needs to be hidden. - if (entry == ContactEntry.BLANK_ENTRY) { - setVisibility(View.INVISIBLE); - } else { - final ImageView starIcon = (ImageView) findViewById(R.id.contact_star_icon); - starIcon.setVisibility(entry.isFavorite ? View.VISIBLE : View.GONE); - setVisibility(View.VISIBLE); - } - } - } - - @Override - protected boolean isDarkTheme() { - return false; - } - - @Override - protected OnClickListener createClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - if (mListener == null) { - return; - } - if (TextUtils.isEmpty(mPhoneNumberString)) { - // Copy "superclass" implementation - mListener.onContactSelected(getLookupUri(), MoreContactUtils - .getTargetRectFromView(PhoneFavoriteTileView.this)); - } else { - // When you tap a frequently-called contact, you want to - // call them at the number that you usually talk to them - // at (i.e. the one displayed in the UI), regardless of - // whether that's their default number. - mListener.onCallNumberDirectly(mPhoneNumberString); - } - } - }; - } - - @Override - protected DefaultImageRequest getDefaultImageRequest(String displayName, String lookupKey) { - return new DefaultImageRequest(displayName, lookupKey, ContactPhotoManager.TYPE_DEFAULT, - DEFAULT_IMAGE_LETTER_SCALE, DEFAULT_IMAGE_LETTER_OFFSET, false); - } - - @Override - protected void configureViewForImage(boolean isDefaultImage) { - // Hide the shadow overlay if the image is a default image (i.e. colored letter tile) - if (mShadowOverlay != null) { - mShadowOverlay.setVisibility(isDefaultImage ? View.GONE : View.VISIBLE); - } - } - - @Override - protected boolean isContactPhotoCircular() { - // Unlike Contacts' tiles, the Dialer's favorites tiles are square. - return false; - } -} diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java deleted file mode 100644 index 77da7e937..000000000 --- a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java +++ /dev/null @@ -1,696 +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.list; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.Lists; - -import android.content.ContentProviderOperation; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.PinnedPositions; -import android.text.TextUtils; -import android.util.Log; -import android.util.LongSparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.ContactTileLoaderFactory; -import com.android.contacts.common.list.ContactEntry; -import com.android.contacts.common.list.ContactTileAdapter.DisplayType; -import com.android.contacts.common.list.ContactTileView; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.dialer.R; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.PriorityQueue; - -/** - * Also allows for a configurable number of columns as well as a maximum row of tiled contacts. - */ -public class PhoneFavoritesTileAdapter extends BaseAdapter implements - OnDragDropListener { - private static final String TAG = PhoneFavoritesTileAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; - - public static final int NO_ROW_LIMIT = -1; - - public static final int ROW_LIMIT_DEFAULT = NO_ROW_LIMIT; - - private ContactTileView.Listener mListener; - private OnDataSetChangedForAnimationListener mDataSetChangedListener; - - private Context mContext; - private Resources mResources; - private ContactsPreferences mContactsPreferences; - - /** Contact data stored in cache. This is used to populate the associated view. */ - protected ArrayList<ContactEntry> mContactEntries = null; - /** Back up of the temporarily removed Contact during dragging. */ - private ContactEntry mDraggedEntry = null; - /** Position of the temporarily removed contact in the cache. */ - private int mDraggedEntryIndex = -1; - /** New position of the temporarily removed contact in the cache. */ - private int mDropEntryIndex = -1; - /** New position of the temporarily entered contact in the cache. */ - private int mDragEnteredEntryIndex = -1; - - private boolean mAwaitingRemove = false; - private boolean mDelayCursorUpdates = false; - - private ContactPhotoManager mPhotoManager; - protected int mNumFrequents; - protected int mNumStarred; - - protected int mIdIndex; - protected int mLookupIndex; - protected int mPhotoUriIndex; - protected int mNamePrimaryIndex; - protected int mNameAlternativeIndex; - protected int mPresenceIndex; - protected int mStatusIndex; - - private int mPhoneNumberIndex; - private int mPhoneNumberTypeIndex; - private int mPhoneNumberLabelIndex; - private int mIsDefaultNumberIndex; - private int mStarredIndex; - protected int mPinnedIndex; - protected int mContactIdIndex; - - /** Indicates whether a drag is in process. */ - private boolean mInDragging = false; - - // Pinned positions start from 1, so there are a total of 20 maximum pinned contacts - public static final int PIN_LIMIT = 21; - - /** - * The soft limit on how many contact tiles to show. - * NOTE This soft limit would not restrict the number of starred contacts to show, rather - * 1. If the count of starred contacts is less than this limit, show 20 tiles total. - * 2. If the count of starred contacts is more than or equal to this limit, - * show all starred tiles and no frequents. - */ - private static final int TILES_SOFT_LIMIT = 20; - - final Comparator<ContactEntry> mContactEntryComparator = new Comparator<ContactEntry>() { - @Override - public int compare(ContactEntry lhs, ContactEntry rhs) { - return ComparisonChain.start() - .compare(lhs.pinned, rhs.pinned) - .compare(getPreferredSortName(lhs), getPreferredSortName(rhs)) - .result(); - } - - private String getPreferredSortName(ContactEntry contactEntry) { - if (mContactsPreferences.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY - || TextUtils.isEmpty(contactEntry.nameAlternative)) { - return contactEntry.namePrimary; - } - return contactEntry.nameAlternative; - } - }; - - public interface OnDataSetChangedForAnimationListener { - public void onDataSetChangedForAnimation(long... idsInPlace); - public void cacheOffsetsForDatasetChange(); - }; - - public PhoneFavoritesTileAdapter(Context context, ContactTileView.Listener listener, - OnDataSetChangedForAnimationListener dataSetChangedListener) { - mDataSetChangedListener = dataSetChangedListener; - mListener = listener; - mContext = context; - mResources = context.getResources(); - mContactsPreferences = new ContactsPreferences(mContext); - mNumFrequents = 0; - mContactEntries = new ArrayList<ContactEntry>(); - - - bindColumnIndices(); - } - - public void setPhotoLoader(ContactPhotoManager photoLoader) { - mPhotoManager = photoLoader; - } - - /** - * Indicates whether a drag is in process. - * - * @param inDragging Boolean variable indicating whether there is a drag in process. - */ - public void setInDragging(boolean inDragging) { - mDelayCursorUpdates = inDragging; - mInDragging = inDragging; - } - - /** Gets whether the drag is in process. */ - public boolean getInDragging() { - return mInDragging; - } - - /** - * Sets the column indices for expected {@link Cursor} - * based on {@link DisplayType}. - */ - protected void bindColumnIndices() { - mIdIndex = ContactTileLoaderFactory.CONTACT_ID; - mNamePrimaryIndex = ContactTileLoaderFactory.DISPLAY_NAME; - mNameAlternativeIndex = ContactTileLoaderFactory.DISPLAY_NAME_ALTERNATIVE; - mStarredIndex = ContactTileLoaderFactory.STARRED; - mPhotoUriIndex = ContactTileLoaderFactory.PHOTO_URI; - mLookupIndex = ContactTileLoaderFactory.LOOKUP_KEY; - mPhoneNumberIndex = ContactTileLoaderFactory.PHONE_NUMBER; - mPhoneNumberTypeIndex = ContactTileLoaderFactory.PHONE_NUMBER_TYPE; - mPhoneNumberLabelIndex = ContactTileLoaderFactory.PHONE_NUMBER_LABEL; - mPinnedIndex = ContactTileLoaderFactory.PINNED; - mContactIdIndex = ContactTileLoaderFactory.CONTACT_ID_FOR_DATA; - - - mPresenceIndex = ContactTileLoaderFactory.CONTACT_PRESENCE; - mStatusIndex = ContactTileLoaderFactory.CONTACT_STATUS; - mIsDefaultNumberIndex = ContactTileLoaderFactory.IS_DEFAULT_NUMBER; - } - - public void refreshContactsPreferences() { - mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); - mContactsPreferences.refreshValue(ContactsPreferences.SORT_ORDER_KEY); - } - - /** - * Gets the number of frequents from the passed in cursor. - * - * This methods is needed so the GroupMemberTileAdapter can override this. - * - * @param cursor The cursor to get number of frequents from. - */ - protected void saveNumFrequentsFromCursor(Cursor cursor) { - mNumFrequents = cursor.getCount() - mNumStarred; - } - - /** - * Creates {@link ContactTileView}s for each item in {@link Cursor}. - * - * Else use {@link ContactTileLoaderFactory} - */ - public void setContactCursor(Cursor cursor) { - if (!mDelayCursorUpdates && cursor != null && !cursor.isClosed()) { - mNumStarred = getNumStarredContacts(cursor); - if (mAwaitingRemove) { - mDataSetChangedListener.cacheOffsetsForDatasetChange(); - } - - saveNumFrequentsFromCursor(cursor); - saveCursorToCache(cursor); - // cause a refresh of any views that rely on this data - notifyDataSetChanged(); - // about to start redraw - mDataSetChangedListener.onDataSetChangedForAnimation(); - } - } - - /** - * Saves the cursor data to the cache, to speed up UI changes. - * - * @param cursor Returned cursor with data to populate the view. - */ - private void saveCursorToCache(Cursor cursor) { - mContactEntries.clear(); - - cursor.moveToPosition(-1); - - final LongSparseArray<Object> duplicates = new LongSparseArray<Object>(cursor.getCount()); - - // Track the length of {@link #mContactEntries} and compare to {@link #TILES_SOFT_LIMIT}. - int counter = 0; - - while (cursor.moveToNext()) { - - final int starred = cursor.getInt(mStarredIndex); - final long id; - - // We display a maximum of TILES_SOFT_LIMIT contacts, or the total number of starred - // whichever is greater. - if (starred < 1 && counter >= TILES_SOFT_LIMIT) { - break; - } else { - id = cursor.getLong(mContactIdIndex); - } - - final ContactEntry existing = (ContactEntry) duplicates.get(id); - if (existing != null) { - // Check if the existing number is a default number. If not, clear the phone number - // and label fields so that the disambiguation dialog will show up. - if (!existing.isDefaultNumber) { - existing.phoneLabel = null; - existing.phoneNumber = null; - } - continue; - } - - final String photoUri = cursor.getString(mPhotoUriIndex); - final String lookupKey = cursor.getString(mLookupIndex); - final int pinned = cursor.getInt(mPinnedIndex); - final String name = cursor.getString(mNamePrimaryIndex); - final String nameAlternative = cursor.getString(mNameAlternativeIndex); - final boolean isStarred = cursor.getInt(mStarredIndex) > 0; - final boolean isDefaultNumber = cursor.getInt(mIsDefaultNumberIndex) > 0; - - final ContactEntry contact = new ContactEntry(); - - contact.id = id; - contact.namePrimary = (!TextUtils.isEmpty(name)) ? name : - mResources.getString(R.string.missing_name); - contact.nameAlternative = (!TextUtils.isEmpty(nameAlternative)) ? nameAlternative : - mResources.getString(R.string.missing_name); - contact.nameDisplayOrder = mContactsPreferences.getDisplayOrder(); - contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null); - contact.lookupKey = lookupKey; - contact.lookupUri = ContentUris.withAppendedId( - Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id); - contact.isFavorite = isStarred; - contact.isDefaultNumber = isDefaultNumber; - - // Set phone number and label - final int phoneNumberType = cursor.getInt(mPhoneNumberTypeIndex); - final String phoneNumberCustomLabel = cursor.getString(mPhoneNumberLabelIndex); - contact.phoneLabel = (String) Phone.getTypeLabel(mResources, phoneNumberType, - phoneNumberCustomLabel); - contact.phoneNumber = cursor.getString(mPhoneNumberIndex); - - contact.pinned = pinned; - mContactEntries.add(contact); - - duplicates.put(id, contact); - - counter++; - } - - mAwaitingRemove = false; - - arrangeContactsByPinnedPosition(mContactEntries); - - notifyDataSetChanged(); - } - - /** - * Iterates over the {@link Cursor} - * Returns position of the first NON Starred Contact - * Returns -1 if {@link DisplayType#STARRED_ONLY} - * Returns 0 if {@link DisplayType#FREQUENT_ONLY} - */ - protected int getNumStarredContacts(Cursor cursor) { - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { - if (cursor.getInt(mStarredIndex) == 0) { - return cursor.getPosition(); - } - } - - // There are not NON Starred contacts in cursor - // Set divider positon to end - return cursor.getCount(); - } - - /** - * Returns the number of frequents that will be displayed in the list. - */ - public int getNumFrequents() { - return mNumFrequents; - } - - @Override - public int getCount() { - if (mContactEntries == null) { - return 0; - } - - return mContactEntries.size(); - } - - /** - * Returns an ArrayList of the {@link ContactEntry}s that are to appear - * on the row for the given position. - */ - @Override - public ContactEntry getItem(int position) { - return mContactEntries.get(position); - } - - /** - * For the top row of tiled contacts, the item id is the position of the row of - * contacts. - * For frequent contacts, the item id is the maximum number of rows of tiled contacts + - * the actual contact id. Since contact ids are always greater than 0, this guarantees that - * all items within this adapter will always have unique ids. - */ - @Override - public long getItemId(int position) { - return getItem(position).id; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return getCount() > 0; - } - - @Override - public void notifyDataSetChanged() { - if (DEBUG) { - Log.v(TAG, "notifyDataSetChanged"); - } - super.notifyDataSetChanged(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (DEBUG) { - Log.v(TAG, "get view for " + String.valueOf(position)); - } - - int itemViewType = getItemViewType(position); - - PhoneFavoriteTileView tileView = null; - - if (convertView instanceof PhoneFavoriteTileView) { - tileView = (PhoneFavoriteTileView) convertView; - } - - if (tileView == null) { - tileView = (PhoneFavoriteTileView) View.inflate(mContext, - R.layout.phone_favorite_tile_view, null); - } - tileView.setPhotoManager(mPhotoManager); - tileView.setListener(mListener); - tileView.loadFromContact(getItem(position)); - return tileView; - } - - @Override - public int getViewTypeCount() { - return ViewTypes.COUNT; - } - - @Override - public int getItemViewType(int position) { - return ViewTypes.TILE; - } - - /** - * Temporarily removes a contact from the list for UI refresh. Stores data for this contact - * in the back-up variable. - * - * @param index Position of the contact to be removed. - */ - public void popContactEntry(int index) { - if (isIndexInBound(index)) { - mDraggedEntry = mContactEntries.get(index); - mDraggedEntryIndex = index; - mDragEnteredEntryIndex = index; - markDropArea(mDragEnteredEntryIndex); - } - } - - /** - * @param itemIndex Position of the contact in {@link #mContactEntries}. - * @return True if the given index is valid for {@link #mContactEntries}. - */ - public boolean isIndexInBound(int itemIndex) { - return itemIndex >= 0 && itemIndex < mContactEntries.size(); - } - - /** - * Mark the tile as drop area by given the item index in {@link #mContactEntries}. - * - * @param itemIndex Position of the contact in {@link #mContactEntries}. - */ - private void markDropArea(int itemIndex) { - if (mDraggedEntry != null && isIndexInBound(mDragEnteredEntryIndex) && - isIndexInBound(itemIndex)) { - mDataSetChangedListener.cacheOffsetsForDatasetChange(); - // Remove the old placeholder item and place the new placeholder item. - final int oldIndex = mDragEnteredEntryIndex; - mContactEntries.remove(mDragEnteredEntryIndex); - mDragEnteredEntryIndex = itemIndex; - mContactEntries.add(mDragEnteredEntryIndex, ContactEntry.BLANK_ENTRY); - ContactEntry.BLANK_ENTRY.id = mDraggedEntry.id; - mDataSetChangedListener.onDataSetChangedForAnimation(); - notifyDataSetChanged(); - } - } - - /** - * Drops the temporarily removed contact to the desired location in the list. - */ - public void handleDrop() { - boolean changed = false; - if (mDraggedEntry != null) { - if (isIndexInBound(mDragEnteredEntryIndex) && - mDragEnteredEntryIndex != mDraggedEntryIndex) { - // Don't add the ContactEntry here (to prevent a double animation from occuring). - // When we receive a new cursor the list of contact entries will automatically be - // populated with the dragged ContactEntry at the correct spot. - mDropEntryIndex = mDragEnteredEntryIndex; - mContactEntries.set(mDropEntryIndex, mDraggedEntry); - mDataSetChangedListener.cacheOffsetsForDatasetChange(); - changed = true; - } else if (isIndexInBound(mDraggedEntryIndex)) { - // If {@link #mDragEnteredEntryIndex} is invalid, - // falls back to the original position of the contact. - mContactEntries.remove(mDragEnteredEntryIndex); - mContactEntries.add(mDraggedEntryIndex, mDraggedEntry); - mDropEntryIndex = mDraggedEntryIndex; - notifyDataSetChanged(); - } - - if (changed && mDropEntryIndex < PIN_LIMIT) { - final ArrayList<ContentProviderOperation> operations = - getReflowedPinningOperations(mContactEntries, mDraggedEntryIndex, - mDropEntryIndex); - if (!operations.isEmpty()) { - // update the database here with the new pinned positions - try { - mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, - operations); - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "Exception thrown when pinning contacts", e); - } - } - } - mDraggedEntry = null; - } - } - - /** - * Invoked when the dragged item is dropped to unsupported location. We will then move the - * contact back to where it was dragged from. - */ - public void dropToUnsupportedView() { - if (isIndexInBound(mDragEnteredEntryIndex)) { - mContactEntries.remove(mDragEnteredEntryIndex); - mContactEntries.add(mDraggedEntryIndex, mDraggedEntry); - notifyDataSetChanged(); - } - } - - /** - * Clears all temporary variables at a new interaction. - */ - public void cleanTempVariables() { - mDraggedEntryIndex = -1; - mDropEntryIndex = -1; - mDragEnteredEntryIndex = -1; - mDraggedEntry = null; - } - - /** - * Used when a contact is removed from speeddial. This will both unstar and set pinned position - * of the contact to PinnedPosition.DEMOTED so that it doesn't show up anymore in the favorites - * list. - */ - private void unstarAndUnpinContact(Uri contactUri) { - final ContentValues values = new ContentValues(2); - values.put(Contacts.STARRED, false); - values.put(Contacts.PINNED, PinnedPositions.DEMOTED); - mContext.getContentResolver().update(contactUri, values, null, null); - } - - /** - * Given a list of contacts that each have pinned positions, rearrange the list (destructive) - * such that all pinned contacts are in their defined pinned positions, and unpinned contacts - * take the spaces between those pinned contacts. Demoted contacts should not appear in the - * resulting list. - * - * This method also updates the pinned positions of pinned contacts so that they are all - * unique positive integers within range from 0 to toArrange.size() - 1. This is because - * when the contact entries are read from the database, it is possible for them to have - * overlapping pin positions due to sync or modifications by third party apps. - */ - @VisibleForTesting - /* package */ void arrangeContactsByPinnedPosition(ArrayList<ContactEntry> toArrange) { - final PriorityQueue<ContactEntry> pinnedQueue = - new PriorityQueue<ContactEntry>(PIN_LIMIT, mContactEntryComparator); - - final List<ContactEntry> unpinnedContacts = new LinkedList<ContactEntry>(); - - final int length = toArrange.size(); - for (int i = 0; i < length; i++) { - final ContactEntry contact = toArrange.get(i); - // Decide whether the contact is hidden(demoted), pinned, or unpinned - if (contact.pinned > PIN_LIMIT || contact.pinned == PinnedPositions.UNPINNED) { - unpinnedContacts.add(contact); - } else if (contact.pinned > PinnedPositions.DEMOTED) { - // Demoted or contacts with negative pinned positions are ignored. - // Pinned contacts go into a priority queue where they are ranked by pinned - // position. This is required because the contacts provider does not return - // contacts ordered by pinned position. - pinnedQueue.add(contact); - } - } - - final int maxToPin = Math.min(PIN_LIMIT, pinnedQueue.size() + unpinnedContacts.size()); - - toArrange.clear(); - for (int i = 1; i < maxToPin + 1; i++) { - if (!pinnedQueue.isEmpty() && pinnedQueue.peek().pinned <= i) { - final ContactEntry toPin = pinnedQueue.poll(); - toPin.pinned = i; - toArrange.add(toPin); - } else if (!unpinnedContacts.isEmpty()) { - toArrange.add(unpinnedContacts.remove(0)); - } - } - - // If there are still contacts in pinnedContacts at this point, it means that the pinned - // positions of these pinned contacts exceed the actual number of contacts in the list. - // For example, the user had 10 frequents, starred and pinned one of them at the last spot, - // and then cleared frequents. Contacts in this situation should become unpinned. - while (!pinnedQueue.isEmpty()) { - final ContactEntry entry = pinnedQueue.poll(); - entry.pinned = PinnedPositions.UNPINNED; - toArrange.add(entry); - } - - // Any remaining unpinned contacts that weren't in the gaps between the pinned contacts - // now just get appended to the end of the list. - toArrange.addAll(unpinnedContacts); - } - - /** - * Given an existing list of contact entries and a single entry that is to be pinned at a - * particular position, return a list of {@link ContentProviderOperation}s that contains new - * pinned positions for all contacts that are forced to be pinned at new positions, trying as - * much as possible to keep pinned contacts at their original location. - * - * At this point in time the pinned position of each contact in the list has already been - * updated by {@link #arrangeContactsByPinnedPosition}, so we can assume that all pinned - * positions(within {@link #PIN_LIMIT} are unique positive integers. - */ - @VisibleForTesting - /* package */ ArrayList<ContentProviderOperation> getReflowedPinningOperations( - ArrayList<ContactEntry> list, int oldPos, int newPinPos) { - final ArrayList<ContentProviderOperation> positions = Lists.newArrayList(); - final int lowerBound = Math.min(oldPos, newPinPos); - final int upperBound = Math.max(oldPos, newPinPos); - for (int i = lowerBound; i <= upperBound; i++) { - final ContactEntry entry = list.get(i); - - // Pinned positions in the database start from 1 instead of being zero-indexed like - // arrays, so offset by 1. - final int databasePinnedPosition = i + 1; - if (entry.pinned == databasePinnedPosition) continue; - - final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(entry.id)); - final ContentValues values = new ContentValues(); - values.put(Contacts.PINNED, databasePinnedPosition); - positions.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); - } - return positions; - } - - protected static class ViewTypes { - public static final int TILE = 0; - public static final int COUNT = 1; - } - - @Override - public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { - setInDragging(true); - final int itemIndex = mContactEntries.indexOf(view.getContactEntry()); - popContactEntry(itemIndex); - } - - @Override - public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { - if (view == null) { - // The user is hovering over a view that is not a contact tile, no need to do - // anything here. - return; - } - final int itemIndex = mContactEntries.indexOf(view.getContactEntry()); - if (mInDragging && - mDragEnteredEntryIndex != itemIndex && - isIndexInBound(itemIndex) && - itemIndex < PIN_LIMIT && - itemIndex >= 0) { - markDropArea(itemIndex); - } - } - - @Override - public void onDragFinished(int x, int y) { - setInDragging(false); - // A contact has been dragged to the RemoveView in order to be unstarred, so simply wait - // for the new contact cursor which will cause the UI to be refreshed without the unstarred - // contact. - if (!mAwaitingRemove) { - handleDrop(); - } - } - - @Override - public void onDroppedOnRemove() { - if (mDraggedEntry != null) { - unstarAndUnpinContact(mDraggedEntry.lookupUri); - mAwaitingRemove = true; - } - } -} diff --git a/src/com/android/dialer/list/RegularSearchFragment.java b/src/com/android/dialer/list/RegularSearchFragment.java deleted file mode 100644 index df18af044..000000000 --- a/src/com/android/dialer/list/RegularSearchFragment.java +++ /dev/null @@ -1,151 +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.list; - -import static android.Manifest.permission.READ_CONTACTS; - -import android.app.Activity; -import android.content.pm.PackageManager; -import android.support.v13.app.FragmentCompat; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.list.PinnedHeaderListView; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.commonbind.analytics.AnalyticsUtil; -import com.android.dialerbind.ObjectFactory; -import com.android.incallui.Call.LogState; - -import com.android.dialer.R; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.service.CachedNumberLookupService; -import com.android.dialer.widget.EmptyContentView; -import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; - -public class RegularSearchFragment extends SearchFragment - implements OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - - public static final int PERMISSION_REQUEST_CODE = 1; - - private static final int SEARCH_DIRECTORY_RESULT_LIMIT = 5; - - private static final CachedNumberLookupService mCachedNumberLookupService = - ObjectFactory.newCachedNumberLookupService(); - - public interface CapabilityChecker { - public boolean isNearbyPlacesSearchEnabled(); - } - - protected String mPermissionToRequest; - - public RegularSearchFragment() { - configureDirectorySearch(); - } - - public void configureDirectorySearch() { - setDirectorySearchEnabled(true); - setDirectoryResultLimit(SEARCH_DIRECTORY_RESULT_LIMIT); - } - - @Override - protected void onCreateView(LayoutInflater inflater, ViewGroup container) { - super.onCreateView(inflater, container); - ((PinnedHeaderListView) getListView()).setScrollToSectionOnHeaderTouch(true); - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - RegularSearchListAdapter adapter = new RegularSearchListAdapter(getActivity()); - adapter.setDisplayPhotos(true); - adapter.setUseCallableUri(usesCallableUri()); - adapter.setListener(this); - return adapter; - } - - @Override - protected void cacheContactInfo(int position) { - if (mCachedNumberLookupService != null) { - final RegularSearchListAdapter adapter = - (RegularSearchListAdapter) getAdapter(); - mCachedNumberLookupService.addContact(getContext(), - adapter.getContactInfo(mCachedNumberLookupService, position)); - } - } - - @Override - protected void setupEmptyView() { - if (mEmptyView != null && getActivity() != null) { - final int imageResource; - final int actionLabelResource; - final int descriptionResource; - final OnEmptyViewActionButtonClickedListener listener; - if (!PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { - imageResource = R.drawable.empty_contacts; - actionLabelResource = R.string.permission_single_turn_on; - descriptionResource = R.string.permission_no_search; - listener = this; - mPermissionToRequest = READ_CONTACTS; - } else { - imageResource = EmptyContentView.NO_IMAGE; - actionLabelResource = EmptyContentView.NO_LABEL; - descriptionResource = EmptyContentView.NO_LABEL; - listener = null; - mPermissionToRequest = null; - } - - mEmptyView.setImage(imageResource); - mEmptyView.setActionLabel(actionLabelResource); - mEmptyView.setDescription(descriptionResource); - if (listener != null) { - mEmptyView.setActionClickedListener(listener); - } - } - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - if (READ_CONTACTS.equals(mPermissionToRequest)) { - FragmentCompat.requestPermissions(this, new String[] {mPermissionToRequest}, - PERMISSION_REQUEST_CODE); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == PERMISSION_REQUEST_CODE) { - setupEmptyView(); - if (grantResults != null && grantResults.length == 1 - && PackageManager.PERMISSION_GRANTED == grantResults[0]) { - PermissionsUtil.notifyPermissionGranted(getActivity(), mPermissionToRequest); - } - } - } - - @Override - protected int getCallInitiationType(boolean isRemoteDirectory) { - return isRemoteDirectory ? LogState.INITIATION_REMOTE_DIRECTORY - : LogState.INITIATION_REGULAR_SEARCH; - } -} diff --git a/src/com/android/dialer/list/RegularSearchListAdapter.java b/src/com/android/dialer/list/RegularSearchListAdapter.java deleted file mode 100644 index afc621cf5..000000000 --- a/src/com/android/dialer/list/RegularSearchListAdapter.java +++ /dev/null @@ -1,130 +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.list; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.text.TextUtils; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.compat.DirectoryCompat; -import com.android.contacts.common.list.DirectoryPartition; -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.dialer.calllog.ContactInfo; -import com.android.dialer.service.CachedNumberLookupService; -import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo; - -/** - * List adapter to display regular search results. - */ -public class RegularSearchListAdapter extends DialerPhoneNumberListAdapter { - protected boolean mIsQuerySipAddress; - - public RegularSearchListAdapter(Context context) { - super(context); - setShortcutEnabled(SHORTCUT_CREATE_NEW_CONTACT, false); - setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, false); - } - - public CachedContactInfo getContactInfo( - CachedNumberLookupService lookupService, int position) { - ContactInfo info = new ContactInfo(); - CachedContactInfo cacheInfo = lookupService.buildCachedContactInfo(info); - final Cursor item = (Cursor) getItem(position); - if (item != null) { - final DirectoryPartition partition = - (DirectoryPartition) getPartition(getPartitionForPosition(position)); - final long directoryId = partition.getDirectoryId(); - final boolean isExtendedDirectory = isExtendedDirectory(directoryId); - - info.name = item.getString(PhoneQuery.DISPLAY_NAME); - info.type = item.getInt(PhoneQuery.PHONE_TYPE); - info.label = item.getString(PhoneQuery.PHONE_LABEL); - info.number = item.getString(PhoneQuery.PHONE_NUMBER); - final String photoUriStr = item.getString(PhoneQuery.PHOTO_URI); - info.photoUri = photoUriStr == null ? null : Uri.parse(photoUriStr); - /* - * An extended directory is custom directory in the app, but not a directory provided by - * framework. So it can't be USER_TYPE_WORK. - * - * When a search result is selected, RegularSearchFragment calls getContactInfo and - * cache the resulting @{link ContactInfo} into local db. Set usertype to USER_TYPE_WORK - * only if it's NOT extended directory id and is enterprise directory. - */ - info.userType = !isExtendedDirectory - && DirectoryCompat.isEnterpriseDirectoryId(directoryId) - ? ContactsUtils.USER_TYPE_WORK : ContactsUtils.USER_TYPE_CURRENT; - - cacheInfo.setLookupKey(item.getString(PhoneQuery.LOOKUP_KEY)); - - final String sourceName = partition.getLabel(); - if (isExtendedDirectory) { - cacheInfo.setExtendedSource(sourceName, directoryId); - } else { - cacheInfo.setDirectorySource(sourceName, directoryId); - } - } - return cacheInfo; - } - - @Override - public String getFormattedQueryString() { - if (mIsQuerySipAddress) { - // Return unnormalized SIP address - return getQueryString(); - } - return super.getFormattedQueryString(); - } - - @Override - public void setQueryString(String queryString) { - // Don't show actions if the query string contains a letter. - final boolean showNumberShortcuts = !TextUtils.isEmpty(getFormattedQueryString()) - && hasDigitsInQueryString(); - mIsQuerySipAddress = PhoneNumberHelper.isUriNumber(queryString); - - if (isChanged(showNumberShortcuts)) { - notifyDataSetChanged(); - } - super.setQueryString(queryString); - } - - protected boolean isChanged(boolean showNumberShortcuts) { - boolean changed = false; - changed |= setShortcutEnabled(SHORTCUT_DIRECT_CALL, - showNumberShortcuts || mIsQuerySipAddress); - changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts); - changed |= setShortcutEnabled(SHORTCUT_MAKE_VIDEO_CALL, - showNumberShortcuts && CallUtil.isVideoEnabled(getContext())); - return changed; - } - - /** - * Whether there is at least one digit in the query string. - */ - private boolean hasDigitsInQueryString() { - String queryString = getQueryString(); - int length = queryString.length(); - for (int i = 0; i < length; i++) { - if (Character.isDigit(queryString.charAt(i))) { - return true; - } - } - return false; - } -} diff --git a/src/com/android/dialer/list/RemoveView.java b/src/com/android/dialer/list/RemoveView.java deleted file mode 100644 index 41f41752e..000000000 --- a/src/com/android/dialer/list/RemoveView.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.android.dialer.list; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.DragEvent; -import android.view.accessibility.AccessibilityEvent; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.dialer.R; - -public class RemoveView extends FrameLayout { - - DragDropController mDragDropController; - TextView mRemoveText; - ImageView mRemoveIcon; - int mUnhighlightedColor; - int mHighlightedColor; - Drawable mRemoveDrawable; - - public RemoveView(Context context) { - super(context); - } - - public RemoveView(Context context, AttributeSet attrs) { - this(context, attrs, -1); - } - - public RemoveView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - mRemoveText = (TextView) findViewById(R.id.remove_view_text); - mRemoveIcon = (ImageView) findViewById(R.id.remove_view_icon); - final Resources r = getResources(); - mUnhighlightedColor = r.getColor(R.color.remove_text_color); - mHighlightedColor = r.getColor(R.color.remove_highlighted_text_color); - mRemoveDrawable = r.getDrawable(R.drawable.ic_remove); - } - - public void setDragDropController(DragDropController controller) { - mDragDropController = controller; - } - - @Override - public boolean onDragEvent(DragEvent event) { - final int action = event.getAction(); - switch (action) { - case DragEvent.ACTION_DRAG_ENTERED: - // TODO: This is temporary solution and should be removed once accessibility for - // drag and drop is supported by framework(b/26871588). - sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT); - setAppearanceHighlighted(); - break; - case DragEvent.ACTION_DRAG_EXITED: - setAppearanceNormal(); - break; - case DragEvent.ACTION_DRAG_LOCATION: - if (mDragDropController != null) { - mDragDropController.handleDragHovered(this, (int) event.getX(), - (int) event.getY()); - } - break; - case DragEvent.ACTION_DROP: - sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT); - if (mDragDropController != null) { - mDragDropController.handleDragFinished((int) event.getX(), (int) event.getY(), - true); - } - setAppearanceNormal(); - break; - } - return true; - } - - private void setAppearanceNormal() { - mRemoveText.setTextColor(mUnhighlightedColor); - mRemoveIcon.setColorFilter(mUnhighlightedColor); - invalidate(); - } - - private void setAppearanceHighlighted() { - mRemoveText.setTextColor(mHighlightedColor); - mRemoveIcon.setColorFilter(mHighlightedColor); - invalidate(); - } -} diff --git a/src/com/android/dialer/list/SearchFragment.java b/src/com/android/dialer/list/SearchFragment.java deleted file mode 100644 index 82395b6f8..000000000 --- a/src/com/android/dialer/list/SearchFragment.java +++ /dev/null @@ -1,399 +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.list; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.AnimatorListenerAdapter; -import android.app.Activity; -import android.app.DialogFragment; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Interpolator; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.Space; - -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.list.ContactListItemView; -import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -import com.android.contacts.common.list.PhoneNumberPickerFragment; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.contacts.common.util.ViewUtil; -import com.android.dialer.R; -import com.android.dialer.dialpad.DialpadFragment.ErrorDialogFragment; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.widget.EmptyContentView; -import com.android.phone.common.animation.AnimUtils; - -public class SearchFragment extends PhoneNumberPickerFragment { - private static final String TAG = SearchFragment.class.getSimpleName(); - - private OnListFragmentScrolledListener mActivityScrollListener; - private View.OnTouchListener mActivityOnTouchListener; - - /* - * Stores the untouched user-entered string that is used to populate the add to contacts - * intent. - */ - private String mAddToContactNumber; - private int mActionBarHeight; - private int mShadowHeight; - private int mPaddingTop; - private int mShowDialpadDuration; - private int mHideDialpadDuration; - - /** - * Used to resize the list view containing search results so that it fits the available space - * above the dialpad. Does not have a user-visible effect in regular touch usage (since the - * dialpad hides that portion of the ListView anyway), but improves usability in accessibility - * mode. - */ - private Space mSpacer; - - private HostInterface mActivity; - - protected EmptyContentView mEmptyView; - - public interface HostInterface { - public boolean isActionBarShowing(); - public boolean isDialpadShown(); - public int getDialpadHeight(); - public int getActionBarHideOffset(); - public int getActionBarHeight(); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - setQuickContactEnabled(true); - setAdjustSelectionBoundsEnabled(false); - setDarkTheme(false); - setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(false /* opposite */)); - setUseCallableUri(true); - - try { - mActivityScrollListener = (OnListFragmentScrolledListener) activity; - } catch (ClassCastException e) { - Log.d(TAG, activity.toString() + " doesn't implement OnListFragmentScrolledListener. " + - "Ignoring."); - } - } - - @Override - public void onStart() { - super.onStart(); - if (isSearchMode()) { - getAdapter().setHasHeader(0, false); - } - - mActivity = (HostInterface) getActivity(); - - final Resources res = getResources(); - mActionBarHeight = mActivity.getActionBarHeight(); - mShadowHeight = res.getDrawable(R.drawable.search_shadow).getIntrinsicHeight(); - mPaddingTop = res.getDimensionPixelSize(R.dimen.search_list_padding_top); - mShowDialpadDuration = res.getInteger(R.integer.dialpad_slide_in_duration); - mHideDialpadDuration = res.getInteger(R.integer.dialpad_slide_out_duration); - - final View parentView = getView(); - - final ListView listView = getListView(); - - if (mEmptyView == null) { - mEmptyView = new EmptyContentView(getActivity()); - ((ViewGroup) getListView().getParent()).addView(mEmptyView); - getListView().setEmptyView(mEmptyView); - setupEmptyView(); - } - - listView.setBackgroundColor(res.getColor(R.color.background_dialer_results)); - listView.setClipToPadding(false); - setVisibleScrollbarEnabled(false); - - //Turn of accessibility live region as the list constantly update itself and spam messages. - listView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); - ContentChangedFilter.addToParent(listView); - - listView.setOnScrollListener(new OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (mActivityScrollListener != null) { - mActivityScrollListener.onListFragmentScrollStateChange(scrollState); - } - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - } - }); - if (mActivityOnTouchListener != null) { - listView.setOnTouchListener(mActivityOnTouchListener); - } - - updatePosition(false /* animate */); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - ViewUtil.addBottomPaddingToListViewForFab(getListView(), getResources()); - } - - @Override - public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { - Animator animator = null; - if (nextAnim != 0) { - animator = AnimatorInflater.loadAnimator(getActivity(), nextAnim); - } - if (animator != null) { - final View view = getView(); - final int oldLayerType = view.getLayerType(); - view.setLayerType(View.LAYER_TYPE_HARDWARE, null); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setLayerType(oldLayerType, null); - } - }); - } - return animator; - } - - @Override - protected void setSearchMode(boolean flag) { - super.setSearchMode(flag); - // This hides the "All contacts with phone numbers" header in the search fragment - final ContactEntryListAdapter adapter = getAdapter(); - if (adapter != null) { - adapter.setHasHeader(0, false); - } - } - - public void setAddToContactNumber(String addToContactNumber) { - mAddToContactNumber = addToContactNumber; - } - - /** - * Return true if phone number is prohibited by a value - - * (R.string.config_prohibited_phone_number_regexp) in the config files. False otherwise. - */ - public boolean checkForProhibitedPhoneNumber(String number) { - // Regular expression prohibiting manual phone call. Can be empty i.e. "no rule". - String prohibitedPhoneNumberRegexp = getResources().getString( - R.string.config_prohibited_phone_number_regexp); - - // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated - // test equipment. - if (number != null - && !TextUtils.isEmpty(prohibitedPhoneNumberRegexp) - && number.matches(prohibitedPhoneNumberRegexp)) { - Log.d(TAG, "The phone number is prohibited explicitly by a rule."); - if (getActivity() != null) { - DialogFragment dialogFragment = ErrorDialogFragment.newInstance( - R.string.dialog_phone_call_prohibited_message); - dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog"); - } - - return true; - } - return false; - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - DialerPhoneNumberListAdapter adapter = new DialerPhoneNumberListAdapter(getActivity()); - adapter.setDisplayPhotos(true); - adapter.setUseCallableUri(super.usesCallableUri()); - adapter.setListener(this); - return adapter; - } - - @Override - protected void onItemClick(int position, long id) { - final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter(); - final int shortcutType = adapter.getShortcutTypeFromPosition(position); - final OnPhoneNumberPickerActionListener listener; - final Intent intent; - final String number; - - Log.i(TAG, "onItemClick: shortcutType=" + shortcutType); - - switch (shortcutType) { - case DialerPhoneNumberListAdapter.SHORTCUT_INVALID: - super.onItemClick(position, id); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_DIRECT_CALL: - number = adapter.getQueryString(); - listener = getOnPhoneNumberPickerListener(); - if (listener != null && !checkForProhibitedPhoneNumber(number)) { - listener.onPickPhoneNumber(number, false /* isVideoCall */, - getCallInitiationType(false /* isRemoteDirectory */)); - } - break; - case DialerPhoneNumberListAdapter.SHORTCUT_CREATE_NEW_CONTACT: - number = TextUtils.isEmpty(mAddToContactNumber) ? - adapter.getFormattedQueryString() : mAddToContactNumber; - intent = IntentUtil.getNewContactIntent(number); - DialerUtils.startActivityWithErrorToast(getActivity(), intent); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_ADD_TO_EXISTING_CONTACT: - number = TextUtils.isEmpty(mAddToContactNumber) ? - adapter.getFormattedQueryString() : mAddToContactNumber; - intent = IntentUtil.getAddToExistingContactIntent(number); - DialerUtils.startActivityWithErrorToast(getActivity(), intent, - R.string.add_contact_not_available); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_SEND_SMS_MESSAGE: - number = adapter.getFormattedQueryString(); - intent = IntentUtil.getSendSmsIntent(number); - DialerUtils.startActivityWithErrorToast(getActivity(), intent); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_MAKE_VIDEO_CALL: - number = TextUtils.isEmpty(mAddToContactNumber) ? - adapter.getQueryString() : mAddToContactNumber; - listener = getOnPhoneNumberPickerListener(); - if (listener != null && !checkForProhibitedPhoneNumber(number)) { - listener.onPickPhoneNumber(number, true /* isVideoCall */, - getCallInitiationType(false /* isRemoteDirectory */)); - } - break; - } - } - - /** - * Updates the position and padding of the search fragment, depending on whether the dialpad is - * shown. This can be optionally animated. - * @param animate - */ - public void updatePosition(boolean animate) { - // Use negative shadow height instead of 0 to account for the 9-patch's shadow. - int startTranslationValue = - mActivity.isDialpadShown() ? mActionBarHeight - mShadowHeight : -mShadowHeight; - int endTranslationValue = 0; - // Prevents ListView from being translated down after a rotation when the ActionBar is up. - if (animate || mActivity.isActionBarShowing()) { - endTranslationValue = - mActivity.isDialpadShown() ? 0 : mActionBarHeight - mShadowHeight; - } - if (animate) { - // If the dialpad will be shown, then this animation involves sliding the list up. - final boolean slideUp = mActivity.isDialpadShown(); - - Interpolator interpolator = slideUp ? AnimUtils.EASE_IN : AnimUtils.EASE_OUT ; - int duration = slideUp ? mShowDialpadDuration : mHideDialpadDuration; - getView().setTranslationY(startTranslationValue); - getView().animate() - .translationY(endTranslationValue) - .setInterpolator(interpolator) - .setDuration(duration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (!slideUp) { - resizeListView(); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - if (slideUp) { - resizeListView(); - } - } - }); - - } else { - getView().setTranslationY(endTranslationValue); - resizeListView(); - } - - // There is padding which should only be applied when the dialpad is not shown. - int paddingTop = mActivity.isDialpadShown() ? 0 : mPaddingTop; - final ListView listView = getListView(); - listView.setPaddingRelative( - listView.getPaddingStart(), - paddingTop, - listView.getPaddingEnd(), - listView.getPaddingBottom()); - } - - public void resizeListView() { - if (mSpacer == null) { - return; - } - int spacerHeight = mActivity.isDialpadShown() ? mActivity.getDialpadHeight() : 0; - if (spacerHeight != mSpacer.getHeight()) { - final LinearLayout.LayoutParams lp = - (LinearLayout.LayoutParams) mSpacer.getLayoutParams(); - lp.height = spacerHeight; - mSpacer.setLayoutParams(lp); - } - } - - @Override - protected void startLoading() { - if (getActivity() == null) { - return; - } - - if (PermissionsUtil.hasContactsPermissions(getActivity())) { - super.startLoading(); - } else if (TextUtils.isEmpty(getQueryString())) { - // Clear out any existing call shortcuts. - final DialerPhoneNumberListAdapter adapter = - (DialerPhoneNumberListAdapter) getAdapter(); - adapter.disableAllShortcuts(); - } else { - // The contact list is not going to change (we have no results since permissions are - // denied), but the shortcuts might because of the different query, so update the - // list. - getAdapter().notifyDataSetChanged(); - } - - setupEmptyView(); - } - - public void setOnTouchListener(View.OnTouchListener onTouchListener) { - mActivityOnTouchListener = onTouchListener; - } - - @Override - protected View inflateView(LayoutInflater inflater, ViewGroup container) { - final LinearLayout parent = (LinearLayout) super.inflateView(inflater, container); - final int orientation = getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - mSpacer = new Space(getActivity()); - parent.addView(mSpacer, - new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0)); - } - return parent; - } - - protected void setupEmptyView() {} -} diff --git a/src/com/android/dialer/list/SmartDialNumberListAdapter.java b/src/com/android/dialer/list/SmartDialNumberListAdapter.java deleted file mode 100644 index fe27a25ab..000000000 --- a/src/com/android/dialer/list/SmartDialNumberListAdapter.java +++ /dev/null @@ -1,130 +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.list; - -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.CallUtil; -import com.android.contacts.common.list.ContactListItemView; -import com.android.dialer.dialpad.SmartDialCursorLoader; -import com.android.dialer.dialpad.SmartDialNameMatcher; -import com.android.dialer.dialpad.SmartDialPrefix; -import com.android.dialer.dialpad.SmartDialMatchPosition; - -import java.util.ArrayList; - -/** - * List adapter to display the SmartDial search results. - */ -public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter { - - private static final String TAG = SmartDialNumberListAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; - - private SmartDialNameMatcher mNameMatcher; - - public SmartDialNumberListAdapter(Context context) { - super(context); - mNameMatcher = new SmartDialNameMatcher("", SmartDialPrefix.getMap()); - setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_DIRECT_CALL, false); - - if (DEBUG) { - Log.v(TAG, "Constructing List Adapter"); - } - } - - /** - * Sets query for the SmartDialCursorLoader. - */ - public void configureLoader(SmartDialCursorLoader loader) { - if (DEBUG) { - Log.v(TAG, "Configure Loader with query" + getQueryString()); - } - - if (getQueryString() == null) { - loader.configureQuery(""); - mNameMatcher.setQuery(""); - } else { - loader.configureQuery(getQueryString()); - mNameMatcher.setQuery(PhoneNumberUtils.normalizeNumber(getQueryString())); - } - } - - /** - * Sets highlight options for a List item in the SmartDial search results. - * @param view ContactListItemView where the result will be displayed. - * @param cursor Object containing information of the associated List item. - */ - @Override - protected void setHighlight(ContactListItemView view, Cursor cursor) { - view.clearHighlightSequences(); - - if (mNameMatcher.matches(cursor.getString(PhoneQuery.DISPLAY_NAME))) { - final ArrayList<SmartDialMatchPosition> nameMatches = mNameMatcher.getMatchPositions(); - for (SmartDialMatchPosition match:nameMatches) { - view.addNameHighlightSequence(match.start, match.end); - if (DEBUG) { - Log.v(TAG, cursor.getString(PhoneQuery.DISPLAY_NAME) + " " + - mNameMatcher.getQuery() + " " + String.valueOf(match.start)); - } - } - } - - final SmartDialMatchPosition numberMatch = mNameMatcher.matchesNumber(cursor.getString( - PhoneQuery.PHONE_NUMBER)); - if (numberMatch != null) { - view.addNumberHighlightSequence(numberMatch.start, numberMatch.end); - } - } - - /** - * Gets Uri for the list item at the given position. - * @param position Location of the data of interest. - * @return Data Uri of the entry. - */ - public Uri getDataUri(int position) { - Cursor cursor = ((Cursor)getItem(position)); - if (cursor != null) { - long id = cursor.getLong(PhoneQuery.PHONE_ID); - return ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, id); - } else { - Log.w(TAG, "Cursor was null in getDataUri() call. Returning null instead."); - return null; - } - } - - @Override - public void setQueryString(String queryString) { - final boolean showNumberShortcuts = !TextUtils.isEmpty(getFormattedQueryString()); - boolean changed = false; - changed |= setShortcutEnabled(SHORTCUT_CREATE_NEW_CONTACT, showNumberShortcuts); - changed |= setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, showNumberShortcuts); - changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts); - changed |= setShortcutEnabled(SHORTCUT_MAKE_VIDEO_CALL, - showNumberShortcuts && CallUtil.isVideoEnabled(getContext())); - if (changed) { - notifyDataSetChanged(); - } - super.setQueryString(queryString); - } -} diff --git a/src/com/android/dialer/list/SmartDialSearchFragment.java b/src/com/android/dialer/list/SmartDialSearchFragment.java deleted file mode 100644 index fcb61ffe0..000000000 --- a/src/com/android/dialer/list/SmartDialSearchFragment.java +++ /dev/null @@ -1,134 +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.list; - -import static android.Manifest.permission.CALL_PHONE; - -import android.app.Activity; -import android.content.Loader; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v13.app.FragmentCompat; -import android.util.Log; -import android.view.View; - -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.dialpad.SmartDialCursorLoader; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.ScreenEvent; -import com.android.dialer.R; -import com.android.dialer.widget.EmptyContentView; -import com.android.incallui.Call.LogState; - -import java.util.ArrayList; - -/** - * Implements a fragment to load and display SmartDial search results. - */ -public class SmartDialSearchFragment extends SearchFragment - implements EmptyContentView.OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - private static final String TAG = SmartDialSearchFragment.class.getSimpleName(); - - private static final int CALL_PHONE_PERMISSION_REQUEST_CODE = 1; - - /** - * Creates a SmartDialListAdapter to display and operate on search results. - */ - @Override - protected ContactEntryListAdapter createListAdapter() { - SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity()); - adapter.setUseCallableUri(super.usesCallableUri()); - adapter.setQuickContactEnabled(true); - // Set adapter's query string to restore previous instance state. - adapter.setQueryString(getQueryString()); - adapter.setListener(this); - return adapter; - } - - /** - * Creates a SmartDialCursorLoader object to load query results. - */ - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - // Smart dialing does not support Directory Load, falls back to normal search instead. - if (id == getDirectoryLoaderId()) { - return super.onCreateLoader(id, args); - } else { - final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); - SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext()); - adapter.configureLoader(loader); - return loader; - } - } - - /** - * Gets the Phone Uri of an entry for calling. - * @param position Location of the data of interest. - * @return Phone Uri to establish a phone call. - */ - @Override - protected Uri getPhoneUri(int position) { - final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); - return adapter.getDataUri(position); - } - - @Override - protected void setupEmptyView() { - if (mEmptyView != null && getActivity() != null) { - if (!PermissionsUtil.hasPermission(getActivity(), CALL_PHONE)) { - mEmptyView.setImage(R.drawable.empty_contacts); - mEmptyView.setActionLabel(R.string.permission_single_turn_on); - mEmptyView.setDescription(R.string.permission_place_call); - mEmptyView.setActionClickedListener(this); - } else { - mEmptyView.setImage(EmptyContentView.NO_IMAGE); - mEmptyView.setActionLabel(EmptyContentView.NO_LABEL); - mEmptyView.setDescription(EmptyContentView.NO_LABEL); - } - } - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - FragmentCompat.requestPermissions(this, new String[] {CALL_PHONE}, - CALL_PHONE_PERMISSION_REQUEST_CODE); - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == CALL_PHONE_PERMISSION_REQUEST_CODE) { - setupEmptyView(); - } - } - - @Override - protected int getCallInitiationType(boolean isRemoteDirectory) { - return LogState.INITIATION_SMART_DIAL; - } - - public boolean isShowingPermissionRequest() { - return mEmptyView != null && mEmptyView.isShowingContent(); - } -} diff --git a/src/com/android/dialer/list/SpeedDialFragment.java b/src/com/android/dialer/list/SpeedDialFragment.java deleted file mode 100644 index 7e10297d0..000000000 --- a/src/com/android/dialer/list/SpeedDialFragment.java +++ /dev/null @@ -1,504 +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.list; - -import static android.Manifest.permission.READ_CONTACTS; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.app.Activity; -import android.app.Fragment; -import android.app.LoaderManager; -import android.content.CursorLoader; -import android.content.Loader; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.os.Trace; -import android.support.v13.app.FragmentCompat; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.animation.AnimationUtils; -import android.view.animation.LayoutAnimationController; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; -import android.widget.ImageView; -import android.widget.ListView; - -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.ContactTileLoaderFactory; -import com.android.contacts.common.list.ContactTileView; -import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -import com.android.contacts.common.util.PermissionsUtil; -import com.android.dialer.R; -import com.android.dialer.widget.EmptyContentView; -import com.android.incallui.Call.LogState; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * This fragment displays the user's favorite/frequent contacts in a grid. - */ -public class SpeedDialFragment extends Fragment implements OnItemClickListener, - PhoneFavoritesTileAdapter.OnDataSetChangedForAnimationListener, - EmptyContentView.OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - - private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; - - /** - * By default, the animation code assumes that all items in a list view are of the same height - * when animating new list items into view (e.g. from the bottom of the screen into view). - * This can cause incorrect translation offsets when a item that is larger or smaller than - * other list item is removed from the list. This key is used to provide the actual height - * of the removed object so that the actual translation appears correct to the user. - */ - private static final long KEY_REMOVED_ITEM_HEIGHT = Long.MAX_VALUE; - - private static final String TAG = "SpeedDialFragment"; - private static final boolean DEBUG = false; - - private int mAnimationDuration; - - /** - * Used with LoaderManager. - */ - private static int LOADER_ID_CONTACT_TILE = 1; - - public interface HostInterface { - public void setDragDropController(DragDropController controller); - public void showAllContactsTab(); - } - - private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { - @Override - public CursorLoader onCreateLoader(int id, Bundle args) { - if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader."); - return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity()); - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished"); - mContactTileAdapter.setContactCursor(data); - setEmptyViewVisibility(mContactTileAdapter.getCount() == 0); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. "); - } - } - - private class ContactTileAdapterListener implements ContactTileView.Listener { - @Override - public void onContactSelected(Uri contactUri, Rect targetRect) { - if (mPhoneNumberPickerActionListener != null) { - mPhoneNumberPickerActionListener.onPickDataUri(contactUri, - false /* isVideoCall */, LogState.INITIATION_SPEED_DIAL); - } - } - - @Override - public void onCallNumberDirectly(String phoneNumber) { - if (mPhoneNumberPickerActionListener != null) { - mPhoneNumberPickerActionListener.onPickPhoneNumber(phoneNumber, - false /* isVideoCall */, LogState.INITIATION_SPEED_DIAL); - } - } - - @Override - public int getApproximateTileWidth() { - return getView().getWidth(); - } - } - - private class ScrollListener implements ListView.OnScrollListener { - @Override - public void onScroll(AbsListView view, - int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (mActivityScrollListener != null) { - mActivityScrollListener.onListFragmentScroll(firstVisibleItem, visibleItemCount, - totalItemCount); - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - mActivityScrollListener.onListFragmentScrollStateChange(scrollState); - } - } - - private OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener; - - private OnListFragmentScrolledListener mActivityScrollListener; - private PhoneFavoritesTileAdapter mContactTileAdapter; - - private View mParentView; - - private PhoneFavoriteListView mListView; - - private View mContactTileFrame; - - private final HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>(); - private final HashMap<Long, Integer> mItemIdLeftMap = new HashMap<Long, Integer>(); - - /** - * Layout used when there are no favorites. - */ - private EmptyContentView mEmptyView; - - private final ContactTileView.Listener mContactTileAdapterListener = - new ContactTileAdapterListener(); - private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener = - new ContactTileLoaderListener(); - private final ScrollListener mScrollListener = new ScrollListener(); - - @Override - public void onAttach(Activity activity) { - if (DEBUG) Log.d(TAG, "onAttach()"); - super.onAttach(activity); - - // Construct two base adapters which will become part of PhoneFavoriteMergedAdapter. - // We don't construct the resultant adapter at this moment since it requires LayoutInflater - // that will be available on onCreateView(). - mContactTileAdapter = new PhoneFavoritesTileAdapter(activity, mContactTileAdapterListener, - this); - mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); - } - - @Override - public void onCreate(Bundle savedState) { - if (DEBUG) Log.d(TAG, "onCreate()"); - Trace.beginSection(TAG + " onCreate"); - super.onCreate(savedState); - - mAnimationDuration = getResources().getInteger(R.integer.fade_duration); - Trace.endSection(); - } - - @Override - public void onResume() { - Trace.beginSection(TAG + " onResume"); - super.onResume(); - if (mContactTileAdapter != null) { - mContactTileAdapter.refreshContactsPreferences(); - } - if (PermissionsUtil.hasContactsPermissions(getActivity())) { - if (getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE) == null) { - getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, - mContactTileLoaderListener); - - } else { - getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad(); - } - - mEmptyView.setDescription(R.string.speed_dial_empty); - mEmptyView.setActionLabel(R.string.speed_dial_empty_add_favorite_action); - } else { - mEmptyView.setDescription(R.string.permission_no_speeddial); - mEmptyView.setActionLabel(R.string.permission_single_turn_on); - } - Trace.endSection(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Trace.beginSection(TAG + " onCreateView"); - mParentView = inflater.inflate(R.layout.speed_dial_fragment, container, false); - - mListView = (PhoneFavoriteListView) mParentView.findViewById(R.id.contact_tile_list); - mListView.setOnItemClickListener(this); - mListView.setVerticalScrollBarEnabled(false); - mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT); - mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); - mListView.getDragDropController().addOnDragDropListener(mContactTileAdapter); - - final ImageView dragShadowOverlay = - (ImageView) getActivity().findViewById(R.id.contact_tile_drag_shadow_overlay); - mListView.setDragShadowOverlay(dragShadowOverlay); - - mEmptyView = (EmptyContentView) mParentView.findViewById(R.id.empty_list_view); - mEmptyView.setImage(R.drawable.empty_speed_dial); - mEmptyView.setActionClickedListener(this); - - mContactTileFrame = mParentView.findViewById(R.id.contact_tile_frame); - - final LayoutAnimationController controller = new LayoutAnimationController( - AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in)); - controller.setDelay(0); - mListView.setLayoutAnimation(controller); - mListView.setAdapter(mContactTileAdapter); - - mListView.setOnScrollListener(mScrollListener); - mListView.setFastScrollEnabled(false); - mListView.setFastScrollAlwaysVisible(false); - - //prevent content changes of the list from firing accessibility events. - mListView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); - ContentChangedFilter.addToParent(mListView); - - Trace.endSection(); - return mParentView; - } - - public boolean hasFrequents() { - if (mContactTileAdapter == null) return false; - return mContactTileAdapter.getNumFrequents() > 0; - } - - /* package */ void setEmptyViewVisibility(final boolean visible) { - final int previousVisibility = mEmptyView.getVisibility(); - final int emptyViewVisibility = visible ? View.VISIBLE : View.GONE; - final int listViewVisibility = visible ? View.GONE : View.VISIBLE; - - if (previousVisibility != emptyViewVisibility) { - final FrameLayout.LayoutParams params = (LayoutParams) mContactTileFrame - .getLayoutParams(); - params.height = visible ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; - mContactTileFrame.setLayoutParams(params); - mEmptyView.setVisibility(emptyViewVisibility); - mListView.setVisibility(listViewVisibility); - } - } - - @Override - public void onStart() { - super.onStart(); - - final Activity activity = getActivity(); - - try { - mActivityScrollListener = (OnListFragmentScrolledListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement OnListFragmentScrolledListener"); - } - - try { - OnDragDropListener listener = (OnDragDropListener) activity; - mListView.getDragDropController().addOnDragDropListener(listener); - ((HostInterface) activity).setDragDropController(mListView.getDragDropController()); - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement OnDragDropListener and HostInterface"); - } - - try { - mPhoneNumberPickerActionListener = (OnPhoneNumberPickerActionListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement PhoneFavoritesFragment.listener"); - } - - // Use initLoader() instead of restartLoader() to refraining unnecessary reload. - // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will - // be called, on which we'll check if "all" contacts should be reloaded again or not. - if (PermissionsUtil.hasContactsPermissions(activity)) { - getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener); - } else { - setEmptyViewVisibility(true); - } - } - - /** - * {@inheritDoc} - * - * This is only effective for elements provided by {@link #mContactTileAdapter}. - * {@link #mContactTileAdapter} has its own logic for click events. - */ - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final int contactTileAdapterCount = mContactTileAdapter.getCount(); - if (position <= contactTileAdapterCount) { - Log.e(TAG, "onItemClick() event for unexpected position. " - + "The position " + position + " is before \"all\" section. Ignored."); - } - } - - /** - * Cache the current view offsets into memory. Once a relayout of views in the ListView - * has happened due to a dataset change, the cached offsets are used to create animations - * that slide views from their previous positions to their new ones, to give the appearance - * that the views are sliding into their new positions. - */ - private void saveOffsets(int removedItemHeight) { - final int firstVisiblePosition = mListView.getFirstVisiblePosition(); - if (DEBUG) { - Log.d(TAG, "Child count : " + mListView.getChildCount()); - } - for (int i = 0; i < mListView.getChildCount(); i++) { - final View child = mListView.getChildAt(i); - final int position = firstVisiblePosition + i; - // Since we are getting the position from mListView and then querying - // mContactTileAdapter, its very possible that things are out of sync - // and we might index out of bounds. Let's make sure that this doesn't happen. - if (!mContactTileAdapter.isIndexInBound(position)) { - continue; - } - final long itemId = mContactTileAdapter.getItemId(position); - if (DEBUG) { - Log.d(TAG, "Saving itemId: " + itemId + " for listview child " + i + " Top: " - + child.getTop()); - } - mItemIdTopMap.put(itemId, child.getTop()); - mItemIdLeftMap.put(itemId, child.getLeft()); - } - mItemIdTopMap.put(KEY_REMOVED_ITEM_HEIGHT, removedItemHeight); - } - - /* - * Performs animations for the gridView - */ - private void animateGridView(final long... idsInPlace) { - if (mItemIdTopMap.isEmpty()) { - // Don't do animations if the database is being queried for the first time and - // the previous item offsets have not been cached, or the user hasn't done anything - // (dragging, swiping etc) that requires an animation. - return; - } - - final ViewTreeObserver observer = mListView.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @SuppressWarnings("unchecked") - @Override - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - final int firstVisiblePosition = mListView.getFirstVisiblePosition(); - final AnimatorSet animSet = new AnimatorSet(); - final ArrayList<Animator> animators = new ArrayList<Animator>(); - for (int i = 0; i < mListView.getChildCount(); i++) { - final View child = mListView.getChildAt(i); - int position = firstVisiblePosition + i; - - // Since we are getting the position from mListView and then querying - // mContactTileAdapter, its very possible that things are out of sync - // and we might index out of bounds. Let's make sure that this doesn't happen. - if (!mContactTileAdapter.isIndexInBound(position)) { - continue; - } - - final long itemId = mContactTileAdapter.getItemId(position); - - if (containsId(idsInPlace, itemId)) { - animators.add(ObjectAnimator.ofFloat( - child, "alpha", 0.0f, 1.0f)); - break; - } else { - Integer startTop = mItemIdTopMap.get(itemId); - Integer startLeft = mItemIdLeftMap.get(itemId); - final int top = child.getTop(); - final int left = child.getLeft(); - int deltaX = 0; - int deltaY = 0; - - if (startLeft != null) { - if (startLeft != left) { - deltaX = startLeft - left; - animators.add(ObjectAnimator.ofFloat( - child, "translationX", deltaX, 0.0f)); - } - } - - if (startTop != null) { - if (startTop != top) { - deltaY = startTop - top; - animators.add(ObjectAnimator.ofFloat( - child, "translationY", deltaY, 0.0f)); - } - } - - if (DEBUG) { - Log.d(TAG, "Found itemId: " + itemId + " for listview child " + i + - " Top: " + top + - " Delta: " + deltaY); - } - } - } - - if (animators.size() > 0) { - animSet.setDuration(mAnimationDuration).playTogether(animators); - animSet.start(); - } - - mItemIdTopMap.clear(); - mItemIdLeftMap.clear(); - return true; - } - }); - } - - private boolean containsId(long[] ids, long target) { - // Linear search on array is fine because this is typically only 0-1 elements long - for (int i = 0; i < ids.length; i++) { - if (ids[i] == target) { - return true; - } - } - return false; - } - - @Override - public void onDataSetChangedForAnimation(long... idsInPlace) { - animateGridView(idsInPlace); - } - - @Override - public void cacheOffsetsForDatasetChange() { - saveOffsets(0); - } - - public AbsListView getListView() { - return mListView; - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - if (!PermissionsUtil.hasPermission(activity, READ_CONTACTS)) { - FragmentCompat.requestPermissions(this, new String[] {READ_CONTACTS}, - READ_CONTACTS_PERMISSION_REQUEST_CODE); - } else { - // Switch tabs - ((HostInterface) activity).showAllContactsTab(); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) { - if (grantResults.length == 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) { - PermissionsUtil.notifyPermissionGranted(getActivity(), READ_CONTACTS); - } - } - } -} diff --git a/src/com/android/dialer/logging/InteractionEvent.java b/src/com/android/dialer/logging/InteractionEvent.java deleted file mode 100644 index 88518b47c..000000000 --- a/src/com/android/dialer/logging/InteractionEvent.java +++ /dev/null @@ -1,76 +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.logging; - -/** - * Class holding constants for Dialer interactions - */ -public class InteractionEvent { - - public static final int UNKNOWN = 0; - - /** - * An incoming call was blocked - */ - public static final int CALL_BLOCKED = 15; - - /** - * The user blocked a number from the Call Log screen - */ - public static final int BLOCK_NUMBER_CALL_LOG = 16; - - /** - * The user blocked a number from the Call details screen - */ - public static final int BLOCK_NUMBER_CALL_DETAIL = 17; - - /** - * The user blocked a number from the Management screen - */ - public static final int BLOCK_NUMBER_MANAGEMENT_SCREEN = 18; - - /** - * The user unblocked a number from the Call Log screen - */ - public static final int UNBLOCK_NUMBER_CALL_LOG = 19; - - /** - * The user unblocked a number from the Call details screen - */ - public static final int UNBLOCK_NUMBER_CALL_DETAIL = 20; - - /** - * The user unblocked a number from the Management screen - */ - public static final int UNBLOCK_NUMBER_MANAGEMENT_SCREEN = 21; - - /** - * The user blocked numbers from contacts marked as send to voicemail - */ - public static final int IMPORT_SEND_TO_VOICEMAIL = 22; - - /** - * The user blocked a number then undid the block - */ - public static final int UNDO_BLOCK_NUMBER = 23; - - /** - * The user unblocked a number then undid the unblock - */ - public static final int UNDO_UNBLOCK_NUMBER = 24; - -} diff --git a/src/com/android/dialer/logging/Logger.java b/src/com/android/dialer/logging/Logger.java deleted file mode 100644 index 25b7268ad..000000000 --- a/src/com/android/dialer/logging/Logger.java +++ /dev/null @@ -1,85 +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.logging; - -import android.app.Activity; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.commonbind.analytics.AnalyticsUtil; -import com.android.dialerbind.ObjectFactory; -import com.android.incallui.Call; - -/** - * Single entry point for all logging/analytics-related work for all user interactions. - */ -public abstract class Logger { - public static final String TAG = "Logger"; - - public static Logger getInstance() { - return ObjectFactory.getLoggerInstance(); - } - - /** - * Logs a call event. PII like the call's number or caller details should never be logged. - * - * @param call to log. - */ - public static void logCall(Call call) { - final Logger logger = getInstance(); - if (logger != null) { - logger.logCallImpl(call); - } - } - - /** - * Logs an event indicating that a screen was displayed. - * - * @param screenType integer identifier of the displayed screen - * @param activity Parent activity of the displayed screen. - */ - public static void logScreenView(int screenType, Activity activity) { - final Logger logger = getInstance(); - if (logger != null) { - logger.logScreenViewImpl(screenType); - } - - final String screenName = ScreenEvent.getScreenName(screenType); - if (!TextUtils.isEmpty(screenName)) { - AnalyticsUtil.sendScreenView(screenName, activity, null); - } else { - Log.w(TAG, "Unknown screenType: " + screenType); - } - } - - /** - * Logs an interaction that occurred - * - * @param interaction an integer representing what interaction occurred. - * {@see com.android.dialer.logging.InteractionEvent} - */ - public static void logInteraction(int interaction) { - final Logger logger = getInstance(); - if (logger != null) { - logger.logInteractionImpl(interaction); - } - } - - public abstract void logCallImpl(Call call); - public abstract void logScreenViewImpl(int screenType); - public abstract void logInteractionImpl(int dialerInteraction); -} diff --git a/src/com/android/dialer/logging/ScreenEvent.java b/src/com/android/dialer/logging/ScreenEvent.java deleted file mode 100644 index e0d7b0026..000000000 --- a/src/com/android/dialer/logging/ScreenEvent.java +++ /dev/null @@ -1,172 +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.logging; - -import android.text.TextUtils; - -import com.android.contacts.common.dialog.ClearFrequentsDialog; -import com.android.contacts.common.interactions.ImportExportDialogFragment; -import com.android.dialer.calllog.CallLogFragment; -import com.android.dialer.dialpad.DialpadFragment; -import com.android.dialer.filterednumber.BlockedNumbersFragment; -import com.android.dialer.list.AllContactsFragment; -import com.android.dialer.list.BlockedListSearchFragment; -import com.android.dialer.list.RegularSearchFragment; -import com.android.dialer.list.SmartDialSearchFragment; -import com.android.dialer.list.SpeedDialFragment; -import com.android.dialer.settings.DialerSettingsActivity; -import com.android.incallui.AnswerFragment; -import com.android.incallui.CallCardFragment; -import com.android.incallui.ConferenceManagerFragment; -import com.android.incallui.InCallActivity; - -import java.util.HashMap; -import java.util.Map; - -/** - * Stores constants identifying individual screens/dialogs/fragments in the application, and also - * provides a mapping of integer id -> screen name mappings for analytics purposes. - */ -public class ScreenEvent { - private static final Map<Integer, String> sScreenNameMap = new HashMap<>(); - - public static final String FRAGMENT_TAG_SEPARATOR = "#"; - - public static final int UNKNOWN = 0; - - // The dialpad in the main Dialer activity - public static final int DIALPAD = 1; - - // The speed dial tab in the main Dialer activity - public static final int SPEED_DIAL = 2; - - // The recents tab in the main Dialer activity - public static final int CALL_LOG = 3; - - // The voicemail tab in the main Dialer activity - public static final int VOICEMAIL_LOG = 4; - - // The all contacts tab in the main Dialer activity - public static final int ALL_CONTACTS = 5; - - // List of search results returned by typing into the search box. - public static final int REGULAR_SEARCH = 6; - - // List of search results returned by typing into the dialpad. - public static final int SMART_DIAL_SEARCH = 7; - - // The All and Missed call log tabs in CallLogActivity - public static final int CALL_LOG_FILTER = 8; - - // Dialer settings screen. - public static final int SETTINGS = 9; - - // The "Import/export contacts" dialog launched via the overflow menu. - public static final int IMPORT_EXPORT_CONTACTS = 10; - - // The "Clear frequents" dialog launched via the overflow menu. - public static final int CLEAR_FREQUENTS = 11; - - // The "Send feedback" dialog launched via the overflow menu. - public static final int SEND_FEEDBACK = 12; - - // The main in call screen that displays caller details and contact photos - public static final int INCALL = 13; - - // The screen that displays the glowpad widget (slide right to answer, - // slide left to dismiss). - public static final int INCOMING_CALL = 14; - - // Conference management fragment displayed for conferences that support - // management of individual calls within the conference. - public static final int CONFERENCE_MANAGEMENT = 15; - - // The dialpad displayed in-call that is used to send dtmf tones. - public static final int INCALL_DIALPAD = 16; - - // Menu options displayed when long pressing on a call log entry. - public static final int CALL_LOG_CONTEXT_MENU = 17; - - // Screen displayed to allow the user to see an overview of all blocked - // numbers. - public static final int BLOCKED_NUMBER_MANAGEMENT = 18; - - // Screen displayed to allow the user to add a new blocked number. - public static final int BLOCKED_NUMBER_ADD_NUMBER = 19; - - static { - sScreenNameMap.put(ScreenEvent.DIALPAD, - getScreenNameWithTag(DialpadFragment.class.getSimpleName(), "Dialer")); - sScreenNameMap.put(ScreenEvent.SPEED_DIAL, SpeedDialFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.CALL_LOG, - getScreenNameWithTag(CallLogFragment.class.getSimpleName(), "History")); - sScreenNameMap.put(ScreenEvent.VOICEMAIL_LOG, - getScreenNameWithTag(CallLogFragment.class.getSimpleName(), "Voicemail")); - sScreenNameMap.put(ScreenEvent.ALL_CONTACTS, AllContactsFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.REGULAR_SEARCH, - RegularSearchFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.SMART_DIAL_SEARCH, - SmartDialSearchFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.CALL_LOG_FILTER, - getScreenNameWithTag(CallLogFragment.class.getSimpleName(), "Filtered")); - sScreenNameMap.put(ScreenEvent.SETTINGS, - DialerSettingsActivity.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.IMPORT_EXPORT_CONTACTS, - ImportExportDialogFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.CLEAR_FREQUENTS, - ClearFrequentsDialog.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.SEND_FEEDBACK, "SendFeedback"); - sScreenNameMap.put(ScreenEvent.INCALL, InCallActivity.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.INCOMING_CALL, AnswerFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.CONFERENCE_MANAGEMENT, - ConferenceManagerFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.INCALL_DIALPAD, - getScreenNameWithTag(DialpadFragment.class.getSimpleName(), "InCall")); - sScreenNameMap.put(ScreenEvent.CALL_LOG_CONTEXT_MENU, "CallLogContextMenu"); - sScreenNameMap.put(ScreenEvent.BLOCKED_NUMBER_MANAGEMENT, - BlockedNumbersFragment.class.getSimpleName()); - sScreenNameMap.put(ScreenEvent.BLOCKED_NUMBER_ADD_NUMBER, - BlockedListSearchFragment.class.getSimpleName()); - } - - /** - * For a given screen type, returns the actual screen name that is used for logging/analytics - * purposes. - * - * @param screenType unique ID of a type of screen - * - * @return the tagged version of the screen name corresponding to the provided screenType, - * or {@null} if the provided screenType is unknown. - */ - public static String getScreenName(int screenType) { - return sScreenNameMap.get(screenType); - } - - /** - * Build a tagged version of the provided screenName if the tag is non-empty. - * - * @param screenName Name of the screen. - * @param tag Optional tag describing the screen. - * @return the unchanged screenName if the tag is {@code null} or empty, the tagged version of - * the screenName otherwise. - */ - public static String getScreenNameWithTag(String screenName, String tag) { - if (TextUtils.isEmpty(tag)) { - return screenName; - } - return screenName + FRAGMENT_TAG_SEPARATOR + tag; - } -} diff --git a/src/com/android/dialer/service/CachedNumberLookupService.java b/src/com/android/dialer/service/CachedNumberLookupService.java deleted file mode 100644 index 018ada93f..000000000 --- a/src/com/android/dialer/service/CachedNumberLookupService.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.android.dialer.service; - -import android.content.Context; -import android.net.Uri; -import android.support.annotation.Nullable; - -import com.android.dialer.calllog.ContactInfo; - -import java.io.InputStream; - -public interface CachedNumberLookupService { - - public interface CachedContactInfo { - public static final int SOURCE_TYPE_DIRECTORY = 1; - public static final int SOURCE_TYPE_EXTENDED = 2; - public static final int SOURCE_TYPE_PLACES = 3; - public static final int SOURCE_TYPE_PROFILE = 4; - public static final int SOURCE_TYPE_CNAP = 5; - - public ContactInfo getContactInfo(); - - public void setSource(int sourceType, String name, long directoryId); - public void setDirectorySource(String name, long directoryId); - public void setExtendedSource(String name, long directoryId); - public void setLookupKey(String lookupKey); - } - - public CachedContactInfo buildCachedContactInfo(ContactInfo info); - - /** - * Perform a lookup using the cached number lookup service to return contact - * information stored in the cache that corresponds to the given number. - * - * @param context Valid context - * @param number Phone number to lookup the cache for - * @return A {@link CachedContactInfo} containing the contact information if the phone - * number is found in the cache, {@link ContactInfo#EMPTY} if the phone number was - * not found in the cache, and null if there was an error when querying the cache. - */ - public CachedContactInfo lookupCachedContactFromNumber(Context context, String number); - - public void addContact(Context context, CachedContactInfo info); - - public boolean isCacheUri(String uri); - - public boolean isBusiness(int sourceType); - public boolean canReportAsInvalid(int sourceType, String objectId); - - /** - * @return return {@link Uri} to the photo or return {@code null} when failing to add photo - */ - public @Nullable Uri addPhoto(Context context, String number, InputStream in); - - /** - * Remove all cached phone number entries from the cache, regardless of how old they - * are. - * - * @param context Valid context - */ - public void clearAllCacheEntries(Context context); -} diff --git a/src/com/android/dialer/service/ExtendedCallInfoService.java b/src/com/android/dialer/service/ExtendedCallInfoService.java deleted file mode 100644 index 5481cc90d..000000000 --- a/src/com/android/dialer/service/ExtendedCallInfoService.java +++ /dev/null @@ -1,79 +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.service; - -import android.support.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Interface of service to get extended call information. - */ -public interface ExtendedCallInfoService { - /** - * All the possible locations that a user can report a number as spam or not spam. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({REPORTING_LOCATION_UNKNOWN, REPORTING_LOCATION_CALL_LOG_HISTORY, - REPORTING_LOCATION_FEEDBACK_PROMPT}) - @interface ReportingLocation {} - int REPORTING_LOCATION_UNKNOWN = 0; - int REPORTING_LOCATION_CALL_LOG_HISTORY = 1; - int REPORTING_LOCATION_FEEDBACK_PROMPT = 2; - - /** - * Interface for a callback to be invoked when data is fetched. - */ - interface Listener { - /** - * Called when data is fetched. - * @param isSpam True if the call is spam. - */ - void onComplete(boolean isSpam); - } - - /** - * Gets extended call information. - * @param number The phone number of the call. - * @param countryIso The country ISO of the call. - * @param listener The callback to be invoked after {@code Info} is fetched. - */ - void getExtendedCallInfo(String number, String countryIso, Listener listener); - - /** - * Reports number as spam. - * @param number The number to be reported. - * @param countryIso The country ISO of the number. - * @param callType Whether the type of call is missed, voicemail, etc. Example of this is - * {@link android.provider.CallLog.Calls#VOICEMAIL_TYPE}. - * @param from Where in the dialer this was reported from. - * Must be one of {@link ReportingLocation}. - */ - void reportSpam(String number, String countryIso, int callType, @ReportingLocation int from); - - /** - * Reports number as not spam. - * @param number The number to be reported. - * @param countryIso The country ISO of the number. - * @param callType Whether the type of call is missed, voicemail, etc. Example of this is - * {@link android.provider.CallLog.Calls#VOICEMAIL_TYPE}. - * @param from Where in the dialer this was reported from. - * Must be one of {@link ReportingLocation}. - */ - void reportNotSpam(String number, String countryIso, int callType, @ReportingLocation int from); -} diff --git a/src/com/android/dialer/settings/AppCompatPreferenceActivity.java b/src/com/android/dialer/settings/AppCompatPreferenceActivity.java deleted file mode 100644 index 4e5d9c90e..000000000 --- a/src/com/android/dialer/settings/AppCompatPreferenceActivity.java +++ /dev/null @@ -1,155 +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.settings; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatDelegate; -import android.support.v7.widget.Toolbar; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; - -/** - * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls - * to be used with AppCompat. - */ -public class AppCompatPreferenceActivity extends PreferenceActivity { - private AppCompatDelegate mDelegate; - - private boolean mIsSafeToCommitTransactions; - - @Override - protected void onCreate(Bundle savedInstanceState) { - getDelegate().installViewFactory(); - getDelegate().onCreate(savedInstanceState); - super.onCreate(savedInstanceState); - mIsSafeToCommitTransactions = true; - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - getDelegate().onPostCreate(savedInstanceState); - } - - public ActionBar getSupportActionBar() { - return getDelegate().getSupportActionBar(); - } - - public void setSupportActionBar(Toolbar toolbar) { - getDelegate().setSupportActionBar(toolbar); - } - - @Override - public MenuInflater getMenuInflater() { - return getDelegate().getMenuInflater(); - } - - @Override - public void setContentView(int layoutResID) { - getDelegate().setContentView(layoutResID); - } - - @Override - public void setContentView(View view) { - getDelegate().setContentView(view); - } - - @Override - public void setContentView(View view, ViewGroup.LayoutParams params) { - getDelegate().setContentView(view, params); - } - - @Override - public void addContentView(View view, ViewGroup.LayoutParams params) { - getDelegate().addContentView(view, params); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - getDelegate().onPostResume(); - } - - @Override - protected void onTitleChanged(CharSequence title, int color) { - super.onTitleChanged(title, color); - getDelegate().setTitle(title); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - getDelegate().onConfigurationChanged(newConfig); - } - - @Override - protected void onStop() { - super.onStop(); - getDelegate().onStop(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - getDelegate().onDestroy(); - } - - @Override - public void invalidateOptionsMenu() { - getDelegate().invalidateOptionsMenu(); - } - - private AppCompatDelegate getDelegate() { - if (mDelegate == null) { - mDelegate = AppCompatDelegate.create(this, null); - } - return mDelegate; - } - - @Override - protected void onStart() { - super.onStart(); - mIsSafeToCommitTransactions = true; - } - - @Override - protected void onResume() { - super.onResume(); - mIsSafeToCommitTransactions = true; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mIsSafeToCommitTransactions = false; - } - - /** - * Returns true if it is safe to commit {@link FragmentTransaction}s at this time, based on - * whether {@link Activity#onSaveInstanceState} has been called or not. - * - * Make sure that the current activity calls into - * {@link super.onSaveInstanceState(Bundle outState)} (if that method is overridden), - * so the flag is properly set. - */ - public boolean isSafeToCommitTransactions() { - return mIsSafeToCommitTransactions; - } -} diff --git a/src/com/android/dialer/settings/DefaultRingtonePreference.java b/src/com/android/dialer/settings/DefaultRingtonePreference.java deleted file mode 100644 index a8a23fddf..000000000 --- a/src/com/android/dialer/settings/DefaultRingtonePreference.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2014 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.settings; - -import android.content.Context; -import android.content.Intent; -import android.media.RingtoneManager; -import android.net.Uri; -import android.preference.RingtonePreference; -import android.util.AttributeSet; -import android.widget.Toast; - -import com.android.dialer.R; -import com.android.dialer.compat.SettingsCompat; - -/** - * RingtonePreference which doesn't show default ringtone setting. - */ -public class DefaultRingtonePreference extends RingtonePreference { - public DefaultRingtonePreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) { - super.onPrepareRingtonePickerIntent(ringtonePickerIntent); - - /* - * Since this preference is for choosing the default ringtone, it - * doesn't make sense to show a 'Default' item. - */ - ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); - } - - @Override - protected void onSaveRingtone(Uri ringtoneUri) { - if (!SettingsCompat.System.canWrite(getContext())) { - Toast.makeText( - getContext(), - getContext().getResources().getString(R.string.toast_cannot_write_system_settings), - Toast.LENGTH_SHORT).show(); - return; - } - RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri); - } - - @Override - protected Uri onRestoreRingtone() { - return RingtoneManager.getActualDefaultRingtoneUri(getContext(), getRingtoneType()); - } -} diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java deleted file mode 100644 index dc1e21457..000000000 --- a/src/com/android/dialer/settings/DialerSettingsActivity.java +++ /dev/null @@ -1,190 +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.settings; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.UserManager; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.support.v4.os.BuildCompat; -import android.telecom.TelecomManager; -import android.telephony.TelephonyManager; -import android.view.MenuItem; -import android.widget.Toast; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.TelephonyManagerCompat; -import com.android.dialer.R; -import com.android.dialer.compat.FilteredNumberCompat; -import com.android.dialer.compat.SettingsCompat; -import com.android.dialer.compat.UserManagerCompat; - -import java.util.List; - -public class DialerSettingsActivity extends AppCompatPreferenceActivity { - protected SharedPreferences mPreferences; - private boolean migrationStatusOnBuildHeaders; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - } - - @Override - protected void onResume() { - super.onResume(); - /* - * The headers need to be recreated if the migration status changed between when the headers - * were created and now. - */ - if (migrationStatusOnBuildHeaders != FilteredNumberCompat.hasMigratedToNewBlocking()) { - invalidateHeaders(); - } - } - - @Override - public void onBuildHeaders(List<Header> target) { - if (showDisplayOptions()) { - Header displayOptionsHeader = new Header(); - displayOptionsHeader.titleRes = R.string.display_options_title; - displayOptionsHeader.fragment = DisplayOptionsSettingsFragment.class.getName(); - target.add(displayOptionsHeader); - } - - Header soundSettingsHeader = new Header(); - soundSettingsHeader.titleRes = R.string.sounds_and_vibration_title; - soundSettingsHeader.fragment = SoundSettingsFragment.class.getName(); - soundSettingsHeader.id = R.id.settings_header_sounds_and_vibration; - target.add(soundSettingsHeader); - - if (CompatUtils.isMarshmallowCompatible()) { - Header quickResponseSettingsHeader = new Header(); - Intent quickResponseSettingsIntent = - new Intent(TelecomManager.ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS); - quickResponseSettingsHeader.titleRes = R.string.respond_via_sms_setting_title; - quickResponseSettingsHeader.intent = quickResponseSettingsIntent; - target.add(quickResponseSettingsHeader); - } - - TelephonyManager telephonyManager = - (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); - - // "Call Settings" (full settings) is shown if the current user is primary user and there - // is only one SIM. Before N, "Calling accounts" setting is shown if the current user is - // primary user and there are multiple SIMs. In N+, "Calling accounts" is shown whenever - // "Call Settings" is not shown. - boolean isPrimaryUser = isPrimaryUser(); - if (isPrimaryUser - && TelephonyManagerCompat.getPhoneCount(telephonyManager) <= 1) { - Header callSettingsHeader = new Header(); - Intent callSettingsIntent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS); - callSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - callSettingsHeader.titleRes = R.string.call_settings_label; - callSettingsHeader.intent = callSettingsIntent; - target.add(callSettingsHeader); - } else if (BuildCompat.isAtLeastN() || isPrimaryUser) { - Header phoneAccountSettingsHeader = new Header(); - Intent phoneAccountSettingsIntent = - new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS); - phoneAccountSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - phoneAccountSettingsHeader.titleRes = R.string.phone_account_settings_label; - phoneAccountSettingsHeader.intent = phoneAccountSettingsIntent; - target.add(phoneAccountSettingsHeader); - } - if (FilteredNumberCompat.canCurrentUserOpenBlockSettings(this)) { - Header blockedCallsHeader = new Header(); - blockedCallsHeader.titleRes = R.string.manage_blocked_numbers_label; - blockedCallsHeader.intent = FilteredNumberCompat.createManageBlockedNumbersIntent(this); - target.add(blockedCallsHeader); - migrationStatusOnBuildHeaders = FilteredNumberCompat.hasMigratedToNewBlocking(); - } - if (isPrimaryUser - && (TelephonyManagerCompat.isTtyModeSupported(telephonyManager) - || TelephonyManagerCompat.isHearingAidCompatibilitySupported(telephonyManager))) { - Header accessibilitySettingsHeader = new Header(); - Intent accessibilitySettingsIntent = - new Intent(TelecomManager.ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS); - accessibilitySettingsHeader.titleRes = R.string.accessibility_settings_title; - accessibilitySettingsHeader.intent = accessibilitySettingsIntent; - target.add(accessibilitySettingsHeader); - } - } - - /** - * Returns {@code true} or {@code false} based on whether the display options setting should be - * shown. For languages such as Chinese, Japanese, or Korean, display options aren't useful - * since contacts are sorted and displayed family name first by default. - * - * @return {@code true} if the display options should be shown, {@code false} otherwise. - */ - private boolean showDisplayOptions() { - return getResources().getBoolean(R.bool.config_display_order_user_changeable) - && getResources().getBoolean(R.bool.config_sort_order_user_changeable); - } - - @Override - public void onHeaderClick(Header header, int position) { - if (header.id == R.id.settings_header_sounds_and_vibration) { - // If we don't have the permission to write to system settings, go to system sound - // settings instead. Otherwise, perform the super implementation (which launches our - // own preference fragment. - if (!SettingsCompat.System.canWrite(this)) { - Toast.makeText( - this, - getResources().getString(R.string.toast_cannot_write_system_settings), - Toast.LENGTH_SHORT).show(); - startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS)); - return; - } - } - super.onHeaderClick(header, position); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - return false; - } - - @Override - public void onBackPressed() { - if (!isSafeToCommitTransactions()) { - return; - } - super.onBackPressed(); - } - - @Override - protected boolean isValidFragment(String fragmentName) { - return true; - } - - /** - * @return Whether the current user is the primary user. - */ - private boolean isPrimaryUser() { - return UserManagerCompat.isSystemUser((UserManager) getSystemService(Context.USER_SERVICE)); - } -} diff --git a/src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java b/src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java deleted file mode 100644 index 4b2c8f6db..000000000 --- a/src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java +++ /dev/null @@ -1,31 +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.settings; - -import android.os.Bundle; -import android.preference.PreferenceFragment; - -import com.android.dialer.R; - -public class DisplayOptionsSettingsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.display_options_settings); - } -} diff --git a/src/com/android/dialer/settings/SoundSettingsFragment.java b/src/com/android/dialer/settings/SoundSettingsFragment.java deleted file mode 100644 index 59f8798c3..000000000 --- a/src/com/android/dialer/settings/SoundSettingsFragment.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2014 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.settings; - -import android.content.Context; -import android.media.RingtoneManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Vibrator; -import android.preference.CheckBoxPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; -import android.telephony.TelephonyManager; -import android.widget.Toast; - -import com.android.contacts.common.compat.SdkVersionOverride; -import com.android.dialer.R; -import com.android.dialer.compat.SettingsCompat; -import com.android.phone.common.util.SettingsUtil; - -public class SoundSettingsFragment extends PreferenceFragment - implements Preference.OnPreferenceChangeListener { - - private static final int NO_DTMF_TONE = 0; - private static final int PLAY_DTMF_TONE = 1; - - private static final int NO_VIBRATION_FOR_CALLS = 0; - private static final int DO_VIBRATION_FOR_CALLS = 1; - - - private static final int DTMF_TONE_TYPE_NORMAL = 0; - - private static final int SHOW_CARRIER_SETTINGS = 0; - private static final int HIDE_CARRIER_SETTINGS = 1; - - private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1; - - private Preference mRingtonePreference; - private CheckBoxPreference mVibrateWhenRinging; - private CheckBoxPreference mPlayDtmfTone; - private ListPreference mDtmfToneLength; - - private final Runnable mRingtoneLookupRunnable = new Runnable() { - @Override - public void run() { - updateRingtonePreferenceSummary(); - } - }; - - private final Handler mRingtoneLookupComplete = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_RINGTONE_SUMMARY: - mRingtonePreference.setSummary((CharSequence) msg.obj); - break; - } - } - }; - - @Override - public Context getContext() { - return getActivity(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - addPreferencesFromResource(R.xml.sound_settings); - - Context context = getActivity(); - - mRingtonePreference = findPreference(context.getString(R.string.ringtone_preference_key)); - mVibrateWhenRinging = (CheckBoxPreference) findPreference( - context.getString(R.string.vibrate_on_preference_key)); - mPlayDtmfTone = (CheckBoxPreference) findPreference( - context.getString(R.string.play_dtmf_preference_key)); - mDtmfToneLength = (ListPreference) findPreference( - context.getString(R.string.dtmf_tone_length_preference_key)); - - if (hasVibrator()) { - mVibrateWhenRinging.setOnPreferenceChangeListener(this); - } else { - getPreferenceScreen().removePreference(mVibrateWhenRinging); - mVibrateWhenRinging = null; - } - - mPlayDtmfTone.setOnPreferenceChangeListener(this); - mPlayDtmfTone.setChecked(shouldPlayDtmfTone()); - - TelephonyManager telephonyManager = - (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); - if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M - && telephonyManager.canChangeDtmfToneLength() - && (telephonyManager.isWorldPhone() || !shouldHideCarrierSettings())) { - mDtmfToneLength.setOnPreferenceChangeListener(this); - mDtmfToneLength.setValueIndex( - Settings.System.getInt(context.getContentResolver(), - Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, - DTMF_TONE_TYPE_NORMAL)); - } else { - getPreferenceScreen().removePreference(mDtmfToneLength); - mDtmfToneLength = null; - } - } - - @Override - public void onResume() { - super.onResume(); - - if (!SettingsCompat.System.canWrite(getContext())) { - // If the user launches this setting fragment, then toggles the WRITE_SYSTEM_SETTINGS - // AppOp, then close the fragment since there is nothing useful to do. - getActivity().onBackPressed(); - return; - } - - if (mVibrateWhenRinging != null) { - mVibrateWhenRinging.setChecked(shouldVibrateWhenRinging()); - } - - // Lookup the ringtone name asynchronously. - new Thread(mRingtoneLookupRunnable).start(); - } - - /** - * Supports onPreferenceChangeListener to look for preference changes. - * - * @param preference The preference to be changed - * @param objValue The value of the selection, NOT its localized display value. - */ - @Override - public boolean onPreferenceChange(Preference preference, Object objValue) { - if (!SettingsCompat.System.canWrite(getContext())) { - // A user shouldn't be able to get here, but this protects against monkey crashes. - Toast.makeText( - getContext(), - getResources().getString(R.string.toast_cannot_write_system_settings), - Toast.LENGTH_SHORT).show(); - return true; - } - if (preference == mVibrateWhenRinging) { - boolean doVibrate = (Boolean) objValue; - Settings.System.putInt(getActivity().getContentResolver(), - Settings.System.VIBRATE_WHEN_RINGING, - doVibrate ? DO_VIBRATION_FOR_CALLS : NO_VIBRATION_FOR_CALLS); - } else if (preference == mDtmfToneLength) { - int index = mDtmfToneLength.findIndexOfValue((String) objValue); - Settings.System.putInt(getActivity().getContentResolver(), - Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, index); - } - return true; - } - - /** - * Click listener for toggle events. - */ - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if (!SettingsCompat.System.canWrite(getContext())) { - Toast.makeText( - getContext(), - getResources().getString(R.string.toast_cannot_write_system_settings), - Toast.LENGTH_SHORT).show(); - return true; - } - if (preference == mPlayDtmfTone) { - Settings.System.putInt(getActivity().getContentResolver(), - Settings.System.DTMF_TONE_WHEN_DIALING, - mPlayDtmfTone.isChecked() ? PLAY_DTMF_TONE : NO_DTMF_TONE); - } - return true; - } - - /** - * Updates the summary text on the ringtone preference with the name of the ringtone. - */ - private void updateRingtonePreferenceSummary() { - SettingsUtil.updateRingtoneName( - getActivity(), - mRingtoneLookupComplete, - RingtoneManager.TYPE_RINGTONE, - mRingtonePreference.getKey(), - MSG_UPDATE_RINGTONE_SUMMARY); - } - - /** - * Obtain the value for "vibrate when ringing" setting. The default value is false. - * - * Watch out: if the setting is missing in the device, this will try obtaining the old - * "vibrate on ring" setting from AudioManager, and save the previous setting to the new one. - */ - private boolean shouldVibrateWhenRinging() { - int vibrateWhenRingingSetting = Settings.System.getInt(getActivity().getContentResolver(), - Settings.System.VIBRATE_WHEN_RINGING, - NO_VIBRATION_FOR_CALLS); - return hasVibrator() && (vibrateWhenRingingSetting == DO_VIBRATION_FOR_CALLS); - } - - /** - * Obtains the value for dialpad/DTMF tones. The default value is true. - */ - private boolean shouldPlayDtmfTone() { - int dtmfToneSetting = Settings.System.getInt(getActivity().getContentResolver(), - Settings.System.DTMF_TONE_WHEN_DIALING, - PLAY_DTMF_TONE); - return dtmfToneSetting == PLAY_DTMF_TONE; - } - - /** - * Whether the device hardware has a vibrator. - */ - private boolean hasVibrator() { - Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); - return vibrator != null && vibrator.hasVibrator(); - } - - private boolean shouldHideCarrierSettings() { - CarrierConfigManager configManager = (CarrierConfigManager) getActivity().getSystemService( - Context.CARRIER_CONFIG_SERVICE); - return configManager.getConfig().getBoolean( - CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL); - } -} diff --git a/src/com/android/dialer/util/AppCompatConstants.java b/src/com/android/dialer/util/AppCompatConstants.java deleted file mode 100644 index 1d52eee1d..000000000 --- a/src/com/android/dialer/util/AppCompatConstants.java +++ /dev/null @@ -1,30 +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.util; - -import android.provider.CallLog.Calls; - -public final class AppCompatConstants { - - public static final int CALLS_INCOMING_TYPE = Calls.INCOMING_TYPE; - public static final int CALLS_OUTGOING_TYPE = Calls.OUTGOING_TYPE; - public static final int CALLS_MISSED_TYPE = Calls.MISSED_TYPE; - public static final int CALLS_VOICEMAIL_TYPE = Calls.VOICEMAIL_TYPE; - // Added to android.provider.CallLog.Calls in N+. - public static final int CALLS_REJECTED_TYPE = 5; - // Added to android.provider.CallLog.Calls in N+. - public static final int CALLS_BLOCKED_TYPE = 6; -} diff --git a/src/com/android/dialer/util/Assert.java b/src/com/android/dialer/util/Assert.java deleted file mode 100644 index ec0a6ccb6..000000000 --- a/src/com/android/dialer/util/Assert.java +++ /dev/null @@ -1,36 +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.util; - -import android.os.Looper; - -public class Assert { - public static void assertNotUiThread(String msg) { - if (Looper.myLooper() == Looper.getMainLooper()) { - throw new AssertionError(msg); - } - } - - public static void assertNotNull(Object object, String msg) { - if (object == null) { - throw new AssertionError(object); - } - } - - public static void assertNotNull(Object object) { - assertNotNull(object, null); - } -} diff --git a/src/com/android/dialer/util/AsyncTaskExecutor.java b/src/com/android/dialer/util/AsyncTaskExecutor.java deleted file mode 100644 index ca09f0878..000000000 --- a/src/com/android/dialer/util/AsyncTaskExecutor.java +++ /dev/null @@ -1,48 +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.util; - -import android.os.AsyncTask; - -import java.util.concurrent.Executor; - -/** - * Interface used to submit {@link AsyncTask} objects to run in the background. - * <p> - * This interface has a direct parallel with the {@link Executor} interface. It exists to decouple - * the mechanics of AsyncTask submission from the description of how that AsyncTask will execute. - * <p> - * One immediate benefit of this approach is that testing becomes much easier, since it is easy to - * introduce a mock or fake AsyncTaskExecutor in unit/integration tests, and thus inspect which - * tasks have been submitted and control their execution in an orderly manner. - * <p> - * Another benefit in due course will be the management of the submitted tasks. An extension to this - * interface is planned to allow Activities to easily cancel all the submitted tasks that are still - * pending in the onDestroy() method of the Activity. - */ -public interface AsyncTaskExecutor { - /** - * Executes the given AsyncTask with the default Executor. - * <p> - * This method <b>must only be called from the ui thread</b>. - * <p> - * The identifier supplied is any Object that can be used to identify the task later. Most - * commonly this will be an enum which the tests can also refer to. {@code null} is also - * accepted, though of course this won't help in identifying the task later. - */ - <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params); -} diff --git a/src/com/android/dialer/util/AsyncTaskExecutors.java b/src/com/android/dialer/util/AsyncTaskExecutors.java deleted file mode 100644 index a59af3e41..000000000 --- a/src/com/android/dialer/util/AsyncTaskExecutors.java +++ /dev/null @@ -1,100 +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.util; - -import android.os.AsyncTask; -import android.os.Looper; - -import com.android.contacts.common.testing.NeededForTesting; -import com.google.common.base.Preconditions; - -import java.util.concurrent.Executor; - -/** - * Factory methods for creating AsyncTaskExecutors. - * <p> - * All of the factory methods on this class check first to see if you have set a static - * {@link AsyncTaskExecutorFactory} set through the - * {@link #setFactoryForTest(AsyncTaskExecutorFactory)} method, and if so delegate to that instead, - * which is one way of injecting dependencies for testing classes whose construction cannot be - * controlled such as {@link android.app.Activity}. - */ -public final class AsyncTaskExecutors { - /** - * A single instance of the {@link AsyncTaskExecutorFactory}, to which we delegate if it is - * non-null, for injecting when testing. - */ - private static AsyncTaskExecutorFactory mInjectedAsyncTaskExecutorFactory = null; - - /** - * Creates an AsyncTaskExecutor that submits tasks to run with - * {@link AsyncTask#SERIAL_EXECUTOR}. - */ - public static AsyncTaskExecutor createAsyncTaskExecutor() { - synchronized (AsyncTaskExecutors.class) { - if (mInjectedAsyncTaskExecutorFactory != null) { - return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor(); - } - return new SimpleAsyncTaskExecutor(AsyncTask.SERIAL_EXECUTOR); - } - } - - /** - * Creates an AsyncTaskExecutor that submits tasks to run with - * {@link AsyncTask#THREAD_POOL_EXECUTOR}. - */ - public static AsyncTaskExecutor createThreadPoolExecutor() { - synchronized (AsyncTaskExecutors.class) { - if (mInjectedAsyncTaskExecutorFactory != null) { - return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor(); - } - return new SimpleAsyncTaskExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - /** Interface for creating AsyncTaskExecutor objects. */ - public interface AsyncTaskExecutorFactory { - AsyncTaskExecutor createAsyncTaskExeuctor(); - } - - @NeededForTesting - public static void setFactoryForTest(AsyncTaskExecutorFactory factory) { - synchronized (AsyncTaskExecutors.class) { - mInjectedAsyncTaskExecutorFactory = factory; - } - } - - public static void checkCalledFromUiThread() { - Preconditions.checkState(Thread.currentThread() == Looper.getMainLooper().getThread(), - "submit method must be called from ui thread, was: " + Thread.currentThread()); - } - - private static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor { - private final Executor mExecutor; - - public SimpleAsyncTaskExecutor(Executor executor) { - mExecutor = executor; - } - - @Override - public <T> AsyncTask<T, ?, ?> submit(Object identifer, AsyncTask<T, ?, ?> task, - T... params) { - checkCalledFromUiThread(); - return task.executeOnExecutor(mExecutor, params); - } - } -} diff --git a/src/com/android/dialer/util/BlockReportSpamDialogs.java b/src/com/android/dialer/util/BlockReportSpamDialogs.java deleted file mode 100644 index 45b2f4228..000000000 --- a/src/com/android/dialer/util/BlockReportSpamDialogs.java +++ /dev/null @@ -1,293 +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.util; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.CheckBox; -import android.widget.CompoundButton; - -import com.android.dialer.R; - -/** - * Helper class for creating block/report dialog fragments. - */ -public class BlockReportSpamDialogs { - public static final String BLOCK_REPORT_SPAM_DIALOG_TAG = "BlockReportSpamDialog"; - public static final String BLOCK_DIALOG_TAG = "BlockDialog"; - public static final String UNBLOCK_DIALOG_TAG = "UnblockDialog"; - public static final String NOT_SPAM_DIALOG_TAG = "NotSpamDialog"; - - /** - * Listener passed to block/report spam dialog for positive click in - * {@link BlockReportSpamDialogFragment}. - */ - public interface OnSpamDialogClickListener { - /** - * Called when user clicks on positive button in block/report spam dialog. - * @param isSpamChecked Whether the spam checkbox is checked. - */ - void onClick(boolean isSpamChecked); - } - - /** - * Listener passed to all dialogs except the block/report spam dialog for positive click. - */ - public interface OnConfirmListener { - /** - * Called when user clicks on positive button in the dialog. - */ - void onClick(); - } - - /** - * Contains the common attributes between all block/unblock/report dialog fragments. - */ - private static class CommonDialogsFragment extends DialogFragment { - /** - * The number to display in the dialog title. - */ - protected String mDisplayNumber; - - /** - * Called when dialog positive button is pressed. - */ - protected OnConfirmListener mPositiveListener; - - /** - * Called when dialog is dismissed. - */ - @Nullable - protected DialogInterface.OnDismissListener mDismissListener; - - @Override - public void onDismiss(DialogInterface dialog) { - if (mDismissListener != null) { - mDismissListener.onDismiss(dialog); - } - super.onDismiss(dialog); - } - - @Override - public void onPause() { - // The dialog is dismissed onPause, i.e. rotation. - dismiss(); - mDismissListener = null; - mPositiveListener = null; - mDisplayNumber = null; - super.onPause(); - } - } - - /** - * Dialog for block/report spam with the mark as spam checkbox. - */ - public static class BlockReportSpamDialogFragment extends CommonDialogsFragment { - /** - * Called when dialog positive button is pressed. - */ - private OnSpamDialogClickListener mPositiveListener; - - /** - * Whether the mark as spam checkbox is checked before displaying the dialog. - */ - private boolean mSpamChecked; - - public static DialogFragment newInstance(String displayNumber, - boolean spamChecked, - OnSpamDialogClickListener positiveListener, - @Nullable DialogInterface.OnDismissListener - dismissListener) { - BlockReportSpamDialogFragment fragment = new BlockReportSpamDialogFragment(); - fragment.mSpamChecked = spamChecked; - fragment.mDisplayNumber = displayNumber; - fragment.mPositiveListener = positiveListener; - fragment.mDismissListener = dismissListener; - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreateDialog(savedInstanceState); - View dialogView = View.inflate(getActivity(), R.layout.block_report_spam_dialog, null); - final CheckBox isSpamCheckbox = - (CheckBox) dialogView.findViewById(R.id.report_number_as_spam_action); - // Listen for changes on the checkbox and update if orientation changes - isSpamCheckbox.setChecked(mSpamChecked); - isSpamCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mSpamChecked = isChecked; - } - }); - - AlertDialog.Builder alertDialogBuilder = createDialogBuilder(getActivity(), this); - Dialog dialog = alertDialogBuilder - .setView(dialogView) - .setTitle(getString(R.string.block_report_number_alert_title, mDisplayNumber)) - .setPositiveButton(R.string.block_number_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - mPositiveListener.onClick(isSpamCheckbox.isChecked()); - } - }).create(); - dialog.setCanceledOnTouchOutside(true); - return dialog; - } - } - - /** - * Dialog for blocking a number. - */ - public static class BlockDialogFragment extends CommonDialogsFragment { - public static DialogFragment newInstance(String displayNumber, - OnConfirmListener positiveListener, - @Nullable DialogInterface.OnDismissListener - dismissListener) { - BlockDialogFragment fragment = new BlockDialogFragment(); - fragment.mDisplayNumber = displayNumber; - fragment.mPositiveListener = positiveListener; - fragment.mDismissListener = dismissListener; - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreateDialog(savedInstanceState); - // Return the newly created dialog - AlertDialog.Builder alertDialogBuilder = createDialogBuilder(getActivity(), this); - Dialog dialog = alertDialogBuilder - .setTitle(getString(R.string.block_report_number_alert_title, mDisplayNumber)) - .setPositiveButton(R.string.block_number_ok, - createGenericOnClickListener(this, mPositiveListener)) - .create(); - dialog.setCanceledOnTouchOutside(true); - return dialog; - } - } - - /** - * Dialog for unblocking a number. - */ - public static class UnblockDialogFragment extends CommonDialogsFragment { - /** - * Whether or not the number is spam. - */ - private boolean mIsSpam; - - public static DialogFragment newInstance(String displayNumber, - boolean isSpam, - OnConfirmListener positiveListener, - @Nullable DialogInterface.OnDismissListener - dismissListener) { - UnblockDialogFragment fragment = new UnblockDialogFragment(); - fragment.mDisplayNumber = displayNumber; - fragment.mIsSpam = isSpam; - fragment.mPositiveListener = positiveListener; - fragment.mDismissListener = dismissListener; - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreateDialog(savedInstanceState); - // Return the newly created dialog - AlertDialog.Builder alertDialogBuilder = createDialogBuilder(getActivity(), this); - if (mIsSpam) { - alertDialogBuilder.setMessage(R.string.unblock_number_alert_details); - } - Dialog dialog = alertDialogBuilder - .setTitle(getString(R.string.unblock_report_number_alert_title, mDisplayNumber)) - .setPositiveButton(R.string.unblock_number_ok, - createGenericOnClickListener(this, mPositiveListener)) - .create(); - dialog.setCanceledOnTouchOutside(true); - return dialog; - } - } - - /** - * Dialog for reporting a number as not spam. - */ - public static class ReportNotSpamDialogFragment extends CommonDialogsFragment { - public static DialogFragment newInstance(String displayNumber, - OnConfirmListener positiveListener, - @Nullable DialogInterface.OnDismissListener - dismissListener) { - ReportNotSpamDialogFragment fragment = new ReportNotSpamDialogFragment(); - fragment.mDisplayNumber = displayNumber; - fragment.mPositiveListener = positiveListener; - fragment.mDismissListener = dismissListener; - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreateDialog(savedInstanceState); - // Return the newly created dialog - AlertDialog.Builder alertDialogBuilder = createDialogBuilder(getActivity(), this); - Dialog dialog = alertDialogBuilder - .setTitle(getString(R.string.report_not_spam_alert_title, mDisplayNumber)) - .setMessage(R.string.report_not_spam_alert_details) - .setPositiveButton(R.string.report_not_spam_alert_button, - createGenericOnClickListener(this, mPositiveListener)) - .create(); - dialog.setCanceledOnTouchOutside(true); - return dialog; - } - } - - /** - * Creates a dialog with the default cancel button listener (dismisses dialog). - */ - private static AlertDialog.Builder createDialogBuilder(Activity activity, - final DialogFragment fragment) { - return new AlertDialog.Builder(activity) - .setCancelable(true) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - fragment.dismiss(); - } - }); - } - - /** - * Creates a generic click listener which dismisses the fragment and then calls the actual - * listener. - */ - private static DialogInterface.OnClickListener createGenericOnClickListener( - final DialogFragment fragment, - final OnConfirmListener listener) { - return new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - fragment.dismiss(); - listener.onClick(); - } - }; - } -} diff --git a/src/com/android/dialer/util/DialerUtils.java b/src/com/android/dialer/util/DialerUtils.java deleted file mode 100644 index 95d6a81b6..000000000 --- a/src/com/android/dialer/util/DialerUtils.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2014 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.util; - -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.graphics.Point; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Telephony; -import android.telecom.TelecomManager; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.Toast; - -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.interactions.TouchPointManager; -import com.android.dialer.R; - -import java.util.Iterator; -import java.util.List; -import java.util.Locale; - -/** - * General purpose utility methods for the Dialer. - */ -public class DialerUtils { - - /** - * Attempts to start an activity and displays a toast with the default error message if the - * activity is not found, instead of throwing an exception. - * - * @param context to start the activity with. - * @param intent to start the activity with. - */ - public static void startActivityWithErrorToast(Context context, Intent intent) { - startActivityWithErrorToast(context, intent, R.string.activity_not_available); - } - - /** - * Attempts to start an activity and displays a toast with a provided error message if the - * activity is not found, instead of throwing an exception. - * - * @param context to start the activity with. - * @param intent to start the activity with. - * @param msgId Resource ID of the string to display in an error message if the activity is - * not found. - */ - public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) { - try { - if ((IntentUtil.CALL_ACTION.equals(intent.getAction()) - && context instanceof Activity)) { - // All dialer-initiated calls should pass the touch point to the InCallUI - Point touchPoint = TouchPointManager.getInstance().getPoint(); - if (touchPoint.x != 0 || touchPoint.y != 0) { - Bundle extras; - // Make sure to not accidentally clobber any existing extras - if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) { - extras = intent.getParcelableExtra( - TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); - } else { - extras = new Bundle(); - } - extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint); - intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); - } - - final boolean hasCallPermission = TelecomUtil.placeCall((Activity) context, intent); - if (!hasCallPermission) { - // TODO: Make calling activity show request permission dialog and handle - // callback results appropriately. - Toast.makeText(context, "Cannot place call without Phone permission", - Toast.LENGTH_SHORT); - } - } else { - context.startActivity(intent); - } - } catch (ActivityNotFoundException e) { - Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show(); - } - } - - /** - * Returns the component name to use in order to send an SMS using the default SMS application, - * or null if none exists. - */ - public static ComponentName getSmsComponent(Context context) { - String smsPackage = Telephony.Sms.getDefaultSmsPackage(context); - if (smsPackage != null) { - final PackageManager packageManager = context.getPackageManager(); - final Intent intent = new Intent(Intent.ACTION_SENDTO, - Uri.fromParts(ContactsUtils.SCHEME_SMSTO, "", null)); - final List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0); - for (ResolveInfo resolveInfo : resolveInfos) { - if (smsPackage.equals(resolveInfo.activityInfo.packageName)) { - return new ComponentName(smsPackage, resolveInfo.activityInfo.name); - } - } - } - return null; - } - - /** - * Closes an {@link AutoCloseable}, silently ignoring any checked exceptions. Does nothing if - * null. - * - * @param closeable to close. - */ - public static void closeQuietly(AutoCloseable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Joins a list of {@link CharSequence} into a single {@link CharSequence} seperated by a - * localized delimiter such as ", ". - * - * @param resources Resources used to get list delimiter. - * @param list List of char sequences to join. - * @return Joined char sequences. - */ - public static CharSequence join(Resources resources, Iterable<CharSequence> list) { - StringBuilder sb = new StringBuilder(); - final BidiFormatter formatter = BidiFormatter.getInstance(); - final CharSequence separator = resources.getString(R.string.list_delimeter); - - Iterator<CharSequence> itr = list.iterator(); - boolean firstTime = true; - while (itr.hasNext()) { - if (firstTime) { - firstTime = false; - } else { - sb.append(separator); - } - // Unicode wrap the elements of the list to respect RTL for individual strings. - sb.append(formatter.unicodeWrap( - itr.next().toString(), TextDirectionHeuristics.FIRSTSTRONG_LTR)); - } - - // Unicode wrap the joined value, to respect locale's RTL ordering for the whole list. - return formatter.unicodeWrap(sb.toString()); - } - - /** - * @return True if the application is currently in RTL mode. - */ - public static boolean isRtl() { - return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == - View.LAYOUT_DIRECTION_RTL; - } - - public static void showInputMethod(View view) { - final InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService( - Context.INPUT_METHOD_SERVICE); - if (imm != null) { - imm.showSoftInput(view, 0); - } - } - - public static void hideInputMethod(View view) { - final InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService( - Context.INPUT_METHOD_SERVICE); - if (imm != null) { - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } -} diff --git a/src/com/android/dialer/util/EmptyLoader.java b/src/com/android/dialer/util/EmptyLoader.java deleted file mode 100644 index dd4c0a330..000000000 --- a/src/com/android/dialer/util/EmptyLoader.java +++ /dev/null @@ -1,60 +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.util; - -import android.app.LoaderManager.LoaderCallbacks; -import android.content.Context; -import android.content.Loader; -import android.os.Bundle; - -/** - * A {@link Loader} only used to make use of the {@link android.app.Fragment#setStartDeferred} - * feature from an old-style fragment which doesn't use {@link Loader}s to load data. - * - * This loader never delivers results. A caller fragment must destroy it when deferred fragments - * should be started. - */ -public class EmptyLoader extends Loader<Object> { - public EmptyLoader(Context context) { - super(context); - } - - /** - * {@link LoaderCallbacks} which just generates {@link EmptyLoader}. {@link #onLoadFinished} - * and {@link #onLoaderReset} are no-op. - */ - public static class Callback implements LoaderCallbacks<Object> { - private final Context mContext; - - public Callback(Context context) { - mContext = context.getApplicationContext(); - } - - @Override - public Loader<Object> onCreateLoader(int id, Bundle args) { - return new EmptyLoader(mContext); - } - - @Override - public void onLoadFinished(Loader<Object> loader, Object data) { - } - - @Override - public void onLoaderReset(Loader<Object> loader) { - } - } -} diff --git a/src/com/android/dialer/util/ExpirableCache.java b/src/com/android/dialer/util/ExpirableCache.java deleted file mode 100644 index 00ebd1607..000000000 --- a/src/com/android/dialer/util/ExpirableCache.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.util; - -import android.util.LruCache; - -import com.android.contacts.common.testing.NeededForTesting; - -import java.util.concurrent.atomic.AtomicInteger; - -import javax.annotation.concurrent.Immutable; -import javax.annotation.concurrent.ThreadSafe; - -/** - * An LRU cache in which all items can be marked as expired at a given time and it is possible to - * query whether a particular cached value is expired or not. - * <p> - * A typical use case for this is caching of values which are expensive to compute but which are - * still useful when out of date. - * <p> - * Consider a cache for contact information: - * <pre>{@code - * private ExpirableCache<String, Contact> mContactCache;}</pre> - * which stores the contact information for a given phone number. - * <p> - * When we need to store contact information for a given phone number, we can look up the info in - * the cache: - * <pre>{@code - * CachedValue<Contact> cachedContact = mContactCache.getCachedValue(phoneNumber); - * }</pre> - * We might also want to fetch the contact information again if the item is expired. - * <pre> - * if (cachedContact.isExpired()) { - * fetchContactForNumber(phoneNumber, - * new FetchListener() { - * @Override - * public void onFetched(Contact contact) { - * mContactCache.put(phoneNumber, contact); - * } - * }); - * }</pre> - * and insert it back into the cache when the fetch completes. - * <p> - * At a certain point we want to expire the content of the cache because we know the content may - * no longer be up-to-date, for instance, when resuming the activity this is shown into: - * <pre> - * @Override - * protected onResume() { - * // We were paused for some time, the cached value might no longer be up to date. - * mContactCache.expireAll(); - * super.onResume(); - * } - * </pre> - * The values will be still available from the cache, but they will be expired. - * <p> - * If interested only in the value itself, not whether it is expired or not, one should use the - * {@link #getPossiblyExpired(Object)} method. If interested only in non-expired values, one should - * use the {@link #get(Object)} method instead. - * <p> - * This class wraps around an {@link LruCache} instance: it follows the {@link LruCache} behavior - * for evicting items when the cache is full. It is possible to supply your own subclass of LruCache - * by using the {@link #create(LruCache)} method, which can define a custom expiration policy. - * Since the underlying cache maps keys to cached values it can determine which items are expired - * and which are not, allowing for an implementation that evicts expired items before non expired - * ones. - * <p> - * This class is thread-safe. - * - * @param <K> the type of the keys - * @param <V> the type of the values - */ -@ThreadSafe -public class ExpirableCache<K, V> { - /** - * A cached value stored inside the cache. - * <p> - * It provides access to the value stored in the cache but also allows to check whether the - * value is expired. - * - * @param <V> the type of value stored in the cache - */ - public interface CachedValue<V> { - /** Returns the value stored in the cache for a given key. */ - public V getValue(); - - /** - * Checks whether the value, while still being present in the cache, is expired. - * - * @return true if the value is expired - */ - public boolean isExpired(); - } - - /** - * Cached values storing the generation at which they were added. - */ - @Immutable - private static class GenerationalCachedValue<V> implements ExpirableCache.CachedValue<V> { - /** The value stored in the cache. */ - public final V mValue; - /** The generation at which the value was added to the cache. */ - private final int mGeneration; - /** The atomic integer storing the current generation of the cache it belongs to. */ - private final AtomicInteger mCacheGeneration; - - /** - * @param cacheGeneration the atomic integer storing the generation of the cache in which - * this value will be stored - */ - public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) { - mValue = value; - mCacheGeneration = cacheGeneration; - // Snapshot the current generation. - mGeneration = mCacheGeneration.get(); - } - - @Override - public V getValue() { - return mValue; - } - - @Override - public boolean isExpired() { - return mGeneration != mCacheGeneration.get(); - } - } - - /** The underlying cache used to stored the cached values. */ - private LruCache<K, CachedValue<V>> mCache; - - /** - * The current generation of items added to the cache. - * <p> - * Items in the cache can belong to a previous generation, but in that case they would be - * expired. - * - * @see ExpirableCache.CachedValue#isExpired() - */ - private final AtomicInteger mGeneration; - - private ExpirableCache(LruCache<K, CachedValue<V>> cache) { - mCache = cache; - mGeneration = new AtomicInteger(0); - } - - /** - * Returns the cached value for the given key, or null if no value exists. - * <p> - * The cached value gives access both to the value associated with the key and whether it is - * expired or not. - * <p> - * If not interested in whether the value is expired, use {@link #getPossiblyExpired(Object)} - * instead. - * <p> - * If only wants values that are not expired, use {@link #get(Object)} instead. - * - * @param key the key to look up - */ - public CachedValue<V> getCachedValue(K key) { - return mCache.get(key); - } - - /** - * Returns the value for the given key, or null if no value exists. - * <p> - * When using this method, it is not possible to determine whether the value is expired or not. - * Use {@link #getCachedValue(Object)} to achieve that instead. However, if using - * {@link #getCachedValue(Object)} to determine if an item is expired, one should use the item - * within the {@link CachedValue} and not call {@link #getPossiblyExpired(Object)} to get the - * value afterwards, since that is not guaranteed to return the same value or that the newly - * returned value is in the same state. - * - * @param key the key to look up - */ - public V getPossiblyExpired(K key) { - CachedValue<V> cachedValue = getCachedValue(key); - return cachedValue == null ? null : cachedValue.getValue(); - } - - /** - * Returns the value for the given key only if it is not expired, or null if no value exists or - * is expired. - * <p> - * This method will return null if either there is no value associated with this key or if the - * associated value is expired. - * - * @param key the key to look up - */ - @NeededForTesting - public V get(K key) { - CachedValue<V> cachedValue = getCachedValue(key); - return cachedValue == null || cachedValue.isExpired() ? null : cachedValue.getValue(); - } - - /** - * Puts an item in the cache. - * <p> - * Newly added item will not be expired until {@link #expireAll()} is next called. - * - * @param key the key to look up - * @param value the value to associate with the key - */ - public void put(K key, V value) { - mCache.put(key, newCachedValue(value)); - } - - /** - * Mark all items currently in the cache as expired. - * <p> - * Newly added items after this call will be marked as not expired. - * <p> - * Expiring the items in the cache does not imply they will be evicted. - */ - public void expireAll() { - mGeneration.incrementAndGet(); - } - - /** - * Creates a new {@link CachedValue} instance to be stored in this cache. - * <p> - * Implementation of {@link LruCache#create(K)} can use this method to create a new entry. - */ - public CachedValue<V> newCachedValue(V value) { - return new GenerationalCachedValue<V>(value, mGeneration); - } - - /** - * Creates a new {@link ExpirableCache} that wraps the given {@link LruCache}. - * <p> - * The created cache takes ownership of the cache passed in as an argument. - * - * @param <K> the type of the keys - * @param <V> the type of the values - * @param cache the cache to store the value in - * @return the newly created expirable cache - * @throws IllegalArgumentException if the cache is not empty - */ - public static <K, V> ExpirableCache<K, V> create(LruCache<K, CachedValue<V>> cache) { - return new ExpirableCache<K, V>(cache); - } - - /** - * Creates a new {@link ExpirableCache} with the given maximum size. - * - * @param <K> the type of the keys - * @param <V> the type of the values - * @return the newly created expirable cache - */ - public static <K, V> ExpirableCache<K, V> create(int maxSize) { - return create(new LruCache<K, CachedValue<V>>(maxSize)); - } -} diff --git a/src/com/android/dialer/util/IntentUtil.java b/src/com/android/dialer/util/IntentUtil.java deleted file mode 100644 index 581e10da4..000000000 --- a/src/com/android/dialer/util/IntentUtil.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2012 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.util; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.SystemClock; -import android.provider.ContactsContract; -import android.telecom.PhoneAccountHandle; -import android.telecom.TelecomManager; -import android.telecom.VideoProfile; - -import com.android.contacts.common.CallUtil; - -/** - * Utilities for creation of intents in Dialer, such as {@link Intent#ACTION_CALL}. - */ -public class IntentUtil { - - public static final String CALL_ACTION = Intent.ACTION_CALL; - private static final String SMS_URI_PREFIX = "sms:"; - private static final int NO_PHONE_TYPE = -1; - - public static final String EXTRA_CALL_INITIATION_TYPE - = "com.android.dialer.EXTRA_CALL_INITIATION_TYPE"; - public static final String EXTRA_CALL_CREATED_TIME_MILLIS = - "android.telecom.extra.CALL_CREATED_TIME_MILLIS"; - - public static class CallIntentBuilder { - private Uri mUri; - private int mCallInitiationType; - private PhoneAccountHandle mPhoneAccountHandle; - private boolean mIsVideoCall = false; - - public CallIntentBuilder(Uri uri) { - mUri = uri; - } - - public CallIntentBuilder(String number) { - this(CallUtil.getCallUri(number)); - } - - public CallIntentBuilder setCallInitiationType(int initiationType) { - mCallInitiationType = initiationType; - return this; - } - - public CallIntentBuilder setPhoneAccountHandle(PhoneAccountHandle accountHandle) { - mPhoneAccountHandle = accountHandle; - return this; - } - - public CallIntentBuilder setIsVideoCall(boolean isVideoCall) { - mIsVideoCall = isVideoCall; - return this; - } - - public Intent build() { - return getCallIntent( - mUri, - mPhoneAccountHandle, - mIsVideoCall ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY, - mCallInitiationType); - } - } - - /** - * Create a call intent that can be used to place a call. - * - * @param uri Address to place the call to. - * @param accountHandle {@link PhoneAccountHandle} to place the call with. - * @param videoState Initial video state of the call. - * @param callIntiationType The UI affordance the call was initiated by. - * @return Call intent with provided extras and data. - */ - public static Intent getCallIntent( - Uri uri, PhoneAccountHandle accountHandle, int videoState, int callIntiationType) { - final Intent intent = new Intent(CALL_ACTION, uri); - intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); - - final Bundle b = new Bundle(); - b.putLong(EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime()); - b.putInt(EXTRA_CALL_INITIATION_TYPE, callIntiationType); - intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, b); - - if (accountHandle != null) { - intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); - } - - return intent; - } - - public static Intent getSendSmsIntent(CharSequence phoneNumber) { - return new Intent(Intent.ACTION_SENDTO, Uri.parse(SMS_URI_PREFIX + phoneNumber)); - } - - public static Intent getNewContactIntent() { - return new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI); - } - - public static Intent getNewContactIntent(CharSequence phoneNumber) { - return getNewContactIntent( - null /* name */, - phoneNumber /* phoneNumber */, - NO_PHONE_TYPE); - } - - public static Intent getNewContactIntent( - CharSequence name, CharSequence phoneNumber, int phoneNumberType) { - Intent intent = getNewContactIntent(); - populateContactIntent(intent, name, phoneNumber, phoneNumberType); - return intent; - } - - public static Intent getAddToExistingContactIntent() { - Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - return intent; - } - - public static Intent getAddToExistingContactIntent(CharSequence phoneNumber) { - return getAddToExistingContactIntent( - null /* name */, - phoneNumber /* phoneNumber */, - NO_PHONE_TYPE); - } - - public static Intent getAddToExistingContactIntent( - CharSequence name, CharSequence phoneNumber, int phoneNumberType) { - Intent intent = getAddToExistingContactIntent(); - populateContactIntent(intent, name, phoneNumber, phoneNumberType); - return intent; - } - - private static void populateContactIntent( - Intent intent, CharSequence name, CharSequence phoneNumber, int phoneNumberType) { - if (phoneNumber != null) { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, phoneNumber); - } - if (name != null) { - intent.putExtra(ContactsContract.Intents.Insert.NAME, name); - } - if (phoneNumberType != NO_PHONE_TYPE) { - intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE, phoneNumberType); - } - } -} diff --git a/src/com/android/dialer/util/MoreStrings.java b/src/com/android/dialer/util/MoreStrings.java deleted file mode 100644 index 68956f25c..000000000 --- a/src/com/android/dialer/util/MoreStrings.java +++ /dev/null @@ -1,43 +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.util; - -/** - * Static utility methods for Strings. - */ -public class MoreStrings { - - public static String toSafeString(String value) { - if (value == null) { - return null; - } - - // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare - // sanitized phone numbers. - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - final char c = value.charAt(i); - if (c == '-' || c == '@' || c == '.') { - builder.append(c); - } else { - builder.append('x'); - } - } - return builder.toString(); - } - -} diff --git a/src/com/android/dialer/util/OrientationUtil.java b/src/com/android/dialer/util/OrientationUtil.java deleted file mode 100644 index 2eb2af3ff..000000000 --- a/src/com/android/dialer/util/OrientationUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2012 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.util; - -import android.content.Context; -import android.content.res.Configuration; - -/** - * Static methods related to device orientation. - */ -public class OrientationUtil { - - /** - * @return if the context is in landscape orientation. - */ - public static boolean isLandscape(Context context) { - return context.getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - } -} diff --git a/src/com/android/dialer/util/PhoneLookupUtil.java b/src/com/android/dialer/util/PhoneLookupUtil.java deleted file mode 100644 index 1a7239642..000000000 --- a/src/com/android/dialer/util/PhoneLookupUtil.java +++ /dev/null @@ -1,40 +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.util; - -import android.net.Uri; -import android.provider.ContactsContract; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.PhoneLookupSdkCompat; - -public final class PhoneLookupUtil { - /** - * @return the column name that stores contact id for phone lookup query. - */ - public static String getContactIdColumnNameForUri(Uri phoneLookupUri) { - if (CompatUtils.isNCompatible()) { - return PhoneLookupSdkCompat.CONTACT_ID; - } - // In pre-N, contact id is stored in {@link PhoneLookup#_ID} in non-sip query. - boolean isSip = phoneLookupUri.getBooleanQueryParameter( - ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, false); - return (isSip) ? PhoneLookupSdkCompat.CONTACT_ID : ContactsContract.PhoneLookup._ID; - } - - private PhoneLookupUtil() {} -} diff --git a/src/com/android/dialer/util/PhoneNumberUtil.java b/src/com/android/dialer/util/PhoneNumberUtil.java deleted file mode 100644 index 33f987359..000000000 --- a/src/com/android/dialer/util/PhoneNumberUtil.java +++ /dev/null @@ -1,138 +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.util; - -import android.content.Context; -import android.provider.CallLog; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; - -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.contacts.common.util.TelephonyManagerUtils; -import com.google.common.collect.Sets; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.Phonenumber; -import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -public class PhoneNumberUtil { - private static final String TAG = "PhoneNumberUtil"; - private static final Set<String> LEGACY_UNKNOWN_NUMBERS = Sets.newHashSet("-1", "-2", "-3"); - - /** Returns true if it is possible to place a call to the given number. */ - public static boolean canPlaceCallsTo(CharSequence number, int presentation) { - return presentation == CallLog.Calls.PRESENTATION_ALLOWED - && !TextUtils.isEmpty(number) && !isLegacyUnknownNumbers(number); - } - - /** - * 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 static boolean isVoicemailNumber( - Context context, PhoneAccountHandle accountHandle, CharSequence number) { - if (TextUtils.isEmpty(number)) { - return false; - } - return TelecomUtil.isVoicemailNumber(context, accountHandle, number.toString()); - } - - /** - * Returns true if the given number is a SIP address. To be able to mock-out this, it is not a - * static method. - */ - public static boolean isSipNumber(CharSequence number) { - return number != null && PhoneNumberHelper.isUriNumber(number.toString()); - } - - public static boolean isUnknownNumberThatCanBeLookedUp( - Context context, - PhoneAccountHandle accountHandle, - CharSequence number, - int presentation) { - if (presentation == CallLog.Calls.PRESENTATION_UNKNOWN) { - return false; - } - if (presentation == CallLog.Calls.PRESENTATION_RESTRICTED) { - return false; - } - if (presentation == CallLog.Calls.PRESENTATION_PAYPHONE) { - return false; - } - if (TextUtils.isEmpty(number)) { - return false; - } - if (isVoicemailNumber(context, accountHandle, number)) { - return false; - } - if (isLegacyUnknownNumbers(number)) { - return false; - } - return true; - } - - public static boolean isLegacyUnknownNumbers(CharSequence number) { - return number != null && LEGACY_UNKNOWN_NUMBERS.contains(number.toString()); - } - - /** - * @return a geographical description string for the specified number. - * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder - */ - public static String getGeoDescription(Context context, String number) { - Log.v(TAG, "getGeoDescription('" + pii(number) + "')..."); - - if (TextUtils.isEmpty(number)) { - return null; - } - - com.google.i18n.phonenumbers.PhoneNumberUtil util = - com.google.i18n.phonenumbers.PhoneNumberUtil.getInstance(); - PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance(); - - Locale locale = context.getResources().getConfiguration().locale; - String countryIso = TelephonyManagerUtils.getCurrentCountryIso(context, locale); - Phonenumber.PhoneNumber pn = null; - try { - Log.v(TAG, "parsing '" + pii(number) - + "' for countryIso '" + countryIso + "'..."); - pn = util.parse(number, countryIso); - Log.v(TAG, "- parsed number: " + pii(pn)); - } catch (NumberParseException e) { - Log.v(TAG, "getGeoDescription: NumberParseException for incoming number '" + - pii(number) + "'"); - } - - if (pn != null) { - String description = geocoder.getDescriptionForNumber(pn, locale); - Log.v(TAG, "- got description: '" + description + "'"); - return description; - } - - return null; - } - - private static String pii(Object pii) { - return com.android.incallui.Log.pii(pii); - } -} diff --git a/src/com/android/dialer/util/TelecomUtil.java b/src/com/android/dialer/util/TelecomUtil.java deleted file mode 100644 index 69c7334b9..000000000 --- a/src/com/android/dialer/util/TelecomUtil.java +++ /dev/null @@ -1,229 +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.util; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.provider.CallLog.Calls; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.telecom.TelecomManager; -import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import com.android.contacts.common.compat.CompatUtils; -import com.android.contacts.common.compat.telecom.TelecomManagerCompat; -import com.android.dialer.compat.DialerCompatUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Performs permission checks before calling into TelecomManager. Each method is self-explanatory - - * perform the required check and return the fallback default if the permission is missing, - * otherwise return the value from TelecomManager. - * - */ -public class TelecomUtil { - private static final String TAG = "TelecomUtil"; - private static boolean sWarningLogged = false; - - public static void showInCallScreen(Context context, boolean showDialpad) { - if (hasReadPhoneStatePermission(context)) { - try { - getTelecomManager(context).showInCallScreen(showDialpad); - } catch (SecurityException e) { - // Just in case - Log.w(TAG, "TelecomManager.showInCallScreen called without permission."); - } - } - } - - public static void silenceRinger(Context context) { - if (hasModifyPhoneStatePermission(context)) { - try { - TelecomManagerCompat.silenceRinger(getTelecomManager(context)); - } catch (SecurityException e) { - // Just in case - Log.w(TAG, "TelecomManager.silenceRinger called without permission."); - } - } - } - - public static void cancelMissedCallsNotification(Context context) { - if (hasModifyPhoneStatePermission(context)) { - try { - getTelecomManager(context).cancelMissedCallsNotification(); - } catch (SecurityException e) { - Log.w(TAG, "TelecomManager.cancelMissedCalls called without permission."); - } - } - } - - public static Uri getAdnUriForPhoneAccount(Context context, PhoneAccountHandle handle) { - if (hasModifyPhoneStatePermission(context)) { - try { - return TelecomManagerCompat.getAdnUriForPhoneAccount( - getTelecomManager(context), handle); - } catch (SecurityException e) { - Log.w(TAG, "TelecomManager.getAdnUriForPhoneAccount called without permission."); - } - } - return null; - } - - public static boolean handleMmi(Context context, String dialString, - @Nullable PhoneAccountHandle handle) { - if (hasModifyPhoneStatePermission(context)) { - try { - if (handle == null) { - return getTelecomManager(context).handleMmi(dialString); - } else { - return getTelecomManager(context).handleMmi(dialString, handle); - } - } catch (SecurityException e) { - Log.w(TAG, "TelecomManager.handleMmi called without permission."); - } - } - return false; - } - - @Nullable - public static PhoneAccountHandle getDefaultOutgoingPhoneAccount(Context context, - String uriScheme) { - if (hasReadPhoneStatePermission(context)) { - return TelecomManagerCompat.getDefaultOutgoingPhoneAccount( - getTelecomManager(context), uriScheme); - } - return null; - } - - public static PhoneAccount getPhoneAccount(Context context, PhoneAccountHandle handle) { - return TelecomManagerCompat.getPhoneAccount(getTelecomManager(context), handle); - } - - public static List<PhoneAccountHandle> getCallCapablePhoneAccounts(Context context) { - if (hasReadPhoneStatePermission(context)) { - return TelecomManagerCompat.getCallCapablePhoneAccounts(getTelecomManager(context)); - } - return new ArrayList<>(); - } - - public static boolean isInCall(Context context) { - if (hasReadPhoneStatePermission(context)) { - return getTelecomManager(context).isInCall(); - } - return false; - } - - public static boolean isVoicemailNumber(Context context, PhoneAccountHandle accountHandle, - String number) { - if (hasReadPhoneStatePermission(context)) { - return TelecomManagerCompat.isVoiceMailNumber(getTelecomManager(context), - accountHandle, number); - } - return false; - } - - @Nullable - public static String getVoicemailNumber(Context context, PhoneAccountHandle accountHandle) { - if (hasReadPhoneStatePermission(context)) { - return TelecomManagerCompat.getVoiceMailNumber(getTelecomManager(context), - getTelephonyManager(context), accountHandle); - } - return null; - } - - /** - * Tries to place a call using the {@link TelecomManager}. - * - * @param activity a valid activity. - * @param intent the call intent. - * - * @return {@code true} if we successfully attempted to place the call, {@code false} if it - * failed due to a permission check. - */ - public static boolean placeCall(Activity activity, Intent intent) { - if (hasCallPhonePermission(activity)) { - TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent); - return true; - } - return false; - } - - public static Uri getCallLogUri(Context context) { - return hasReadWriteVoicemailPermissions(context) ? Calls.CONTENT_URI_WITH_VOICEMAIL - : Calls.CONTENT_URI; - } - - public static boolean hasReadWriteVoicemailPermissions(Context context) { - return isDefaultDialer(context) - || (hasPermission(context, Manifest.permission.READ_VOICEMAIL) - && hasPermission(context, Manifest.permission.WRITE_VOICEMAIL)); - } - - public static boolean hasModifyPhoneStatePermission(Context context) { - return isDefaultDialer(context) - || hasPermission(context, Manifest.permission.MODIFY_PHONE_STATE); - } - - public static boolean hasReadPhoneStatePermission(Context context) { - return isDefaultDialer(context) - || hasPermission(context, Manifest.permission.READ_PHONE_STATE); - } - - public static boolean hasCallPhonePermission(Context context) { - return isDefaultDialer(context) - || hasPermission(context, Manifest.permission.CALL_PHONE); - } - - private static boolean hasPermission(Context context, String permission) { - return ContextCompat.checkSelfPermission(context, permission) - == PackageManager.PERMISSION_GRANTED; - } - - public static boolean isDefaultDialer(Context context) { - final boolean result = TextUtils.equals(context.getPackageName(), - TelecomManagerCompat.getDefaultDialerPackage(getTelecomManager(context))); - if (result) { - sWarningLogged = false; - } else { - if (!sWarningLogged) { - // Log only once to prevent spam. - Log.w(TAG, "Dialer is not currently set to be default dialer"); - sWarningLogged = true; - } - } - return result; - } - - private static TelecomManager getTelecomManager(Context context) { - return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); - } - - private static TelephonyManager getTelephonyManager(Context context) { - return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - } -} diff --git a/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java b/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java deleted file mode 100644 index 80a0368bd..000000000 --- a/src/com/android/dialer/voicemail/VisualVoicemailEnabledChecker.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.android.dialer.voicemail; - - -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; - -import com.android.dialer.calllog.CallLogQueryHandler; - -/** - * Helper class to check whether visual voicemail is enabled. - * - * Call isVisualVoicemailEnabled() to retrieve the result. - * - * The result is cached and saved in a SharedPreferences, stored as a boolean in - * PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER. Every time a new instance is created, it will try to - * restore the cached result from the SharedPreferences. - * - * Call asyncUpdate() to make a CallLogQuery to check the actual status. This is a async call so - * isVisualVoicemailEnabled() will not be affected immediately. - * - * If the status has changed as a result of asyncUpdate(), - * Callback.onVisualVoicemailEnabledStatusChanged() will be called with the new value. - */ -public class VisualVoicemailEnabledChecker implements CallLogQueryHandler.Listener { - - public static final String PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER = - "has_active_voicemail_provider"; - private SharedPreferences mPrefs; - private boolean mHasActiveVoicemailProvider; - private CallLogQueryHandler mCallLogQueryHandler; - private VoicemailStatusHelper mVoicemailStatusHelper; - private Context mContext; - - public interface Callback { - - /** - * Callback to notify enabled status has changed to the @param newValue - */ - void onVisualVoicemailEnabledStatusChanged(boolean newValue); - } - - private Callback mCallback; - - public VisualVoicemailEnabledChecker(Context context, @Nullable Callback callback) { - mContext = context; - mCallback = callback; - mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); - mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); - mHasActiveVoicemailProvider = mPrefs.getBoolean(PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, - false); - } - - /** - * @return whether visual voicemail is enabled. Result is cached, call asyncUpdate() to - * update the result. - */ - public boolean isVisualVoicemailEnabled() { - return mHasActiveVoicemailProvider; - } - - /** - * Perform an async query into the system to check the status of visual voicemail. - * If the status has changed, Callback.onVisualVoicemailEnabledStatusChanged() will be called. - */ - public void asyncUpdate() { - mCallLogQueryHandler = - new CallLogQueryHandler(mContext, mContext.getContentResolver(), this); - mCallLogQueryHandler.fetchVoicemailStatus(); - } - - @Override - public void onVoicemailStatusFetched(Cursor statusCursor) { - boolean hasActiveVoicemailProvider = - mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; - if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) { - mHasActiveVoicemailProvider = hasActiveVoicemailProvider; - mPrefs.edit().putBoolean(PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, - mHasActiveVoicemailProvider); - if (mCallback != null) { - mCallback.onVisualVoicemailEnabledStatusChanged(mHasActiveVoicemailProvider); - } - } - } - - @Override - public void onVoicemailUnreadCountFetched(Cursor cursor) { - // Do nothing - } - - @Override - public void onMissedCallsUnreadCountFetched(Cursor cursor) { - // Do nothing - } - - @Override - public boolean onCallsFetched(Cursor combinedCursor) { - // Do nothing - return false; - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailArchiveActivity.java b/src/com/android/dialer/voicemail/VoicemailArchiveActivity.java deleted file mode 100644 index 16b947cd3..000000000 --- a/src/com/android/dialer/voicemail/VoicemailArchiveActivity.java +++ /dev/null @@ -1,160 +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.voicemail; - -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.MenuItem; -import android.view.View; - -import com.android.contacts.common.GeoUtil; -import com.android.dialer.DialtactsActivity; -import com.android.dialer.R; -import com.android.dialer.TransactionSafeActivity; -import com.android.dialer.calllog.CallLogAdapter; -import com.android.dialer.calllog.CallLogQueryHandler; -import com.android.dialer.calllog.ContactInfoHelper; -import com.android.dialer.widget.EmptyContentView; -import com.android.dialerbind.ObjectFactory; - -/** - * This activity manages all the voicemails archived by the user. - */ -public class VoicemailArchiveActivity extends TransactionSafeActivity - implements CallLogAdapter.CallFetcher, CallLogQueryHandler.Listener { - private RecyclerView mRecyclerView; - private LinearLayoutManager mLayoutManager; - private EmptyContentView mEmptyListView; - private CallLogAdapter mAdapter; - private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; - private CallLogQueryHandler mCallLogQueryHandler; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (!isSafeToCommitTransactions()) { - return true; - } - - switch (item.getItemId()) { - case android.R.id.home: - Intent intent = new Intent(this, DialtactsActivity.class); - // Clears any activities between VoicemailArchiveActivity and DialtactsActivity - // on the activity stack and reuses the existing instance of DialtactsActivity - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.call_log_fragment); - - // Make window opaque to reduce overdraw - getWindow().setBackgroundDrawable(null); - - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setElevation(0); - - mCallLogQueryHandler = new CallLogQueryHandler(this, getContentResolver(), this); - mVoicemailPlaybackPresenter = VoicemailArchivePlaybackPresenter - .getInstance(this, savedInstanceState); - - mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); - mRecyclerView.setHasFixedSize(true); - mLayoutManager = new LinearLayoutManager(this); - mRecyclerView.setLayoutManager(mLayoutManager); - mEmptyListView = (EmptyContentView) findViewById(R.id.empty_list_view); - mEmptyListView.setDescription(R.string.voicemail_archive_empty); - mEmptyListView.setImage(R.drawable.empty_call_log); - - mAdapter = ObjectFactory.newCallLogAdapter( - this, - this, - new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)), - mVoicemailPlaybackPresenter, - CallLogAdapter.ACTIVITY_TYPE_ARCHIVE); - mRecyclerView.setAdapter(mAdapter); - fetchCalls(); - } - - @Override - protected void onPause() { - mVoicemailPlaybackPresenter.onPause(); - mAdapter.onPause(); - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - mAdapter.onResume(); - mVoicemailPlaybackPresenter.onResume(); - } - - @Override - public void onDestroy() { - mVoicemailPlaybackPresenter.onDestroy(); - mAdapter.changeCursor(null); - super.onDestroy(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mVoicemailPlaybackPresenter.onSaveInstanceState(outState); - } - - @Override - public void fetchCalls() { - mCallLogQueryHandler.fetchVoicemailArchive(); - } - - @Override - public void onVoicemailStatusFetched(Cursor statusCursor) { - // Do nothing - } - - @Override - public void onVoicemailUnreadCountFetched(Cursor cursor) { - // Do nothing - } - - @Override - public void onMissedCallsUnreadCountFetched(Cursor cursor) { - // Do nothing - } - - @Override - public boolean onCallsFetched(Cursor cursor) { - mAdapter.changeCursorVoicemail(cursor); - boolean showListView = cursor != null && cursor.getCount() > 0; - mRecyclerView.setVisibility(showListView ? View.VISIBLE : View.GONE); - mEmptyListView.setVisibility(!showListView ? View.VISIBLE : View.GONE); - return true; - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java deleted file mode 100644 index 5f73d1689..000000000 --- a/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java +++ /dev/null @@ -1,90 +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.voicemail; - -import android.app.Activity; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; -import com.android.dialer.calllog.CallLogAsyncTaskUtil; -import com.android.dialer.database.VoicemailArchiveContract; -import java.io.FileNotFoundException; -import java.util.concurrent.TimeUnit; - -/** - * Similar to the {@link VoicemailPlaybackPresenter}, but for the archive voicemail tab. It checks - * whether the voicemail file exists locally before preparing it. - */ -public class VoicemailArchivePlaybackPresenter extends VoicemailPlaybackPresenter { - private static final String TAG = "VMPlaybackPresenter"; - private static VoicemailPlaybackPresenter sInstance; - - public VoicemailArchivePlaybackPresenter(Activity activity) { - super(activity); - } - - public static VoicemailPlaybackPresenter getInstance( - Activity activity, Bundle savedInstanceState) { - if (sInstance == null) { - sInstance = new VoicemailArchivePlaybackPresenter(activity); - } - - sInstance.init(activity, savedInstanceState); - return sInstance; - } - - @Override - protected void checkForContent(final OnContentCheckedListener callback) { - mAsyncTaskExecutor.submit(Tasks.CHECK_FOR_CONTENT, new AsyncTask<Void, Void, Boolean>() { - @Override - public Boolean doInBackground(Void... params) { - try { - // Check if the _data column of the archived voicemail is valid - if (mVoicemailUri != null) { - mContext.getContentResolver().openInputStream(mVoicemailUri); - return true; - } - } catch (FileNotFoundException e) { - Log.d(TAG, "Voicemail file not found for " + mVoicemailUri); - } - return false; - } - - @Override - public void onPostExecute(Boolean hasContent) { - callback.onContentChecked(hasContent); - } - }); - } - - @Override - protected void startArchiveVoicemailTask(final Uri voicemailUri, final boolean archivedByUser) { - // If a user wants to share an archived voicemail, no need for archiving, just go straight - // to share intent. - if (!archivedByUser) { - sendShareIntent(voicemailUri); - } - } - - @Override - protected boolean requestContent(int code) { - handleError(new FileNotFoundException("Voicemail archive file does not exist")); - return false; // No way for archive tab to request content - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java b/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java deleted file mode 100644 index 7abf9a72c..000000000 --- a/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java +++ /dev/null @@ -1,346 +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.voicemail; - -import com.android.contacts.common.testing.NeededForTesting; -import com.android.dialer.calllog.CallLogQuery; -import com.android.dialer.database.VoicemailArchiveContract; -import com.android.dialer.util.AsyncTaskExecutor; -import com.android.dialer.util.AsyncTaskExecutors; -import com.google.common.base.Preconditions; -import com.google.common.io.ByteStreams; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.CallLog; -import android.provider.VoicemailContract; -import android.util.Log; -import com.android.common.io.MoreCloseables; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import javax.annotation.Nullable; - -/** - * Class containing asynchronous tasks for voicemails. - */ -@NeededForTesting -public class VoicemailAsyncTaskUtil { - private static final String TAG = "VoicemailAsyncTaskUtil"; - - /** The enumeration of {@link AsyncTask} objects we use in this class. */ - public enum Tasks { - GET_VOICEMAIL_FILE_PATH, - SET_VOICEMAIL_ARCHIVE_STATUS, - ARCHIVE_VOICEMAIL_CONTENT - } - - @NeededForTesting - public interface OnArchiveVoicemailListener { - /** - * Called after the voicemail has been archived. - * - * @param archivedVoicemailUri the URI of the archived voicemail - */ - void onArchiveVoicemail(@Nullable Uri archivedVoicemailUri); - } - - @NeededForTesting - public interface OnSetVoicemailArchiveStatusListener { - /** - * Called after the voicemail archived_by_user column is updated. - * - * @param success whether the update was successful or not - */ - void onSetVoicemailArchiveStatus(boolean success); - } - - @NeededForTesting - public interface OnGetArchivedVoicemailFilePathListener { - /** - * Called after the voicemail file path is obtained. - * - * @param filePath the file path of the archived voicemail - */ - void onGetArchivedVoicemailFilePath(@Nullable String filePath); - } - - private final ContentResolver mResolver; - private final AsyncTaskExecutor mAsyncTaskExecutor; - - @NeededForTesting - public VoicemailAsyncTaskUtil(ContentResolver contentResolver) { - mResolver = Preconditions.checkNotNull(contentResolver); - mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); - } - - /** - * Returns the archived voicemail file path. - */ - @NeededForTesting - public void getVoicemailFilePath( - final OnGetArchivedVoicemailFilePathListener listener, - final Uri voicemailUri) { - Preconditions.checkNotNull(listener); - Preconditions.checkNotNull(voicemailUri); - mAsyncTaskExecutor.submit(Tasks.GET_VOICEMAIL_FILE_PATH, - new AsyncTask<Void, Void, String>() { - @Nullable - @Override - protected String doInBackground(Void... params) { - try (Cursor cursor = mResolver.query(voicemailUri, - new String[]{VoicemailArchiveContract.VoicemailArchive._DATA}, - null, null, null)) { - if (hasContent(cursor)) { - return cursor.getString(cursor.getColumnIndex( - VoicemailArchiveContract.VoicemailArchive._DATA)); - } - } - return null; - } - - @Override - protected void onPostExecute(String filePath) { - listener.onGetArchivedVoicemailFilePath(filePath); - } - }); - } - - /** - * Updates the archived_by_user flag of the archived voicemail. - */ - @NeededForTesting - public void setVoicemailArchiveStatus( - final OnSetVoicemailArchiveStatusListener listener, - final Uri voicemailUri, - final boolean archivedByUser) { - Preconditions.checkNotNull(listener); - Preconditions.checkNotNull(voicemailUri); - mAsyncTaskExecutor.submit(Tasks.SET_VOICEMAIL_ARCHIVE_STATUS, - new AsyncTask<Void, Void, Boolean>() { - @Override - protected Boolean doInBackground(Void... params) { - ContentValues values = new ContentValues(1); - values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, - archivedByUser); - return mResolver.update(voicemailUri, values, null, null) > 0; - } - - @Override - protected void onPostExecute(Boolean success) { - listener.onSetVoicemailArchiveStatus(success); - } - }); - } - - /** - * Checks if a voicemail has already been archived, if so, return the previously archived URI. - * Otherwise, copy the voicemail information to the local dialer database. If archive was - * successful, archived voicemail URI is returned to listener, otherwise null. - */ - @NeededForTesting - public void archiveVoicemailContent( - final OnArchiveVoicemailListener listener, - final Uri voicemailUri) { - Preconditions.checkNotNull(listener); - Preconditions.checkNotNull(voicemailUri); - mAsyncTaskExecutor.submit(Tasks.ARCHIVE_VOICEMAIL_CONTENT, - new AsyncTask<Void, Void, Uri>() { - @Nullable - @Override - protected Uri doInBackground(Void... params) { - Uri archivedVoicemailUri = getArchivedVoicemailUri(voicemailUri); - - // If previously archived, return uri, otherwise archive everything. - if (archivedVoicemailUri != null) { - return archivedVoicemailUri; - } - - // Combine call log and voicemail content info. - ContentValues values = getVoicemailContentValues(voicemailUri); - if (values == null) { - return null; - } - - Uri insertedVoicemailUri = mResolver.insert( - VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, values); - if (insertedVoicemailUri == null) { - return null; - } - - // Copy voicemail content to a new file. - boolean copiedFile = false; - try (InputStream inputStream = mResolver.openInputStream(voicemailUri); - OutputStream outputStream = - mResolver.openOutputStream(insertedVoicemailUri)) { - if (inputStream != null && outputStream != null) { - ByteStreams.copy(inputStream, outputStream); - copiedFile = true; - return insertedVoicemailUri; - } - } catch (IOException e) { - Log.w(TAG, "Failed to copy voicemail content to new file: " - + e.toString()); - } finally { - if (!copiedFile) { - // Roll back insert if the voicemail content was not copied. - mResolver.delete(insertedVoicemailUri, null, null); - } - } - return null; - } - - @Override - protected void onPostExecute(Uri archivedVoicemailUri) { - listener.onArchiveVoicemail(archivedVoicemailUri); - } - }); - } - - /** - * Helper method to get the archived URI of a voicemail. - * - * @param voicemailUri a {@link android.provider.VoicemailContract.Voicemails#CONTENT_URI} URI. - * @return the URI of the archived voicemail or {@code null} - */ - @Nullable - private Uri getArchivedVoicemailUri(Uri voicemailUri) { - try (Cursor cursor = getArchiveExistsCursor(voicemailUri)) { - if (hasContent(cursor)) { - return VoicemailArchiveContract.VoicemailArchive - .buildWithId(cursor.getInt(cursor.getColumnIndex( - VoicemailArchiveContract.VoicemailArchive._ID))); - } - } - return null; - } - - /** - * Helper method to make a copy of all the values needed to display a voicemail. - * - * @param voicemailUri a {@link VoicemailContract.Voicemails#CONTENT_URI} URI. - * @return the combined call log and voicemail values for the given URI, or {@code null} - */ - @Nullable - private ContentValues getVoicemailContentValues(Uri voicemailUri) { - try (Cursor callLogInfo = getCallLogInfoCursor(voicemailUri); - Cursor contentInfo = getContentInfoCursor(voicemailUri)) { - - if (hasContent(callLogInfo) && hasContent(contentInfo)) { - // Create values to insert into database. - ContentValues values = new ContentValues(); - - // Insert voicemail call log info. - values.put(VoicemailArchiveContract.VoicemailArchive.COUNTRY_ISO, - callLogInfo.getString(CallLogQuery.COUNTRY_ISO)); - values.put(VoicemailArchiveContract.VoicemailArchive.GEOCODED_LOCATION, - callLogInfo.getString(CallLogQuery.GEOCODED_LOCATION)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NAME, - callLogInfo.getString(CallLogQuery.CACHED_NAME)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_TYPE, - callLogInfo.getInt(CallLogQuery.CACHED_NUMBER_TYPE)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_LABEL, - callLogInfo.getString(CallLogQuery.CACHED_NUMBER_LABEL)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_LOOKUP_URI, - callLogInfo.getString(CallLogQuery.CACHED_LOOKUP_URI)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_MATCHED_NUMBER, - callLogInfo.getString(CallLogQuery.CACHED_MATCHED_NUMBER)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NORMALIZED_NUMBER, - callLogInfo.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_FORMATTED_NUMBER, - callLogInfo.getString(CallLogQuery.CACHED_FORMATTED_NUMBER)); - values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER_PRESENTATION, - callLogInfo.getInt(CallLogQuery.NUMBER_PRESENTATION)); - values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_COMPONENT_NAME, - callLogInfo.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME)); - values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_ID, - callLogInfo.getString(CallLogQuery.ACCOUNT_ID)); - values.put(VoicemailArchiveContract.VoicemailArchive.FEATURES, - callLogInfo.getInt(CallLogQuery.FEATURES)); - values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_PHOTO_URI, - callLogInfo.getString(CallLogQuery.CACHED_PHOTO_URI)); - - // Insert voicemail content info. - values.put(VoicemailArchiveContract.VoicemailArchive.SERVER_ID, - contentInfo.getInt(contentInfo.getColumnIndex( - VoicemailContract.Voicemails._ID))); - values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER, - contentInfo.getString(contentInfo.getColumnIndex( - VoicemailContract.Voicemails.NUMBER))); - values.put(VoicemailArchiveContract.VoicemailArchive.DATE, - contentInfo.getLong(contentInfo.getColumnIndex( - VoicemailContract.Voicemails.DATE))); - values.put(VoicemailArchiveContract.VoicemailArchive.DURATION, - contentInfo.getLong(contentInfo.getColumnIndex( - VoicemailContract.Voicemails.DURATION))); - values.put(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE, - contentInfo.getString(contentInfo.getColumnIndex( - VoicemailContract.Voicemails.MIME_TYPE))); - values.put(VoicemailArchiveContract.VoicemailArchive.TRANSCRIPTION, - contentInfo.getString(contentInfo.getColumnIndex( - VoicemailContract.Voicemails.TRANSCRIPTION))); - - // Achived is false by default because it is updated after insertion. - values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, false); - - return values; - } - } - return null; - } - - private boolean hasContent(@Nullable Cursor cursor) { - return cursor != null && cursor.moveToFirst(); - } - - @Nullable - private Cursor getCallLogInfoCursor(Uri voicemailUri) { - return mResolver.query( - ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, - ContentUris.parseId(voicemailUri)), - CallLogQuery._PROJECTION, null, null, null); - } - - @Nullable - private Cursor getContentInfoCursor(Uri voicemailUri) { - return mResolver.query(voicemailUri, - new String[] { - VoicemailContract.Voicemails._ID, - VoicemailContract.Voicemails.NUMBER, - VoicemailContract.Voicemails.DATE, - VoicemailContract.Voicemails.DURATION, - VoicemailContract.Voicemails.MIME_TYPE, - VoicemailContract.Voicemails.TRANSCRIPTION, - }, null, null, null); - } - - @Nullable - private Cursor getArchiveExistsCursor(Uri voicemailUri) { - return mResolver.query(VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, - new String[] {VoicemailArchiveContract.VoicemailArchive._ID}, - VoicemailArchiveContract.VoicemailArchive.SERVER_ID + "=" - + ContentUris.parseId(voicemailUri), - null, - null); - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailAudioManager.java b/src/com/android/dialer/voicemail/VoicemailAudioManager.java deleted file mode 100644 index fe6cf5f45..000000000 --- a/src/com/android/dialer/voicemail/VoicemailAudioManager.java +++ /dev/null @@ -1,200 +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.voicemail; - -import android.content.Context; -import android.media.AudioManager; -import android.media.AudioManager.OnAudioFocusChangeListener; -import android.telecom.CallAudioState; -import android.util.Log; - -import java.util.concurrent.RejectedExecutionException; - -/** - * This class manages all audio changes for voicemail playback. - */ -final class VoicemailAudioManager implements OnAudioFocusChangeListener, - WiredHeadsetManager.Listener { - private static final String TAG = VoicemailAudioManager.class.getSimpleName(); - - public static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL; - - private AudioManager mAudioManager; - private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; - private WiredHeadsetManager mWiredHeadsetManager; - private boolean mWasSpeakerOn; - private CallAudioState mCallAudioState; - - public VoicemailAudioManager(Context context, - VoicemailPlaybackPresenter voicemailPlaybackPresenter) { - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; - mWiredHeadsetManager = new WiredHeadsetManager(context); - mWiredHeadsetManager.setListener(this); - - mCallAudioState = getInitialAudioState(); - Log.i(TAG, "Initial audioState = " + mCallAudioState); - } - - public void requestAudioFocus() { - int result = mAudioManager.requestAudioFocus( - this, - PLAYBACK_STREAM, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); - if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - throw new RejectedExecutionException("Could not capture audio focus."); - } - } - - public void abandonAudioFocus() { - mAudioManager.abandonAudioFocus(this); - } - - @Override - public void onAudioFocusChange(int focusChange) { - Log.d(TAG, "onAudioFocusChange: focusChange=" + focusChange); - mVoicemailPlaybackPresenter.onAudioFocusChange(focusChange == AudioManager.AUDIOFOCUS_GAIN); - } - - @Override - public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { - Log.i(TAG, "wired headset was plugged in changed: " + oldIsPluggedIn - + " -> "+ newIsPluggedIn); - - if (oldIsPluggedIn == newIsPluggedIn) { - return; - } - - int newRoute = mCallAudioState.getRoute(); // start out with existing route - if (newIsPluggedIn) { - newRoute = CallAudioState.ROUTE_WIRED_HEADSET; - } else { - if (mWasSpeakerOn) { - newRoute = CallAudioState.ROUTE_SPEAKER; - } else { - newRoute = CallAudioState.ROUTE_EARPIECE; - } - } - - mVoicemailPlaybackPresenter.setSpeakerphoneOn(newRoute == CallAudioState.ROUTE_SPEAKER); - - // We need to call this every time even if we do not change the route because the supported - // routes changed either to include or not include WIRED_HEADSET. - setSystemAudioState( - new CallAudioState(false /* muted */, newRoute, calculateSupportedRoutes())); - } - - public void setSpeakerphoneOn(boolean on) { - setAudioRoute(on ? CallAudioState.ROUTE_SPEAKER : CallAudioState.ROUTE_WIRED_OR_EARPIECE); - } - - public boolean isWiredHeadsetPluggedIn() { - return mWiredHeadsetManager.isPluggedIn(); - } - - public void registerReceivers() { - // Receivers is plural because we expect to add bluetooth support. - mWiredHeadsetManager.registerReceiver(); - } - - public void unregisterReceivers() { - mWiredHeadsetManager.unregisterReceiver(); - } - - /** - * Change the audio route, for example from earpiece to speakerphone. - * - * @param route The new audio route to use. See {@link CallAudioState}. - */ - void setAudioRoute(int route) { - Log.v(TAG, "setAudioRoute, route: " + CallAudioState.audioRouteToString(route)); - - // Change ROUTE_WIRED_OR_EARPIECE to a single entry. - int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask()); - - // If route is unsupported, do nothing. - if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) { - Log.w(TAG, "Asking to set to a route that is unsupported: " + newRoute); - return; - } - - if (mCallAudioState.getRoute() != newRoute) { - // Remember the new speaker state so it can be restored when the user plugs and unplugs - // a headset. - mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER; - setSystemAudioState(new CallAudioState(false /* muted */, newRoute, - mCallAudioState.getSupportedRouteMask())); - } - } - - private CallAudioState getInitialAudioState() { - int supportedRouteMask = calculateSupportedRoutes(); - int route = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, - supportedRouteMask); - return new CallAudioState(false /* muted */, route, supportedRouteMask); - } - - private int calculateSupportedRoutes() { - int routeMask = CallAudioState.ROUTE_SPEAKER; - if (mWiredHeadsetManager.isPluggedIn()) { - routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; - } else { - routeMask |= CallAudioState.ROUTE_EARPIECE; - } - return routeMask; - } - - private int selectWiredOrEarpiece(int route, int supportedRouteMask) { - // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of - // ROUTE_WIRED_OR_EARPIECE so that callers don't have to make a call to check which is - // supported before calling setAudioRoute. - if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) { - route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask; - if (route == 0) { - Log.wtf(TAG, "One of wired headset or earpiece should always be valid."); - // assume earpiece in this case. - route = CallAudioState.ROUTE_EARPIECE; - } - } - return route; - } - - private void setSystemAudioState(CallAudioState callAudioState) { - CallAudioState oldAudioState = mCallAudioState; - mCallAudioState = callAudioState; - - Log.i(TAG, "setSystemAudioState: changing from " + oldAudioState + " to " - + mCallAudioState); - - // Audio route. - if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { - turnOnSpeaker(true); - } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE || - mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) { - // Just handle turning off the speaker, the system will handle switching between wired - // headset and earpiece. - turnOnSpeaker(false); - } - } - - private void turnOnSpeaker(boolean on) { - if (mAudioManager.isSpeakerphoneOn() != on) { - Log.i(TAG, "turning speaker phone on: " + on); - mAudioManager.setSpeakerphoneOn(on); - } - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java deleted file mode 100644 index 7d6fe78d1..000000000 --- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java +++ /dev/null @@ -1,638 +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.voicemail; - -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.util.AttributeSet; -import android.support.design.widget.Snackbar; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.Space; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.common.io.MoreCloseables; -import com.android.dialer.PhoneCallDetails; -import com.android.dialer.R; -import com.android.dialer.calllog.CallLogAsyncTaskUtil; - -import com.android.dialer.database.VoicemailArchiveContract; -import com.android.dialer.database.VoicemailArchiveContract.VoicemailArchive; -import com.android.dialer.util.AsyncTaskExecutor; -import com.android.dialer.util.AsyncTaskExecutors; -import com.android.dialerbind.ObjectFactory; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledExecutorService; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; -import javax.annotation.concurrent.NotThreadSafe; -import javax.annotation.concurrent.ThreadSafe; - -/** - * Displays and plays a single voicemail. See {@link VoicemailPlaybackPresenter} for - * details on the voicemail playback implementation. - * - * This class is not thread-safe, it is thread-confined. All calls to all public - * methods on this class are expected to come from the main ui thread. - */ -@NotThreadSafe -public class VoicemailPlaybackLayout extends LinearLayout - implements VoicemailPlaybackPresenter.PlaybackView, - CallLogAsyncTaskUtil.CallLogAsyncTaskListener { - private static final String TAG = VoicemailPlaybackLayout.class.getSimpleName(); - private static final int VOICEMAIL_DELETE_DELAY_MS = 3000; - private static final int VOICEMAIL_ARCHIVE_DELAY_MS = 3000; - - /** The enumeration of {@link AsyncTask} objects we use in this class. */ - public enum Tasks { - QUERY_ARCHIVED_STATUS - } - - /** - * Controls the animation of the playback slider. - */ - @ThreadSafe - private final class PositionUpdater implements Runnable { - - /** Update rate for the slider, 30fps. */ - private static final int SLIDER_UPDATE_PERIOD_MILLIS = 1000 / 30; - - private int mDurationMs; - private final ScheduledExecutorService mExecutorService; - private final Object mLock = new Object(); - @GuardedBy("mLock") private ScheduledFuture<?> mScheduledFuture; - - private Runnable mUpdateClipPositionRunnable = new Runnable() { - @Override - public void run() { - int currentPositionMs = 0; - synchronized (mLock) { - if (mScheduledFuture == null || mPresenter == null) { - // This task has been canceled. Just stop now. - return; - } - currentPositionMs = mPresenter.getMediaPlayerPosition(); - } - setClipPosition(currentPositionMs, mDurationMs); - } - }; - - public PositionUpdater(int durationMs, ScheduledExecutorService executorService) { - mDurationMs = durationMs; - mExecutorService = executorService; - } - - @Override - public void run() { - post(mUpdateClipPositionRunnable); - } - - public void startUpdating() { - synchronized (mLock) { - cancelPendingRunnables(); - mScheduledFuture = mExecutorService.scheduleAtFixedRate( - this, 0, SLIDER_UPDATE_PERIOD_MILLIS, TimeUnit.MILLISECONDS); - } - } - - public void stopUpdating() { - synchronized (mLock) { - cancelPendingRunnables(); - } - } - - @GuardedBy("mLock") - private void cancelPendingRunnables() { - if (mScheduledFuture != null) { - mScheduledFuture.cancel(true); - mScheduledFuture = null; - } - removeCallbacks(mUpdateClipPositionRunnable); - } - } - - /** - * Handle state changes when the user manipulates the seek bar. - */ - private final OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() { - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - if (mPresenter != null) { - mPresenter.pausePlaybackForSeeking(); - } - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (mPresenter != null) { - mPresenter.resumePlaybackAfterSeeking(seekBar.getProgress()); - } - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - setClipPosition(progress, seekBar.getMax()); - // Update the seek position if user manually changed it. This makes sure position gets - // updated when user use volume button to seek playback in talkback mode. - if (fromUser) { - mPresenter.seek(progress); - } - } - }; - - /** - * Click listener to toggle speakerphone. - */ - private final View.OnClickListener mSpeakerphoneListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPresenter != null) { - mPresenter.toggleSpeakerphone(); - } - } - }; - - /** - * Click listener to play or pause voicemail playback. - */ - private final View.OnClickListener mStartStopButtonListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mPresenter == null) { - return; - } - - if (mIsPlaying) { - mPresenter.pausePlayback(); - } else { - mPresenter.resumePlayback(); - } - } - }; - - private final View.OnClickListener mDeleteButtonListener = new View.OnClickListener() { - @Override - public void onClick(View view ) { - if (mPresenter == null) { - return; - } - mPresenter.pausePlayback(); - mPresenter.onVoicemailDeleted(); - - final Uri deleteUri = mVoicemailUri; - final Runnable deleteCallback = new Runnable() { - @Override - public void run() { - if (Objects.equals(deleteUri, mVoicemailUri)) { - CallLogAsyncTaskUtil.deleteVoicemail(mContext, deleteUri, - VoicemailPlaybackLayout.this); - } - } - }; - - final Handler handler = new Handler(); - // Add a little buffer time in case the user clicked "undo" at the end of the delay - // window. - handler.postDelayed(deleteCallback, VOICEMAIL_DELETE_DELAY_MS + 50); - - Snackbar.make(VoicemailPlaybackLayout.this, R.string.snackbar_voicemail_deleted, - Snackbar.LENGTH_LONG) - .setDuration(VOICEMAIL_DELETE_DELAY_MS) - .setAction(R.string.snackbar_voicemail_deleted_undo, - new View.OnClickListener() { - @Override - public void onClick(View view) { - mPresenter.onVoicemailDeleteUndo(); - handler.removeCallbacks(deleteCallback); - } - }) - .setActionTextColor( - mContext.getResources().getColor( - R.color.dialer_snackbar_action_text_color)) - .show(); - } - }; - - private final View.OnClickListener mArchiveButtonListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPresenter == null || isArchiving(mVoicemailUri)) { - return; - } - mIsArchiving.add(mVoicemailUri); - mPresenter.pausePlayback(); - updateArchiveUI(mVoicemailUri); - disableUiElements(); - mPresenter.archiveContent(mVoicemailUri, true); - } - }; - - private final View.OnClickListener mShareButtonListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPresenter == null || isArchiving(mVoicemailUri)) { - return; - } - disableUiElements(); - mPresenter.archiveContent(mVoicemailUri, false); - } - }; - - private Context mContext; - private VoicemailPlaybackPresenter mPresenter; - private Uri mVoicemailUri; - private final AsyncTaskExecutor mAsyncTaskExecutor = - AsyncTaskExecutors.createAsyncTaskExecutor(); - private boolean mIsPlaying = false; - /** - * Keeps track of which voicemails are currently being archived in order to update the voicemail - * card UI every time a user opens a new card. - */ - private static final ArrayList<Uri> mIsArchiving = new ArrayList<>(); - - private SeekBar mPlaybackSeek; - private ImageButton mStartStopButton; - private ImageButton mPlaybackSpeakerphone; - private ImageButton mDeleteButton; - private ImageButton mArchiveButton; - private ImageButton mShareButton; - - private Space mArchiveSpace; - private Space mShareSpace; - - private TextView mStateText; - private TextView mPositionText; - private TextView mTotalDurationText; - - private PositionUpdater mPositionUpdater; - private Drawable mVoicemailSeekHandleEnabled; - private Drawable mVoicemailSeekHandleDisabled; - - public VoicemailPlaybackLayout(Context context) { - this(context, null); - } - - public VoicemailPlaybackLayout(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.voicemail_playback_layout, this); - } - - @Override - public void setPresenter(VoicemailPlaybackPresenter presenter, Uri voicemailUri) { - mPresenter = presenter; - mVoicemailUri = voicemailUri; - if (ObjectFactory.isVoicemailArchiveEnabled(mContext)) { - updateArchiveUI(mVoicemailUri); - updateArchiveButton(mVoicemailUri); - } - - if (ObjectFactory.isVoicemailShareEnabled(mContext)) { - // Show share button and space before it - mShareSpace.setVisibility(View.VISIBLE); - mShareButton.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mPlaybackSeek = (SeekBar) findViewById(R.id.playback_seek); - mStartStopButton = (ImageButton) findViewById(R.id.playback_start_stop); - mPlaybackSpeakerphone = (ImageButton) findViewById(R.id.playback_speakerphone); - mDeleteButton = (ImageButton) findViewById(R.id.delete_voicemail); - mArchiveButton =(ImageButton) findViewById(R.id.archive_voicemail); - mShareButton = (ImageButton) findViewById(R.id.share_voicemail); - - mArchiveSpace = (Space) findViewById(R.id.space_before_archive_voicemail); - mShareSpace = (Space) findViewById(R.id.space_before_share_voicemail); - - mStateText = (TextView) findViewById(R.id.playback_state_text); - mPositionText = (TextView) findViewById(R.id.playback_position_text); - mTotalDurationText = (TextView) findViewById(R.id.total_duration_text); - - mPlaybackSeek.setOnSeekBarChangeListener(mSeekBarChangeListener); - mStartStopButton.setOnClickListener(mStartStopButtonListener); - mPlaybackSpeakerphone.setOnClickListener(mSpeakerphoneListener); - mDeleteButton.setOnClickListener(mDeleteButtonListener); - mArchiveButton.setOnClickListener(mArchiveButtonListener); - mShareButton.setOnClickListener(mShareButtonListener); - - mPositionText.setText(formatAsMinutesAndSeconds(0)); - mTotalDurationText.setText(formatAsMinutesAndSeconds(0)); - - mVoicemailSeekHandleEnabled = getResources().getDrawable( - R.drawable.ic_voicemail_seek_handle, mContext.getTheme()); - mVoicemailSeekHandleDisabled = getResources().getDrawable( - R.drawable.ic_voicemail_seek_handle_disabled, mContext.getTheme()); - } - - @Override - public void onPlaybackStarted(int duration, ScheduledExecutorService executorService) { - mIsPlaying = true; - - mStartStopButton.setImageResource(R.drawable.ic_pause); - - if (mPositionUpdater != null) { - mPositionUpdater.stopUpdating(); - mPositionUpdater = null; - } - mPositionUpdater = new PositionUpdater(duration, executorService); - mPositionUpdater.startUpdating(); - } - - @Override - public void onPlaybackStopped() { - mIsPlaying = false; - - mStartStopButton.setImageResource(R.drawable.ic_play_arrow); - - if (mPositionUpdater != null) { - mPositionUpdater.stopUpdating(); - mPositionUpdater = null; - } - } - - @Override - public void onPlaybackError() { - if (mPositionUpdater != null) { - mPositionUpdater.stopUpdating(); - } - - disableUiElements(); - mStateText.setText(getString(R.string.voicemail_playback_error)); - } - - @Override - public void onSpeakerphoneOn(boolean on) { - if (on) { - mPlaybackSpeakerphone.setImageResource(R.drawable.ic_volume_up_24dp); - // Speaker is now on, tapping button will turn it off. - mPlaybackSpeakerphone.setContentDescription( - mContext.getString(R.string.voicemail_speaker_off)); - } else { - mPlaybackSpeakerphone.setImageResource(R.drawable.ic_volume_down_24dp); - // Speaker is now off, tapping button will turn it on. - mPlaybackSpeakerphone.setContentDescription( - mContext.getString(R.string.voicemail_speaker_on)); - } - } - - @Override - public void setClipPosition(int positionMs, int durationMs) { - int seekBarPositionMs = Math.max(0, positionMs); - int seekBarMax = Math.max(seekBarPositionMs, durationMs); - if (mPlaybackSeek.getMax() != seekBarMax) { - mPlaybackSeek.setMax(seekBarMax); - } - - mPlaybackSeek.setProgress(seekBarPositionMs); - - mPositionText.setText(formatAsMinutesAndSeconds(seekBarPositionMs)); - mTotalDurationText.setText(formatAsMinutesAndSeconds(durationMs)); - } - - @Override - public void setSuccess() { - mStateText.setText(null); - } - - @Override - public void setIsFetchingContent() { - disableUiElements(); - mStateText.setText(getString(R.string.voicemail_fetching_content)); - } - - @Override - public void setFetchContentTimeout() { - mStartStopButton.setEnabled(true); - mStateText.setText(getString(R.string.voicemail_fetching_timout)); - } - - @Override - public int getDesiredClipPosition() { - return mPlaybackSeek.getProgress(); - } - - @Override - public void disableUiElements() { - mStartStopButton.setEnabled(false); - resetSeekBar(); - } - - @Override - public void enableUiElements() { - mDeleteButton.setEnabled(true); - mStartStopButton.setEnabled(true); - mPlaybackSeek.setEnabled(true); - mPlaybackSeek.setThumb(mVoicemailSeekHandleEnabled); - } - - @Override - public void resetSeekBar() { - mPlaybackSeek.setProgress(0); - mPlaybackSeek.setEnabled(false); - mPlaybackSeek.setThumb(mVoicemailSeekHandleDisabled); - } - - @Override - public void onDeleteCall() {} - - @Override - public void onDeleteVoicemail() { - mPresenter.onVoicemailDeletedInDatabase(); - } - - @Override - public void onGetCallDetails(PhoneCallDetails[] details) {} - - private String getString(int resId) { - return mContext.getString(resId); - } - - /** - * Formats a number of milliseconds as something that looks like {@code 00:05}. - * <p> - * We always use four digits, two for minutes two for seconds. In the very unlikely event - * that the voicemail duration exceeds 99 minutes, the display is capped at 99 minutes. - */ - private String formatAsMinutesAndSeconds(int millis) { - int seconds = millis / 1000; - int minutes = seconds / 60; - seconds -= minutes * 60; - if (minutes > 99) { - minutes = 99; - } - return String.format("%02d:%02d", minutes, seconds); - } - - /** - * Called when a voicemail archive succeeded. If the expanded voicemail was being - * archived, update the card UI. Either way, display a snackbar linking user to archive. - */ - @Override - public void onVoicemailArchiveSucceded(Uri voicemailUri) { - if (isArchiving(voicemailUri)) { - mIsArchiving.remove(voicemailUri); - if (Objects.equals(voicemailUri, mVoicemailUri)) { - onVoicemailArchiveResult(); - hideArchiveButton(); - } - } - - Snackbar.make(this, R.string.snackbar_voicemail_archived, - Snackbar.LENGTH_LONG) - .setDuration(VOICEMAIL_ARCHIVE_DELAY_MS) - .setAction(R.string.snackbar_voicemail_archived_goto, - new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(mContext, - VoicemailArchiveActivity.class); - mContext.startActivity(intent); - } - }) - .setActionTextColor( - mContext.getResources().getColor(R.color.dialer_snackbar_action_text_color)) - .show(); - } - - /** - * If a voicemail archive failed, and the expanded card was being archived, update the card UI. - * Either way, display a toast saying the voicemail archive failed. - */ - @Override - public void onVoicemailArchiveFailed(Uri voicemailUri) { - if (isArchiving(voicemailUri)) { - mIsArchiving.remove(voicemailUri); - if (Objects.equals(voicemailUri, mVoicemailUri)) { - onVoicemailArchiveResult(); - } - } - String toastStr = mContext.getString(R.string.voicemail_archive_failed); - Toast.makeText(mContext, toastStr, Toast.LENGTH_SHORT).show(); - } - - public void hideArchiveButton() { - mArchiveSpace.setVisibility(View.GONE); - mArchiveButton.setVisibility(View.GONE); - mArchiveButton.setClickable(false); - mArchiveButton.setEnabled(false); - } - - /** - * Whenever a voicemail archive succeeds or fails, clear the text displayed in the voicemail - * card. - */ - private void onVoicemailArchiveResult() { - enableUiElements(); - mStateText.setText(null); - mArchiveButton.setColorFilter(null); - } - - /** - * Whether or not the voicemail with the given uri is being archived. - */ - private boolean isArchiving(@Nullable Uri uri) { - return uri != null && mIsArchiving.contains(uri); - } - - /** - * Show the proper text and hide the archive button if the voicemail is still being archived. - */ - private void updateArchiveUI(@Nullable Uri voicemailUri) { - if (!Objects.equals(voicemailUri, mVoicemailUri)) { - return; - } - if (isArchiving(voicemailUri)) { - // If expanded card was in the middle of archiving, disable buttons and display message - disableUiElements(); - mDeleteButton.setEnabled(false); - mArchiveButton.setColorFilter(getResources().getColor(R.color.setting_disabled_color)); - mStateText.setText(getString(R.string.voicemail_archiving_content)); - } else { - onVoicemailArchiveResult(); - } - } - - /** - * Hides the archive button if the voicemail has already been archived, shows otherwise. - * @param voicemailUri the URI of the voicemail for which the archive button needs to be updated - */ - private void updateArchiveButton(@Nullable final Uri voicemailUri) { - if (voicemailUri == null || - !Objects.equals(voicemailUri, mVoicemailUri) || isArchiving(voicemailUri) || - Objects.equals(voicemailUri.getAuthority(),VoicemailArchiveContract.AUTHORITY)) { - return; - } - mAsyncTaskExecutor.submit(Tasks.QUERY_ARCHIVED_STATUS, - new AsyncTask<Void, Void, Boolean>() { - @Override - public Boolean doInBackground(Void... params) { - Cursor cursor = mContext.getContentResolver().query(VoicemailArchive.CONTENT_URI, - null, VoicemailArchive.SERVER_ID + "=" + ContentUris.parseId(mVoicemailUri) - + " AND " + VoicemailArchive.ARCHIVED + "= 1", null, null); - boolean archived = cursor != null && cursor.getCount() > 0; - cursor.close(); - return archived; - } - - @Override - public void onPostExecute(Boolean archived) { - if (!Objects.equals(voicemailUri, mVoicemailUri)) { - return; - } - - if (archived) { - hideArchiveButton(); - } else { - mArchiveSpace.setVisibility(View.VISIBLE); - mArchiveButton.setVisibility(View.VISIBLE); - mArchiveButton.setClickable(true); - mArchiveButton.setEnabled(true); - } - - } - }); - } - - @VisibleForTesting - public String getStateText() { - return mStateText.getText().toString(); - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java deleted file mode 100644 index e224ddc2a..000000000 --- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java +++ /dev/null @@ -1,1010 +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.voicemail; - -import com.google.common.annotations.VisibleForTesting; - -import android.app.Activity; -import android.content.Context; -import android.content.ContentResolver; -import android.content.Intent; -import android.database.ContentObserver; -import android.database.Cursor; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.PowerManager; -import android.provider.VoicemailContract; -import android.support.v4.content.FileProvider; -import android.util.Log; -import android.view.WindowManager.LayoutParams; - -import com.android.dialer.R; -import com.android.dialer.calllog.CallLogAsyncTaskUtil; -import com.android.dialer.util.AsyncTaskExecutor; -import com.android.dialer.util.AsyncTaskExecutors; -import com.android.common.io.MoreCloseables; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.annotation.concurrent.NotThreadSafe; -import javax.annotation.concurrent.ThreadSafe; - -/** - * Contains the controlling logic for a voicemail playback in the call log. It is closely coupled - * to assumptions about the behaviors and lifecycle of the call log, in particular in the - * {@link CallLogFragment} and {@link CallLogAdapter}. - * <p> - * This controls a single {@link com.android.dialer.voicemail.VoicemailPlaybackLayout}. A single - * instance can be reused for different such layouts, using {@link #setPlaybackView}. This - * is to facilitate reuse across different voicemail call log entries. - * <p> - * This class is not thread safe. The thread policy for this class is thread-confinement, all calls - * into this class from outside must be done from the main UI thread. - */ -@NotThreadSafe -@VisibleForTesting -public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListener, - MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { - - private static final String TAG = "VmPlaybackPresenter"; - - /** Contract describing the behaviour we need from the ui we are controlling. */ - public interface PlaybackView { - int getDesiredClipPosition(); - void disableUiElements(); - void enableUiElements(); - void onPlaybackError(); - void onPlaybackStarted(int duration, ScheduledExecutorService executorService); - void onPlaybackStopped(); - void onSpeakerphoneOn(boolean on); - void setClipPosition(int clipPositionInMillis, int clipLengthInMillis); - void setSuccess(); - void setFetchContentTimeout(); - void setIsFetchingContent(); - void onVoicemailArchiveSucceded(Uri voicemailUri); - void onVoicemailArchiveFailed(Uri voicemailUri); - void setPresenter(VoicemailPlaybackPresenter presenter, Uri voicemailUri); - void resetSeekBar(); - } - - public interface OnVoicemailDeletedListener { - void onVoicemailDeleted(Uri uri); - void onVoicemailDeleteUndo(); - void onVoicemailDeletedInDatabase(); - } - - /** The enumeration of {@link AsyncTask} objects we use in this class. */ - public enum Tasks { - CHECK_FOR_CONTENT, - CHECK_CONTENT_AFTER_CHANGE, - ARCHIVE_VOICEMAIL - } - - protected interface OnContentCheckedListener { - void onContentChecked(boolean hasContent); - } - - private static final String[] HAS_CONTENT_PROJECTION = new String[] { - VoicemailContract.Voicemails.HAS_CONTENT, - VoicemailContract.Voicemails.DURATION - }; - - private static final int NUMBER_OF_THREADS_IN_POOL = 2; - // Time to wait for content to be fetched before timing out. - private static final long FETCH_CONTENT_TIMEOUT_MS = 20000; - - private static final String VOICEMAIL_URI_KEY = - VoicemailPlaybackPresenter.class.getName() + ".VOICEMAIL_URI"; - private static final String IS_PREPARED_KEY = - VoicemailPlaybackPresenter.class.getName() + ".IS_PREPARED"; - // If present in the saved instance bundle, we should not resume playback on create. - private static final String IS_PLAYING_STATE_KEY = - VoicemailPlaybackPresenter.class.getName() + ".IS_PLAYING_STATE_KEY"; - // If present in the saved instance bundle, indicates where to set the playback slider. - private static final String CLIP_POSITION_KEY = - VoicemailPlaybackPresenter.class.getName() + ".CLIP_POSITION_KEY"; - private static final String IS_SPEAKERPHONE_ON_KEY = - VoicemailPlaybackPresenter.class.getName() + ".IS_SPEAKER_PHONE_ON"; - public static final int PLAYBACK_REQUEST = 0; - public static final int ARCHIVE_REQUEST = 1; - public static final int SHARE_REQUEST = 2; - - /** - * The most recently cached duration. We cache this since we don't want to keep requesting it - * from the player, as this can easily lead to throwing {@link IllegalStateException} (any time - * the player is released, it's illegal to ask for the duration). - */ - private final AtomicInteger mDuration = new AtomicInteger(0); - - private static VoicemailPlaybackPresenter sInstance; - - private Activity mActivity; - protected Context mContext; - private PlaybackView mView; - protected Uri mVoicemailUri; - - protected MediaPlayer mMediaPlayer; - private int mPosition; - private boolean mIsPlaying; - // MediaPlayer crashes on some method calls if not prepared but does not have a method which - // exposes its prepared state. Store this locally, so we can check and prevent crashes. - private boolean mIsPrepared; - private boolean mIsSpeakerphoneOn; - - private boolean mShouldResumePlaybackAfterSeeking; - private int mInitialOrientation; - - // Used to run async tasks that need to interact with the UI. - protected AsyncTaskExecutor mAsyncTaskExecutor; - private static ScheduledExecutorService mScheduledExecutorService; - /** - * Used to handle the result of a successful or time-out fetch result. - * <p> - * This variable is thread-contained, accessed only on the ui thread. - */ - private FetchResultHandler mFetchResultHandler; - private final List<FetchResultHandler> mArchiveResultHandlers = new ArrayList<>(); - private Handler mHandler = new Handler(); - private PowerManager.WakeLock mProximityWakeLock; - private VoicemailAudioManager mVoicemailAudioManager; - - private OnVoicemailDeletedListener mOnVoicemailDeletedListener; - private final VoicemailAsyncTaskUtil mVoicemailAsyncTaskUtil; - - /** - * Obtain singleton instance of this class. Use a single instance to provide a consistent - * listener to the AudioManager when requesting and abandoning audio focus. - * - * Otherwise, after rotation the previous listener will still be active but a new listener - * will be provided to calls to the AudioManager, which is bad. For example, abandoning - * audio focus with the new listeners results in an AUDIO_FOCUS_GAIN callback to the - * previous listener, which is the opposite of the intended behavior. - */ - public static VoicemailPlaybackPresenter getInstance( - Activity activity, Bundle savedInstanceState) { - if (sInstance == null) { - sInstance = new VoicemailPlaybackPresenter(activity); - } - - sInstance.init(activity, savedInstanceState); - return sInstance; - } - - /** - * Initialize variables which are activity-independent and state-independent. - */ - protected VoicemailPlaybackPresenter(Activity activity) { - Context context = activity.getApplicationContext(); - mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor(); - mVoicemailAudioManager = new VoicemailAudioManager(context, this); - mVoicemailAsyncTaskUtil = new VoicemailAsyncTaskUtil(context.getContentResolver()); - PowerManager powerManager = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { - mProximityWakeLock = powerManager.newWakeLock( - PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); - } - } - - /** - * Update variables which are activity-dependent or state-dependent. - */ - protected void init(Activity activity, Bundle savedInstanceState) { - mActivity = activity; - mContext = activity; - - mInitialOrientation = mContext.getResources().getConfiguration().orientation; - mActivity.setVolumeControlStream(VoicemailAudioManager.PLAYBACK_STREAM); - - if (savedInstanceState != null) { - // Restores playback state when activity is recreated, such as after rotation. - mVoicemailUri = (Uri) savedInstanceState.getParcelable(VOICEMAIL_URI_KEY); - mIsPrepared = savedInstanceState.getBoolean(IS_PREPARED_KEY); - mPosition = savedInstanceState.getInt(CLIP_POSITION_KEY, 0); - mIsPlaying = savedInstanceState.getBoolean(IS_PLAYING_STATE_KEY, false); - mIsSpeakerphoneOn = savedInstanceState.getBoolean(IS_SPEAKERPHONE_ON_KEY, false); - } - - if (mMediaPlayer == null) { - mIsPrepared = false; - mIsPlaying = false; - } - } - - /** - * Must be invoked when the parent Activity is saving it state. - */ - public void onSaveInstanceState(Bundle outState) { - if (mView != null) { - outState.putParcelable(VOICEMAIL_URI_KEY, mVoicemailUri); - outState.putBoolean(IS_PREPARED_KEY, mIsPrepared); - outState.putInt(CLIP_POSITION_KEY, mView.getDesiredClipPosition()); - outState.putBoolean(IS_PLAYING_STATE_KEY, mIsPlaying); - outState.putBoolean(IS_SPEAKERPHONE_ON_KEY, mIsSpeakerphoneOn); - } - } - - /** - * Specify the view which this presenter controls and the voicemail to prepare to play. - */ - public void setPlaybackView( - PlaybackView view, Uri voicemailUri, boolean startPlayingImmediately) { - mView = view; - mView.setPresenter(this, voicemailUri); - - // Handles cases where the same entry is binded again when scrolling in list, or where - // the MediaPlayer was retained after an orientation change. - if (mMediaPlayer != null && mIsPrepared && voicemailUri.equals(mVoicemailUri)) { - // If the voicemail card was rebinded, we need to set the position to the appropriate - // point. Since we retain the media player, we can just set it to the position of the - // media player. - mPosition = mMediaPlayer.getCurrentPosition(); - onPrepared(mMediaPlayer); - } else { - if (!voicemailUri.equals(mVoicemailUri)) { - mVoicemailUri = voicemailUri; - mPosition = 0; - // Default to earpiece. - setSpeakerphoneOn(false); - mVoicemailAudioManager.setSpeakerphoneOn(false); - } else { - // Update the view to the current speakerphone state. - mView.onSpeakerphoneOn(mIsSpeakerphoneOn); - } - /* - * Check to see if the content field in the DB is set. If set, we proceed to - * prepareContent() method. We get the duration of the voicemail from the query and set - * it if the content is not available. - */ - checkForContent(new OnContentCheckedListener() { - @Override - public void onContentChecked(boolean hasContent) { - if (hasContent) { - prepareContent(); - } else if (mView != null) { - mView.resetSeekBar(); - mView.setClipPosition(0, mDuration.get()); - } - } - }); - - if (startPlayingImmediately) { - // Since setPlaybackView can get called during the view binding process, we don't - // want to reset mIsPlaying to false if the user is currently playing the - // voicemail and the view is rebound. - mIsPlaying = startPlayingImmediately; - } - } - } - - /** - * Reset the presenter for playback back to its original state. - */ - public void resetAll() { - pausePresenter(true); - - mView = null; - mVoicemailUri = null; - } - - /** - * When navigating away from voicemail playback, we need to release the media player, - * pause the UI and save the position. - * - * @param reset {@code true} if we want to reset the position of the playback, {@code false} if - * we want to retain the current position (in case we return to the voicemail). - */ - public void pausePresenter(boolean reset) { - if (mMediaPlayer != null) { - mMediaPlayer.release(); - mMediaPlayer = null; - } - - disableProximitySensor(false /* waitForFarState */); - - mIsPrepared = false; - mIsPlaying = false; - - if (reset) { - // We want to reset the position whether or not the view is valid. - mPosition = 0; - } - - if (mView != null) { - mView.onPlaybackStopped(); - if (reset) { - mView.setClipPosition(0, mDuration.get()); - } else { - mPosition = mView.getDesiredClipPosition(); - } - } - } - - /** - * Must be invoked when the parent activity is resumed. - */ - public void onResume() { - mVoicemailAudioManager.registerReceivers(); - } - - /** - * Must be invoked when the parent activity is paused. - */ - public void onPause() { - mVoicemailAudioManager.unregisterReceivers(); - - if (mContext != null && mIsPrepared - && mInitialOrientation != mContext.getResources().getConfiguration().orientation) { - // If an orientation change triggers the pause, retain the MediaPlayer. - Log.d(TAG, "onPause: Orientation changed."); - return; - } - - // Release the media player, otherwise there may be failures. - pausePresenter(false); - - if (mActivity != null) { - mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - } - - /** - * Must be invoked when the parent activity is destroyed. - */ - public void onDestroy() { - // Clear references to avoid leaks from the singleton instance. - mActivity = null; - mContext = null; - - if (mScheduledExecutorService != null) { - mScheduledExecutorService.shutdown(); - mScheduledExecutorService = null; - } - - if (!mArchiveResultHandlers.isEmpty()) { - for (FetchResultHandler fetchResultHandler : mArchiveResultHandlers) { - fetchResultHandler.destroy(); - } - mArchiveResultHandlers.clear(); - } - - if (mFetchResultHandler != null) { - mFetchResultHandler.destroy(); - mFetchResultHandler = null; - } - } - - /** - * Checks to see if we have content available for this voicemail. - */ - protected void checkForContent(final OnContentCheckedListener callback) { - mAsyncTaskExecutor.submit(Tasks.CHECK_FOR_CONTENT, new AsyncTask<Void, Void, Boolean>() { - @Override - public Boolean doInBackground(Void... params) { - return queryHasContent(mVoicemailUri); - } - - @Override - public void onPostExecute(Boolean hasContent) { - callback.onContentChecked(hasContent); - } - }); - } - - private boolean queryHasContent(Uri voicemailUri) { - if (voicemailUri == null || mContext == null) { - return false; - } - - ContentResolver contentResolver = mContext.getContentResolver(); - Cursor cursor = contentResolver.query( - voicemailUri, null, null, null, null); - try { - if (cursor != null && cursor.moveToNext()) { - int duration = cursor.getInt(cursor.getColumnIndex( - VoicemailContract.Voicemails.DURATION)); - // Convert database duration (seconds) into mDuration (milliseconds) - mDuration.set(duration > 0 ? duration * 1000 : 0); - return cursor.getInt(cursor.getColumnIndex( - VoicemailContract.Voicemails.HAS_CONTENT)) == 1; - } - } finally { - MoreCloseables.closeQuietly(cursor); - } - return false; - } - - /** - * Makes a broadcast request to ask that a voicemail source fetch this content. - * <p> - * This method <b>must be called on the ui thread</b>. - * <p> - * This method will be called when we realise that we don't have content for this voicemail. It - * will trigger a broadcast to request that the content be downloaded. It will add a listener to - * the content resolver so that it will be notified when the has_content field changes. It will - * also set a timer. If the has_content field changes to true within the allowed time, we will - * proceed to {@link #prepareContent()}. If the has_content field does not - * become true within the allowed time, we will update the ui to reflect the fact that content - * was not available. - * - * @return whether issued request to fetch content - */ - protected boolean requestContent(int code) { - if (mContext == null || mVoicemailUri == null) { - return false; - } - - FetchResultHandler tempFetchResultHandler = - new FetchResultHandler(new Handler(), mVoicemailUri, code); - - switch (code) { - case ARCHIVE_REQUEST: - mArchiveResultHandlers.add(tempFetchResultHandler); - break; - default: - if (mFetchResultHandler != null) { - mFetchResultHandler.destroy(); - } - mView.setIsFetchingContent(); - mFetchResultHandler = tempFetchResultHandler; - break; - } - - // Send voicemail fetch request. - Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, mVoicemailUri); - mContext.sendBroadcast(intent); - return true; - } - - @ThreadSafe - private class FetchResultHandler extends ContentObserver implements Runnable { - private AtomicBoolean mIsWaitingForResult = new AtomicBoolean(true); - private final Handler mFetchResultHandler; - private final Uri mVoicemailUri; - private final int mRequestCode; - - public FetchResultHandler(Handler handler, Uri uri, int code) { - super(handler); - mFetchResultHandler = handler; - mRequestCode = code; - mVoicemailUri = uri; - if (mContext != null) { - mContext.getContentResolver().registerContentObserver( - mVoicemailUri, false, this); - mFetchResultHandler.postDelayed(this, FETCH_CONTENT_TIMEOUT_MS); - } - } - - /** - * Stop waiting for content and notify UI if {@link FETCH_CONTENT_TIMEOUT_MS} has elapsed. - */ - @Override - public void run() { - if (mIsWaitingForResult.getAndSet(false) && mContext != null) { - mContext.getContentResolver().unregisterContentObserver(this); - if (mView != null) { - mView.setFetchContentTimeout(); - } - } - } - - public void destroy() { - if (mIsWaitingForResult.getAndSet(false) && mContext != null) { - mContext.getContentResolver().unregisterContentObserver(this); - mFetchResultHandler.removeCallbacks(this); - } - } - - @Override - public void onChange(boolean selfChange) { - mAsyncTaskExecutor.submit(Tasks.CHECK_CONTENT_AFTER_CHANGE, - new AsyncTask<Void, Void, Boolean>() { - - @Override - public Boolean doInBackground(Void... params) { - return queryHasContent(mVoicemailUri); - } - - @Override - public void onPostExecute(Boolean hasContent) { - if (hasContent && mContext != null && mIsWaitingForResult.getAndSet(false)) { - mContext.getContentResolver().unregisterContentObserver( - FetchResultHandler.this); - prepareContent(); - if (mRequestCode == ARCHIVE_REQUEST) { - startArchiveVoicemailTask(mVoicemailUri, true /* archivedByUser */); - } else if (mRequestCode == SHARE_REQUEST) { - startArchiveVoicemailTask(mVoicemailUri, false /* archivedByUser */); - } - } - } - }); - } - } - - /** - * Prepares the voicemail content for playback. - * <p> - * This method will be called once we know that our voicemail has content (according to the - * content provider). this method asynchronously tries to prepare the data source through the - * media player. If preparation is successful, the media player will {@link #onPrepared()}, - * and it will call {@link #onError()} otherwise. - */ - protected void prepareContent() { - if (mView == null) { - return; - } - Log.d(TAG, "prepareContent"); - - // Release the previous media player, otherwise there may be failures. - if (mMediaPlayer != null) { - mMediaPlayer.release(); - mMediaPlayer = null; - } - - mView.disableUiElements(); - mIsPrepared = false; - - try { - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnPreparedListener(this); - mMediaPlayer.setOnErrorListener(this); - mMediaPlayer.setOnCompletionListener(this); - - mMediaPlayer.reset(); - mMediaPlayer.setDataSource(mContext, mVoicemailUri); - mMediaPlayer.setAudioStreamType(VoicemailAudioManager.PLAYBACK_STREAM); - mMediaPlayer.prepareAsync(); - } catch (IOException e) { - handleError(e); - } - } - - /** - * Once the media player is prepared, enables the UI and adopts the appropriate playback state. - */ - @Override - public void onPrepared(MediaPlayer mp) { - if (mView == null) { - return; - } - Log.d(TAG, "onPrepared"); - mIsPrepared = true; - - // Update the duration in the database if it was not previously retrieved - CallLogAsyncTaskUtil.updateVoicemailDuration(mContext, mVoicemailUri, - TimeUnit.MILLISECONDS.toSeconds(mMediaPlayer.getDuration())); - - mDuration.set(mMediaPlayer.getDuration()); - - Log.d(TAG, "onPrepared: mPosition=" + mPosition); - mView.setClipPosition(mPosition, mDuration.get()); - mView.enableUiElements(); - mView.setSuccess(); - mMediaPlayer.seekTo(mPosition); - - if (mIsPlaying) { - resumePlayback(); - } else { - pausePlayback(); - } - } - - /** - * Invoked if preparing the media player fails, for example, if file is missing or the voicemail - * is an unknown file format that can't be played. - */ - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - handleError(new IllegalStateException("MediaPlayer error listener invoked: " + extra)); - return true; - } - - protected void handleError(Exception e) { - Log.d(TAG, "handleError: Could not play voicemail " + e); - - if (mIsPrepared) { - mMediaPlayer.release(); - mMediaPlayer = null; - mIsPrepared = false; - } - - if (mView != null) { - mView.onPlaybackError(); - } - - mPosition = 0; - mIsPlaying = false; - } - - /** - * After done playing the voicemail clip, reset the clip position to the start. - */ - @Override - public void onCompletion(MediaPlayer mediaPlayer) { - pausePlayback(); - - // Reset the seekbar position to the beginning. - mPosition = 0; - if (mView != null) { - mView.setClipPosition(0, mDuration.get()); - } - } - - /** - * Only play voicemail when audio focus is granted. When it is lost (usually by another - * application requesting focus), pause playback. - * - * @param gainedFocus {@code true} if the audio focus was gained, {@code} false otherwise. - */ - public void onAudioFocusChange(boolean gainedFocus) { - if (mIsPlaying == gainedFocus) { - // Nothing new here, just exit. - return; - } - - if (!mIsPlaying) { - resumePlayback(); - } else { - pausePlayback(); - } - } - - /** - * Resumes voicemail playback at the clip position stored by the presenter. Null-op if already - * playing. - */ - public void resumePlayback() { - if (mView == null) { - return; - } - - if (!mIsPrepared) { - /* - * Check content before requesting content to avoid duplicated requests. It is possible - * that the UI doesn't know content has arrived if the fetch took too long causing a - * timeout, but succeeded. - */ - checkForContent(new OnContentCheckedListener() { - @Override - public void onContentChecked(boolean hasContent) { - if (!hasContent) { - // No local content, download from server. Queue playing if the request was - // issued, - mIsPlaying = requestContent(PLAYBACK_REQUEST); - } else { - // Queue playing once the media play loaded the content. - mIsPlaying = true; - prepareContent(); - } - } - }); - return; - } - - mIsPlaying = true; - - if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { - // Clamp the start position between 0 and the duration. - mPosition = Math.max(0, Math.min(mPosition, mDuration.get())); - - mMediaPlayer.seekTo(mPosition); - - try { - // Grab audio focus. - // Can throw RejectedExecutionException. - mVoicemailAudioManager.requestAudioFocus(); - mMediaPlayer.start(); - setSpeakerphoneOn(mIsSpeakerphoneOn); - } catch (RejectedExecutionException e) { - handleError(e); - } - } - - Log.d(TAG, "Resumed playback at " + mPosition + "."); - mView.onPlaybackStarted(mDuration.get(), getScheduledExecutorServiceInstance()); - } - - /** - * Pauses voicemail playback at the current position. Null-op if already paused. - */ - public void pausePlayback() { - if (!mIsPrepared) { - return; - } - - mIsPlaying = false; - - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { - mMediaPlayer.pause(); - } - - mPosition = mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition(); - - Log.d(TAG, "Paused playback at " + mPosition + "."); - - if (mView != null) { - mView.onPlaybackStopped(); - } - - mVoicemailAudioManager.abandonAudioFocus(); - - if (mActivity != null) { - mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); - } - disableProximitySensor(true /* waitForFarState */); - } - - /** - * Pauses playback when the user starts seeking the position, and notes whether the voicemail is - * playing to know whether to resume playback once the user selects a new position. - */ - public void pausePlaybackForSeeking() { - if (mMediaPlayer != null) { - mShouldResumePlaybackAfterSeeking = mMediaPlayer.isPlaying(); - } - pausePlayback(); - } - - public void resumePlaybackAfterSeeking(int desiredPosition) { - mPosition = desiredPosition; - if (mShouldResumePlaybackAfterSeeking) { - mShouldResumePlaybackAfterSeeking = false; - resumePlayback(); - } - } - - /** - * Seek to position. This is called when user manually seek the playback. It could be either - * by touch or volume button while in talkback mode. - * @param position - */ - public void seek(int position) { - mPosition = position; - } - - private void enableProximitySensor() { - if (mProximityWakeLock == null || mIsSpeakerphoneOn || !mIsPrepared - || mMediaPlayer == null || !mMediaPlayer.isPlaying()) { - return; - } - - if (!mProximityWakeLock.isHeld()) { - Log.i(TAG, "Acquiring proximity wake lock"); - mProximityWakeLock.acquire(); - } else { - Log.i(TAG, "Proximity wake lock already acquired"); - } - } - - private void disableProximitySensor(boolean waitForFarState) { - if (mProximityWakeLock == null) { - return; - } - if (mProximityWakeLock.isHeld()) { - Log.i(TAG, "Releasing proximity wake lock"); - int flags = waitForFarState ? PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY : 0; - mProximityWakeLock.release(flags); - } else { - Log.i(TAG, "Proximity wake lock already released"); - } - } - - /** - * This is for use by UI interactions only. It simplifies UI logic. - */ - public void toggleSpeakerphone() { - mVoicemailAudioManager.setSpeakerphoneOn(!mIsSpeakerphoneOn); - setSpeakerphoneOn(!mIsSpeakerphoneOn); - } - - /** - * This method only handles app-level changes to the speakerphone. Audio layer changes should - * be handled separately. This is so that the VoicemailAudioManager can trigger changes to - * the presenter without the presenter triggering the audio manager and duplicating actions. - */ - public void setSpeakerphoneOn(boolean on) { - if (mView == null) { - return; - } - - mView.onSpeakerphoneOn(on); - - mIsSpeakerphoneOn = on; - - // This should run even if speakerphone is not being toggled because we may be switching - // from earpiece to headphone and vise versa. Also upon initial setup the default audio - // source is the earpiece, so we want to trigger the proximity sensor. - if (mIsPlaying) { - if (on || mVoicemailAudioManager.isWiredHeadsetPluggedIn()) { - disableProximitySensor(false /* waitForFarState */); - if (mIsPrepared && mMediaPlayer != null && mMediaPlayer.isPlaying()) { - mActivity.getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); - } - } else { - enableProximitySensor(); - if (mActivity != null) { - mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); - } - } - } - } - - public void setOnVoicemailDeletedListener(OnVoicemailDeletedListener listener) { - mOnVoicemailDeletedListener = listener; - } - - public int getMediaPlayerPosition() { - return mIsPrepared && mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : 0; - } - - public void notifyUiOfArchiveResult(Uri voicemailUri, boolean archived) { - if (mView == null) { - return; - } - if (archived) { - mView.onVoicemailArchiveSucceded(voicemailUri); - } else { - mView.onVoicemailArchiveFailed(voicemailUri); - } - } - - /* package */ void onVoicemailDeleted() { - // Trampoline the event notification to the interested listener. - if (mOnVoicemailDeletedListener != null) { - mOnVoicemailDeletedListener.onVoicemailDeleted(mVoicemailUri); - } - } - - /* package */ void onVoicemailDeleteUndo() { - // Trampoline the event notification to the interested listener. - if (mOnVoicemailDeletedListener != null) { - mOnVoicemailDeletedListener.onVoicemailDeleteUndo(); - } - } - - /* package */ void onVoicemailDeletedInDatabase() { - // Trampoline the event notification to the interested listener. - if (mOnVoicemailDeletedListener != null) { - mOnVoicemailDeletedListener.onVoicemailDeletedInDatabase(); - } - } - - private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() { - if (mScheduledExecutorService == null) { - mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL); - } - return mScheduledExecutorService; - } - - /** - * If voicemail has already been downloaded, go straight to archiving. Otherwise, request - * the voicemail content first. - */ - public void archiveContent(final Uri voicemailUri, final boolean archivedByUser) { - checkForContent(new OnContentCheckedListener() { - @Override - public void onContentChecked(boolean hasContent) { - if (!hasContent) { - requestContent(archivedByUser ? ARCHIVE_REQUEST : SHARE_REQUEST); - } else { - startArchiveVoicemailTask(voicemailUri, archivedByUser); - } - } - }); - } - - /** - * Asynchronous task used to archive a voicemail given its uri. - */ - protected void startArchiveVoicemailTask(final Uri voicemailUri, final boolean archivedByUser) { - mVoicemailAsyncTaskUtil.archiveVoicemailContent( - new VoicemailAsyncTaskUtil.OnArchiveVoicemailListener() { - @Override - public void onArchiveVoicemail(final Uri archivedVoicemailUri) { - if (archivedVoicemailUri == null) { - notifyUiOfArchiveResult(voicemailUri, false); - return; - } - - if (archivedByUser) { - setArchivedVoicemailStatusAndUpdateUI(voicemailUri, - archivedVoicemailUri, true); - } else { - sendShareIntent(archivedVoicemailUri); - } - } - }, voicemailUri); - } - - /** - * Sends the intent for sharing the voicemail file. - */ - protected void sendShareIntent(final Uri voicemailUri) { - mVoicemailAsyncTaskUtil.getVoicemailFilePath( - new VoicemailAsyncTaskUtil.OnGetArchivedVoicemailFilePathListener() { - @Override - public void onGetArchivedVoicemailFilePath(String filePath) { - mView.enableUiElements(); - if (filePath == null) { - mView.setFetchContentTimeout(); - return; - } - Uri voicemailFileUri = FileProvider.getUriForFile( - mContext, - mContext.getString(R.string.contacts_file_provider_authority), - new File(filePath)); - mContext.startActivity(Intent.createChooser( - getShareIntent(voicemailFileUri), - mContext.getResources().getText( - R.string.call_log_share_voicemail))); - } - }, voicemailUri); - } - - /** Sets archived_by_user field to the given boolean and updates the URI. */ - private void setArchivedVoicemailStatusAndUpdateUI( - final Uri voicemailUri, - final Uri archivedVoicemailUri, - boolean status) { - mVoicemailAsyncTaskUtil.setVoicemailArchiveStatus( - new VoicemailAsyncTaskUtil.OnSetVoicemailArchiveStatusListener() { - @Override - public void onSetVoicemailArchiveStatus(boolean success) { - notifyUiOfArchiveResult(voicemailUri, success); - } - }, archivedVoicemailUri, status); - } - - private Intent getShareIntent(Uri voicemailFileUri) { - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, voicemailFileUri); - shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.setType(mContext.getContentResolver() - .getType(voicemailFileUri)); - return shareIntent; - } - - @VisibleForTesting - public boolean isPlaying() { - return mIsPlaying; - } - - @VisibleForTesting - public boolean isSpeakerphoneOn() { - return mIsSpeakerphoneOn; - } - - @VisibleForTesting - public void clearInstance() { - sInstance = null; - } -} diff --git a/src/com/android/dialer/voicemail/VoicemailStatusHelper.java b/src/com/android/dialer/voicemail/VoicemailStatusHelper.java deleted file mode 100644 index d790b7764..000000000 --- a/src/com/android/dialer/voicemail/VoicemailStatusHelper.java +++ /dev/null @@ -1,91 +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.voicemail; - -import android.database.Cursor; -import android.net.Uri; -import android.provider.VoicemailContract.Status; - -import com.google.common.annotations.VisibleForTesting; - -import java.util.List; - -/** - * Interface used by the call log UI to determine what user message, if any, related to voicemail - * source status needs to be shown. The messages are returned in the order of importance. - * <p> - * The implementation of this interface interacts with the voicemail content provider to fetch - * statuses of all the registered voicemail sources and determines if any status message needs to - * be shown. The user of this interface must observe/listen to provider changes and invoke - * this class to check if any message needs to be shown. - */ -public interface VoicemailStatusHelper { - @VisibleForTesting - public class StatusMessage { - /** Package of the source on behalf of which this message has to be shown.*/ - public final String sourcePackage; - /** - * The string resource id of the status message that should be shown in the call log - * page. Set to -1, if this message is not to be shown in call log. - */ - public final int callLogMessageId; - /** - * The string resource id of the status message that should be shown in the call details - * page. Set to -1, if this message is not to be shown in call details page. - */ - public final int callDetailsMessageId; - /** The string resource id of the action message that should be shown. */ - public final int actionMessageId; - /** URI for the corrective action, where applicable. Null if no action URI is available. */ - public final Uri actionUri; - - public StatusMessage(String sourcePackage, int callLogMessageId, int callDetailsMessageId, - int actionMessageId, Uri actionUri) { - this.sourcePackage = sourcePackage; - this.callLogMessageId = callLogMessageId; - this.callDetailsMessageId = callDetailsMessageId; - this.actionMessageId = actionMessageId; - this.actionUri = actionUri; - } - - /** Whether this message should be shown in the call log page. */ - public boolean showInCallLog() { - return callLogMessageId != -1; - } - - /** Whether this message should be shown in the call details page. */ - public boolean showInCallDetails() { - return callDetailsMessageId != -1; - } - } - - /** - * Returns a list of messages, in the order or priority that should be shown to the user. An - * empty list is returned if no message needs to be shown. - * @param cursor The cursor pointing to the query on {@link Status#CONTENT_URI}. The projection - * to be used is defined by the implementation class of this interface. - */ - @VisibleForTesting - public List<StatusMessage> getStatusMessages(Cursor cursor); - - /** - * Returns the number of active voicemail sources installed. - * <p> - * The number of sources is counted by querying the voicemail status table. - */ - public int getNumberActivityVoicemailSources(Cursor cursor); -} diff --git a/src/com/android/dialer/voicemail/VoicemailStatusHelperImpl.java b/src/com/android/dialer/voicemail/VoicemailStatusHelperImpl.java deleted file mode 100644 index ff1786862..000000000 --- a/src/com/android/dialer/voicemail/VoicemailStatusHelperImpl.java +++ /dev/null @@ -1,272 +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.voicemail; - -import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED; -import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_OK; -import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION; -import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK; -import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING; -import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION; -import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK; - -import android.database.Cursor; -import android.net.Uri; -import android.provider.VoicemailContract.Status; - -import com.android.contacts.common.util.UriUtils; -import com.android.dialer.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** Implementation of {@link VoicemailStatusHelper}. */ -public class VoicemailStatusHelperImpl implements VoicemailStatusHelper { - private static final int SOURCE_PACKAGE_INDEX = 0; - private static final int CONFIGURATION_STATE_INDEX = 1; - private static final int DATA_CHANNEL_STATE_INDEX = 2; - private static final int NOTIFICATION_CHANNEL_STATE_INDEX = 3; - private static final int SETTINGS_URI_INDEX = 4; - private static final int VOICEMAIL_ACCESS_URI_INDEX = 5; - private static final int NUM_COLUMNS = 6; - /** Projection on the voicemail_status table used by this class. */ - public static final String[] PROJECTION = new String[NUM_COLUMNS]; - static { - PROJECTION[SOURCE_PACKAGE_INDEX] = Status.SOURCE_PACKAGE; - PROJECTION[CONFIGURATION_STATE_INDEX] = Status.CONFIGURATION_STATE; - PROJECTION[DATA_CHANNEL_STATE_INDEX] = Status.DATA_CHANNEL_STATE; - PROJECTION[NOTIFICATION_CHANNEL_STATE_INDEX] = Status.NOTIFICATION_CHANNEL_STATE; - PROJECTION[SETTINGS_URI_INDEX] = Status.SETTINGS_URI; - PROJECTION[VOICEMAIL_ACCESS_URI_INDEX] = Status.VOICEMAIL_ACCESS_URI; - } - - /** Possible user actions. */ - public static enum Action { - NONE(-1), - CALL_VOICEMAIL(R.string.voicemail_status_action_call_server), - CONFIGURE_VOICEMAIL(R.string.voicemail_status_action_configure); - - private final int mMessageId; - private Action(int messageId) { - mMessageId = messageId; - } - - public int getMessageId() { - return mMessageId; - } - } - - /** - * Overall state of the source status. Each state is associated with the corresponding display - * string and the corrective action. The states are also assigned a relative priority which is - * used to order the messages from different sources. - */ - private static enum OverallState { - // TODO: Add separate string for call details and call log pages for the states that needs - // to be shown in both. - /** Both notification and data channel are not working. */ - NO_CONNECTION(0, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available, - R.string.voicemail_status_audio_not_available), - /** Notifications working, but data channel is not working. Audio cannot be downloaded. */ - NO_DATA(1, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available, - R.string.voicemail_status_audio_not_available), - /** Messages are known to be waiting but data channel is not working. */ - MESSAGE_WAITING(2, Action.CALL_VOICEMAIL, R.string.voicemail_status_messages_waiting, - R.string.voicemail_status_audio_not_available), - /** Notification channel not working, but data channel is. */ - NO_NOTIFICATIONS(3, Action.CALL_VOICEMAIL, - R.string.voicemail_status_voicemail_not_available), - /** Invite user to set up voicemail. */ - INVITE_FOR_CONFIGURATION(4, Action.CONFIGURE_VOICEMAIL, - R.string.voicemail_status_configure_voicemail), - /** - * No detailed notifications, but data channel is working. - * This is normal mode of operation for certain sources. No action needed. - */ - NO_DETAILED_NOTIFICATION(5, Action.NONE, -1), - /** Visual voicemail not yet set up. No local action needed. */ - NOT_CONFIGURED(6, Action.NONE, -1), - /** Everything is OK. */ - OK(7, Action.NONE, -1), - /** If one or more state value set by the source is not valid. */ - INVALID(8, Action.NONE, -1); - - private final int mPriority; - private final Action mAction; - private final int mCallLogMessageId; - private final int mCallDetailsMessageId; - - private OverallState(int priority, Action action, int callLogMessageId) { - this(priority, action, callLogMessageId, -1); - } - - private OverallState(int priority, Action action, int callLogMessageId, - int callDetailsMessageId) { - mPriority = priority; - mAction = action; - mCallLogMessageId = callLogMessageId; - mCallDetailsMessageId = callDetailsMessageId; - } - - public Action getAction() { - return mAction; - } - - public int getPriority() { - return mPriority; - } - - public int getCallLogMessageId() { - return mCallLogMessageId; - } - - public int getCallDetailsMessageId() { - return mCallDetailsMessageId; - } - } - - /** A wrapper on {@link StatusMessage} which additionally stores the priority of the message. */ - private static class MessageStatusWithPriority { - private final StatusMessage mMessage; - private final int mPriority; - - public MessageStatusWithPriority(StatusMessage message, int priority) { - mMessage = message; - mPriority = priority; - } - } - - @Override - public List<StatusMessage> getStatusMessages(Cursor cursor) { - List<MessageStatusWithPriority> messages = - new ArrayList<VoicemailStatusHelperImpl.MessageStatusWithPriority>(); - cursor.moveToPosition(-1); - while(cursor.moveToNext()) { - MessageStatusWithPriority message = getMessageForStatusEntry(cursor); - if (message != null) { - messages.add(message); - } - } - // Finally reorder the messages by their priority. - return reorderMessages(messages); - } - - @Override - public int getNumberActivityVoicemailSources(Cursor cursor) { - int count = 0; - cursor.moveToPosition(-1); - while(cursor.moveToNext()) { - if (isVoicemailSourceActive(cursor)) { - ++count; - } - } - return count; - } - - /** Returns whether the source status in the cursor corresponds to an active source. */ - private boolean isVoicemailSourceActive(Cursor cursor) { - return cursor.getString(SOURCE_PACKAGE_INDEX) != null - && cursor.getInt(CONFIGURATION_STATE_INDEX) == Status.CONFIGURATION_STATE_OK; - } - - private List<StatusMessage> reorderMessages(List<MessageStatusWithPriority> messageWrappers) { - Collections.sort(messageWrappers, new Comparator<MessageStatusWithPriority>() { - @Override - public int compare(MessageStatusWithPriority msg1, MessageStatusWithPriority msg2) { - return msg1.mPriority - msg2.mPriority; - } - }); - List<StatusMessage> reorderMessages = new ArrayList<VoicemailStatusHelper.StatusMessage>(); - // Copy the ordered message objects into the final list. - for (MessageStatusWithPriority messageWrapper : messageWrappers) { - reorderMessages.add(messageWrapper.mMessage); - } - return reorderMessages; - } - - /** - * Returns the message for the status entry pointed to by the cursor. - */ - private MessageStatusWithPriority getMessageForStatusEntry(Cursor cursor) { - final String sourcePackage = cursor.getString(SOURCE_PACKAGE_INDEX); - if (sourcePackage == null) { - return null; - } - final OverallState overallState = getOverallState(cursor.getInt(CONFIGURATION_STATE_INDEX), - cursor.getInt(DATA_CHANNEL_STATE_INDEX), - cursor.getInt(NOTIFICATION_CHANNEL_STATE_INDEX)); - final Action action = overallState.getAction(); - - // No source package or no action, means no message shown. - if (action == Action.NONE) { - return null; - } - - Uri actionUri = null; - if (action == Action.CALL_VOICEMAIL) { - actionUri = UriUtils.parseUriOrNull(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX)); - // Even if actionUri is null, it is still be useful to show the notification. - } else if (action == Action.CONFIGURE_VOICEMAIL) { - actionUri = UriUtils.parseUriOrNull(cursor.getString(SETTINGS_URI_INDEX)); - // If there is no settings URI, there is no point in showing the notification. - if (actionUri == null) { - return null; - } - } - return new MessageStatusWithPriority( - new StatusMessage(sourcePackage, overallState.getCallLogMessageId(), - overallState.getCallDetailsMessageId(), action.getMessageId(), - actionUri), - overallState.getPriority()); - } - - private OverallState getOverallState(int configurationState, int dataChannelState, - int notificationChannelState) { - if (configurationState == CONFIGURATION_STATE_OK) { - // Voicemail is configured. Let's see how is the data channel. - if (dataChannelState == DATA_CHANNEL_STATE_OK) { - // Data channel is fine. What about notification channel? - if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) { - return OverallState.OK; - } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) { - return OverallState.NO_DETAILED_NOTIFICATION; - } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) { - return OverallState.NO_NOTIFICATIONS; - } - } else if (dataChannelState == DATA_CHANNEL_STATE_NO_CONNECTION) { - // Data channel is not working. What about notification channel? - if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) { - return OverallState.NO_DATA; - } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) { - return OverallState.MESSAGE_WAITING; - } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) { - return OverallState.NO_CONNECTION; - } - } - } else if (configurationState == CONFIGURATION_STATE_CAN_BE_CONFIGURED) { - // Voicemail not configured. data/notification channel states are irrelevant. - return OverallState.INVITE_FOR_CONFIGURATION; - } else if (configurationState == Status.CONFIGURATION_STATE_NOT_CONFIGURED) { - // Voicemail not configured. data/notification channel states are irrelevant. - return OverallState.NOT_CONFIGURED; - } - // Will reach here only if the source has set an invalid value. - return OverallState.INVALID; - } -} diff --git a/src/com/android/dialer/voicemail/WiredHeadsetManager.java b/src/com/android/dialer/voicemail/WiredHeadsetManager.java deleted file mode 100644 index 7351f4f01..000000000 --- a/src/com/android/dialer/voicemail/WiredHeadsetManager.java +++ /dev/null @@ -1,88 +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.voicemail; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import android.util.Log; - -/** Listens for and caches headset state. */ -class WiredHeadsetManager { - private static final String TAG = WiredHeadsetManager.class.getSimpleName(); - - interface Listener { - void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn); - } - - /** Receiver for wired headset plugged and unplugged events. */ - private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (AudioManager.ACTION_HEADSET_PLUG.equals(intent.getAction())) { - boolean isPluggedIn = intent.getIntExtra("state", 0) == 1; - Log.v(TAG, "ACTION_HEADSET_PLUG event, plugged in: " + isPluggedIn); - onHeadsetPluggedInChanged(isPluggedIn); - } - } - } - - private final WiredHeadsetBroadcastReceiver mReceiver; - private boolean mIsPluggedIn; - private Listener mListener; - private Context mContext; - - WiredHeadsetManager(Context context) { - mContext = context; - mReceiver = new WiredHeadsetBroadcastReceiver(); - - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mIsPluggedIn = audioManager.isWiredHeadsetOn(); - - } - - void setListener(Listener listener) { - mListener = listener; - } - - boolean isPluggedIn() { - return mIsPluggedIn; - } - - void registerReceiver() { - // Register for misc other intent broadcasts. - IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - mContext.registerReceiver(mReceiver, intentFilter); - } - - void unregisterReceiver() { - mContext.unregisterReceiver(mReceiver); - } - - private void onHeadsetPluggedInChanged(boolean isPluggedIn) { - if (mIsPluggedIn != isPluggedIn) { - Log.v(TAG, "onHeadsetPluggedInChanged, mIsPluggedIn: " + mIsPluggedIn + " -> " - + isPluggedIn); - boolean oldIsPluggedIn = mIsPluggedIn; - mIsPluggedIn = isPluggedIn; - if (mListener != null) { - mListener.onWiredHeadsetPluggedInChanged(oldIsPluggedIn, mIsPluggedIn); - } - } - } -}
\ No newline at end of file diff --git a/src/com/android/dialer/widget/ActionBarController.java b/src/com/android/dialer/widget/ActionBarController.java deleted file mode 100644 index edf57b163..000000000 --- a/src/com/android/dialer/widget/ActionBarController.java +++ /dev/null @@ -1,243 +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.widget; - -import com.google.common.annotations.VisibleForTesting; - -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.os.Bundle; -import android.util.Log; - -import com.android.dialer.DialtactsActivity; -import com.android.phone.common.animation.AnimUtils.AnimationCallback; - -/** - * Controls the various animated properties of the actionBar: showing/hiding, fading/revealing, - * and collapsing/expanding, and assigns suitable properties to the actionBar based on the - * current state of the UI. - */ -public class ActionBarController { - public static final boolean DEBUG = DialtactsActivity.DEBUG; - public static final String TAG = "ActionBarController"; - private static final String KEY_IS_SLID_UP = "key_actionbar_is_slid_up"; - private static final String KEY_IS_FADED_OUT = "key_actionbar_is_faded_out"; - private static final String KEY_IS_EXPANDED = "key_actionbar_is_expanded"; - - private ActivityUi mActivityUi; - private SearchEditTextLayout mSearchBox; - - private boolean mIsActionBarSlidUp; - - private final AnimationCallback mFadeOutCallback = new AnimationCallback() { - @Override - public void onAnimationEnd() { - slideActionBar(true /* slideUp */, false /* animate */); - } - - @Override - public void onAnimationCancel() { - slideActionBar(true /* slideUp */, false /* animate */); - } - }; - - public interface ActivityUi { - public boolean isInSearchUi(); - public boolean hasSearchQuery(); - public boolean shouldShowActionBar(); - public int getActionBarHeight(); - public int getActionBarHideOffset(); - public void setActionBarHideOffset(int offset); - } - - public ActionBarController(ActivityUi activityUi, SearchEditTextLayout searchBox) { - mActivityUi = activityUi; - mSearchBox = searchBox; - } - - /** - * @return Whether or not the action bar is currently showing (both slid down and visible) - */ - public boolean isActionBarShowing() { - return !mIsActionBarSlidUp && !mSearchBox.isFadedOut(); - } - - /** - * Called when the user has tapped on the collapsed search box, to start a new search query. - */ - public void onSearchBoxTapped() { - if (DEBUG) { - Log.d(TAG, "OnSearchBoxTapped: isInSearchUi " + mActivityUi.isInSearchUi()); - } - if (!mActivityUi.isInSearchUi()) { - mSearchBox.expand(true /* animate */, true /* requestFocus */); - } - } - - /** - * Called when search UI has been exited for some reason. - */ - public void onSearchUiExited() { - if (DEBUG) { - Log.d(TAG, "OnSearchUIExited: isExpanded " + mSearchBox.isExpanded() - + " isFadedOut: " + mSearchBox.isFadedOut() - + " shouldShowActionBar: " + mActivityUi.shouldShowActionBar()); - } - if (mSearchBox.isExpanded()) { - mSearchBox.collapse(true /* animate */); - } - if (mSearchBox.isFadedOut()) { - mSearchBox.fadeIn(); - } - - if (mActivityUi.shouldShowActionBar()) { - slideActionBar(false /* slideUp */, false /* animate */); - } else { - slideActionBar(true /* slideUp */, false /* animate */); - } - } - - /** - * Called to indicate that the user is trying to hide the dialpad. Should be called before - * any state changes have actually occurred. - */ - public void onDialpadDown() { - if (DEBUG) { - Log.d(TAG, "OnDialpadDown: isInSearchUi " + mActivityUi.isInSearchUi() - + " hasSearchQuery: " + mActivityUi.hasSearchQuery() - + " isFadedOut: " + mSearchBox.isFadedOut() - + " isExpanded: " + mSearchBox.isExpanded()); - } - if (mActivityUi.isInSearchUi()) { - if (mActivityUi.hasSearchQuery()) { - if (mSearchBox.isFadedOut()) { - mSearchBox.setVisible(true); - } - if (!mSearchBox.isExpanded()) { - mSearchBox.expand(false /* animate */, false /* requestFocus */); - } - slideActionBar(false /* slideUp */, true /* animate */); - } else { - mSearchBox.fadeIn(); - } - } - } - - /** - * Called to indicate that the user is trying to show the dialpad. Should be called before - * any state changes have actually occurred. - */ - public void onDialpadUp() { - if (DEBUG) { - Log.d(TAG, "OnDialpadUp: isInSearchUi " + mActivityUi.isInSearchUi()); - } - if (mActivityUi.isInSearchUi()) { - slideActionBar(true /* slideUp */, true /* animate */); - } else { - // From the lists fragment - mSearchBox.fadeOut(mFadeOutCallback); - } - } - - public void slideActionBar(boolean slideUp, boolean animate) { - if (DEBUG) { - Log.d(TAG, "Sliding actionBar - up: " + slideUp + " animate: " + animate); - } - if (animate) { - ValueAnimator animator = - slideUp ? ValueAnimator.ofFloat(0, 1) : ValueAnimator.ofFloat(1, 0); - animator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final float value = (float) animation.getAnimatedValue(); - setHideOffset( - (int) (mActivityUi.getActionBarHeight() * value)); - } - }); - animator.start(); - } else { - setHideOffset(slideUp ? mActivityUi.getActionBarHeight() : 0); - } - mIsActionBarSlidUp = slideUp; - } - - public void setAlpha(float alphaValue) { - mSearchBox.animate().alpha(alphaValue).start(); - } - - public void setHideOffset(int offset) { - mIsActionBarSlidUp = offset >= mActivityUi.getActionBarHeight(); - mActivityUi.setActionBarHideOffset(offset); - } - - /** - * @return The offset the action bar is being translated upwards by - */ - public int getHideOffset() { - return mActivityUi.getActionBarHideOffset(); - } - - public int getActionBarHeight() { - return mActivityUi.getActionBarHeight(); - } - - /** - * Saves the current state of the action bar into a provided {@link Bundle} - */ - public void saveInstanceState(Bundle outState) { - outState.putBoolean(KEY_IS_SLID_UP, mIsActionBarSlidUp); - outState.putBoolean(KEY_IS_FADED_OUT, mSearchBox.isFadedOut()); - outState.putBoolean(KEY_IS_EXPANDED, mSearchBox.isExpanded()); - } - - /** - * Restores the action bar state from a provided {@link Bundle}. - */ - public void restoreInstanceState(Bundle inState) { - mIsActionBarSlidUp = inState.getBoolean(KEY_IS_SLID_UP); - - final boolean isSearchBoxFadedOut = inState.getBoolean(KEY_IS_FADED_OUT); - if (isSearchBoxFadedOut) { - if (!mSearchBox.isFadedOut()) { - mSearchBox.setVisible(false); - } - } else if (mSearchBox.isFadedOut()) { - mSearchBox.setVisible(true); - } - - final boolean isSearchBoxExpanded = inState.getBoolean(KEY_IS_EXPANDED); - if (isSearchBoxExpanded) { - if (!mSearchBox.isExpanded()) { - mSearchBox.expand(false, false); - } - } else if (mSearchBox.isExpanded()) { - mSearchBox.collapse(false); - } - } - - /** - * This should be called after onCreateOptionsMenu has been called, when the actionbar has - * been laid out and actually has a height. - */ - public void restoreActionBarOffset() { - slideActionBar(mIsActionBarSlidUp /* slideUp */, false /* animate */); - } - - @VisibleForTesting - public boolean getIsActionBarSlidUp() { - return mIsActionBarSlidUp; - } -} diff --git a/src/com/android/dialer/widget/EmptyContentView.java b/src/com/android/dialer/widget/EmptyContentView.java deleted file mode 100644 index 719fd3ff8..000000000 --- a/src/com/android/dialer/widget/EmptyContentView.java +++ /dev/null @@ -1,118 +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.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.dialer.R; - -public class EmptyContentView extends LinearLayout implements View.OnClickListener { - - public static final int NO_LABEL = 0; - public static final int NO_IMAGE = 0; - - private ImageView mImageView; - private TextView mDescriptionView; - private TextView mActionView; - private OnEmptyViewActionButtonClickedListener mOnActionButtonClickedListener; - - public interface OnEmptyViewActionButtonClickedListener { - public void onEmptyViewActionButtonClicked(); - } - - public EmptyContentView(Context context) { - this(context, null); - } - - public EmptyContentView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public EmptyContentView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public EmptyContentView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setOrientation(LinearLayout.VERTICAL); - - final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.empty_content_view, this); - // Don't let touches fall through the empty view. - setClickable(true); - mImageView = (ImageView) findViewById(R.id.emptyListViewImage); - mDescriptionView = (TextView) findViewById(R.id.emptyListViewMessage); - mActionView = (TextView) findViewById(R.id.emptyListViewAction); - mActionView.setOnClickListener(this); - } - - public void setDescription(int resourceId) { - if (resourceId == NO_LABEL) { - mDescriptionView.setText(null); - mDescriptionView.setVisibility(View.GONE); - } else { - mDescriptionView.setText(resourceId); - mDescriptionView.setVisibility(View.VISIBLE); - } - } - - public void setImage(int resourceId) { - if (resourceId == NO_LABEL) { - mImageView.setImageDrawable(null); - mImageView.setVisibility(View.GONE); - } else { - mImageView.setImageResource(resourceId); - mImageView.setVisibility(View.VISIBLE); - } - } - - public void setActionLabel(int resourceId) { - if (resourceId == NO_LABEL) { - mActionView.setText(null); - mActionView.setVisibility(View.GONE); - } else { - mActionView.setText(resourceId); - mActionView.setVisibility(View.VISIBLE); - } - } - - public boolean isShowingContent() { - return mImageView.getVisibility() == View.VISIBLE - || mDescriptionView.getVisibility() == View.VISIBLE - || mActionView.getVisibility() == View.VISIBLE; - } - - public void setActionClickedListener(OnEmptyViewActionButtonClickedListener listener) { - mOnActionButtonClickedListener = listener; - } - - @Override - public void onClick(View v) { - if (mOnActionButtonClickedListener != null) { - mOnActionButtonClickedListener.onEmptyViewActionButtonClicked(); - } - } -} diff --git a/src/com/android/dialer/widget/SearchEditTextLayout.java b/src/com/android/dialer/widget/SearchEditTextLayout.java deleted file mode 100644 index 4f100dc44..000000000 --- a/src/com/android/dialer/widget/SearchEditTextLayout.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2014 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.widget; - -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.util.AttributeSet; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.View; -import android.widget.EditText; -import android.widget.FrameLayout; - -import com.android.dialer.R; -import com.android.dialer.util.DialerUtils; -import com.android.phone.common.animation.AnimUtils; - -public class SearchEditTextLayout extends FrameLayout { - private static final float EXPAND_MARGIN_FRACTION_START = 0.8f; - private static final int ANIMATION_DURATION = 200; - - private OnKeyListener mPreImeKeyListener; - private int mTopMargin; - private int mBottomMargin; - private int mLeftMargin; - private int mRightMargin; - - private float mCollapsedElevation; - - /* Subclass-visible for testing */ - protected boolean mIsExpanded = false; - protected boolean mIsFadedOut = false; - - private View mCollapsed; - private View mExpanded; - private EditText mSearchView; - private View mSearchIcon; - private View mCollapsedSearchBox; - private View mVoiceSearchButtonView; - private View mOverflowButtonView; - private View mBackButtonView; - private View mExpandedSearchBox; - private View mClearButtonView; - - private ValueAnimator mAnimator; - - private Callback mCallback; - - /** - * Listener for the back button next to the search view being pressed - */ - public interface Callback { - public void onBackButtonClicked(); - public void onSearchViewClicked(); - } - - public SearchEditTextLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setPreImeKeyListener(OnKeyListener listener) { - mPreImeKeyListener = listener; - } - - public void setCallback(Callback listener) { - mCallback = listener; - } - - @Override - protected void onFinishInflate() { - MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); - mTopMargin = params.topMargin; - mBottomMargin = params.bottomMargin; - mLeftMargin = params.leftMargin; - mRightMargin = params.rightMargin; - - mCollapsedElevation = getElevation(); - - mCollapsed = findViewById(R.id.search_box_collapsed); - mExpanded = findViewById(R.id.search_box_expanded); - mSearchView = (EditText) mExpanded.findViewById(R.id.search_view); - - mSearchIcon = findViewById(R.id.search_magnifying_glass); - mCollapsedSearchBox = findViewById(R.id.search_box_start_search); - mVoiceSearchButtonView = findViewById(R.id.voice_search_button); - mOverflowButtonView = findViewById(R.id.dialtacts_options_menu_button); - mBackButtonView = findViewById(R.id.search_back_button); - mExpandedSearchBox = findViewById(R.id.search_box_expanded); - mClearButtonView = findViewById(R.id.search_close_button); - - // Convert a long click into a click to expand the search box, and then long click on the - // search view. This accelerates the long-press scenario for copy/paste. - mCollapsedSearchBox.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - mCollapsedSearchBox.performClick(); - mSearchView.performLongClick(); - return false; - } - }); - - mSearchView.setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - DialerUtils.showInputMethod(v); - } else { - DialerUtils.hideInputMethod(v); - } - } - }); - - mSearchView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mCallback != null) { - mCallback.onSearchViewClicked(); - } - } - }); - - mSearchView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - mClearButtonView.setVisibility(TextUtils.isEmpty(s) ? View.GONE : View.VISIBLE); - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - - findViewById(R.id.search_close_button).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mSearchView.setText(null); - } - }); - - findViewById(R.id.search_back_button).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mCallback != null) { - mCallback.onBackButtonClicked(); - } - } - }); - - super.onFinishInflate(); - } - - @Override - public boolean dispatchKeyEventPreIme(KeyEvent event) { - if (mPreImeKeyListener != null) { - if (mPreImeKeyListener.onKey(this, event.getKeyCode(), event)) { - return true; - } - } - return super.dispatchKeyEventPreIme(event); - } - - public void fadeOut() { - fadeOut(null); - } - - public void fadeOut(AnimUtils.AnimationCallback callback) { - AnimUtils.fadeOut(this, ANIMATION_DURATION, callback); - mIsFadedOut = true; - } - - public void fadeIn() { - AnimUtils.fadeIn(this, ANIMATION_DURATION); - mIsFadedOut = false; - } - - public void setVisible(boolean visible) { - if (visible) { - setAlpha(1); - setVisibility(View.VISIBLE); - mIsFadedOut = false; - } else { - setAlpha(0); - setVisibility(View.GONE); - mIsFadedOut = true; - } - } - - public void expand(boolean animate, boolean requestFocus) { - updateVisibility(true /* isExpand */); - - if (animate) { - AnimUtils.crossFadeViews(mExpanded, mCollapsed, ANIMATION_DURATION); - mAnimator = ValueAnimator.ofFloat(EXPAND_MARGIN_FRACTION_START, 0f); - setMargins(EXPAND_MARGIN_FRACTION_START); - prepareAnimator(true); - } else { - mExpanded.setVisibility(View.VISIBLE); - mExpanded.setAlpha(1); - setMargins(0f); - mCollapsed.setVisibility(View.GONE); - } - - // Set 9-patch background. This owns the padding, so we need to restore the original values. - int paddingTop = this.getPaddingTop(); - int paddingStart = this.getPaddingStart(); - int paddingBottom = this.getPaddingBottom(); - int paddingEnd = this.getPaddingEnd(); - setBackgroundResource(R.drawable.search_shadow); - setElevation(0); - setPaddingRelative(paddingStart, paddingTop, paddingEnd, paddingBottom); - - if (requestFocus) { - mSearchView.requestFocus(); - } - mIsExpanded = true; - } - - public void collapse(boolean animate) { - updateVisibility(false /* isExpand */); - - if (animate) { - AnimUtils.crossFadeViews(mCollapsed, mExpanded, ANIMATION_DURATION); - mAnimator = ValueAnimator.ofFloat(0f, 1f); - prepareAnimator(false); - } else { - mCollapsed.setVisibility(View.VISIBLE); - mCollapsed.setAlpha(1); - setMargins(1f); - mExpanded.setVisibility(View.GONE); - } - - mIsExpanded = false; - setElevation(mCollapsedElevation); - setBackgroundResource(R.drawable.rounded_corner); - } - - /** - * Updates the visibility of views depending on whether we will show the expanded or collapsed - * search view. This helps prevent some jank with the crossfading if we are animating. - * - * @param isExpand Whether we are about to show the expanded search box. - */ - private void updateVisibility(boolean isExpand) { - int collapsedViewVisibility = isExpand ? View.GONE : View.VISIBLE; - int expandedViewVisibility = isExpand ? View.VISIBLE : View.GONE; - - mSearchIcon.setVisibility(collapsedViewVisibility); - mCollapsedSearchBox.setVisibility(collapsedViewVisibility); - mVoiceSearchButtonView.setVisibility(collapsedViewVisibility); - mOverflowButtonView.setVisibility(collapsedViewVisibility); - mBackButtonView.setVisibility(expandedViewVisibility); - // TODO: Prevents keyboard from jumping up in landscape mode after exiting the - // SearchFragment when the query string is empty. More elegant fix? - //mExpandedSearchBox.setVisibility(expandedViewVisibility); - if (TextUtils.isEmpty(mSearchView.getText())) { - mClearButtonView.setVisibility(View.GONE); - } else { - mClearButtonView.setVisibility(expandedViewVisibility); - } - } - - private void prepareAnimator(final boolean expand) { - if (mAnimator != null) { - mAnimator.cancel(); - } - - mAnimator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final Float fraction = (Float) animation.getAnimatedValue(); - setMargins(fraction); - } - }); - - mAnimator.setDuration(ANIMATION_DURATION); - mAnimator.start(); - } - - public boolean isExpanded() { - return mIsExpanded; - } - - public boolean isFadedOut() { - return mIsFadedOut; - } - - /** - * Assigns margins to the search box as a fraction of its maximum margin size - * - * @param fraction How large the margins should be as a fraction of their full size - */ - private void setMargins(float fraction) { - MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); - params.topMargin = (int) (mTopMargin * fraction); - params.bottomMargin = (int) (mBottomMargin * fraction); - params.leftMargin = (int) (mLeftMargin * fraction); - params.rightMargin = (int) (mRightMargin * fraction); - requestLayout(); - } -} |