diff options
13 files changed, 530 insertions, 199 deletions
diff --git a/res/layout/blocked_number_fragment.xml b/res/layout/blocked_number_fragment.xml index e86ccb596..bb4b7f20f 100644 --- a/res/layout/blocked_number_fragment.xml +++ b/res/layout/blocked_number_fragment.xml @@ -27,23 +27,22 @@ android:layout_height="wrap_content" card_view:cardCornerRadius="0dp"> - <ListView android:id="@id/android:list" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background_dialer_white" - android:layout_weight="1" - android:drawSelectorOnTop="false" - android:headerDividersEnabled="false" /> - <LinearLayout - android:id="@android:id/empty" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical"> + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/background_dialer_white"> <include layout="@layout/blocked_number_header" /> - <TextView android:id="@id/android:empty" + <ListView android:id="@id/android:list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:drawSelectorOnTop="false" + android:headerDividersEnabled="false" /> + + <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingStart="@dimen/blocked_number_horizontal_margin" diff --git a/res/layout/blocked_number_header.xml b/res/layout/blocked_number_header.xml index 95880a7c0..39f532281 100644 --- a/res/layout/blocked_number_header.xml +++ b/res/layout/blocked_number_header.xml @@ -13,10 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" @@ -26,7 +23,7 @@ android:padding="@dimen/blocked_number_container_padding" style="@android:style/TextAppearance.Material.Subhead" /> - <RelativeLayout android:id="@+id/importsettings" + <RelativeLayout android:id="@+id/import_settings" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone"> @@ -77,4 +74,4 @@ android:layout_marginTop="8dp" android:text="@string/blockNumber" /> -</LinearLayout> +</merge> diff --git a/res/values/strings.xml b/res/values/strings.xml index cd7ce8fa8..0b7d82c61 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -833,7 +833,7 @@ You previously marked some callers to be automatically sent to voicemail via other apps. </string> - <!-- Labe for button to view numbers of contacts previous marked to be sent to voicemail. + <!-- Label for button to view numbers of contacts previous marked to be sent to voicemail. [CHAR_LIMIT=20] --> <string name="blocked_call_settings_view_numbers_button">View Numbers</string> @@ -841,6 +841,9 @@ list. [CHAR_LIMIT=20] --> <string name="blocked_call_settings_import_button">Import</string> + <!-- Error toast message for when send to voicemail import fails. [CHAR LIMIT=40] --> + <string name="send_to_voicemail_import_failed">Import failed</string> + <!-- String describing the delete icon on a blocked number list item. When tapped, it will show a dialog confirming the unblocking of the number. [CHAR LIMIT=NONE]--> @@ -853,7 +856,7 @@ <!-- Button to bring up UI to add a number to the blocked call list. [CHAR LIMIT=40] --> <string name="blockNumber">Add number</string> - <!-- Heading for the block list in the "Spam and blocked calls" settings. [CHAR LIMIT=64] --> + <!-- Heading for the block list in the "Spam and blocked cal)ls" settings. [CHAR LIMIT=64] --> <string name="blockList">Block list</string> <!-- Label for progress dialog when validating a number to be added to the block list. diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java index 654a96fcf..ef219800f 100644 --- a/src/com/android/dialer/CallDetailActivity.java +++ b/src/com/android/dialer/CallDetailActivity.java @@ -49,14 +49,15 @@ 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.CallLogAsyncTaskListener; -import com.android.dialer.onboard.OnboardingActivity; import com.android.dialer.calllog.CallLogAsyncTaskUtil; import com.android.dialer.calllog.CallTypeHelper; import com.android.dialer.calllog.ContactInfoHelper; import com.android.dialer.calllog.PhoneAccountUtils; -import com.android.dialer.database.FilteredNumberAsyncQueryHandler; import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; +import com.android.dialer.database.FilteredNumberAsyncQueryHandler; import com.android.dialer.filterednumber.FilterNumberDialogFragment; +import com.android.dialer.filterednumber.FilteredNumbersUtil; +import com.android.dialer.onboard.OnboardingActivity; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil.CallIntentBuilder; import com.android.dialer.util.PhoneNumberUtil; @@ -106,17 +107,9 @@ public class CallDetailActivity extends AppCompatActivity // 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(); + mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString(); mDisplayNumber = mDetails.displayNumber; final int numberPresentation = mDetails.numberPresentation; - final Uri contactUri = mDetails.contactUri; - final Uri photoUri = mDetails.photoUri; - final PhoneAccountHandle accountHandle = mDetails.accountHandle; - - // Cache the details about the phone number. - mIsVoicemailNumber = - PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, mNumber); final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails); @@ -137,7 +130,8 @@ public class CallDetailActivity extends AppCompatActivity } } - String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, accountHandle); + String accountLabel = + PhoneAccountUtils.getAccountLabel(mContext, mDetails.accountHandle); if (!TextUtils.isEmpty(accountLabel)) { mAccountLabel.setText(accountLabel); mAccountLabel.setVisibility(View.VISIBLE); @@ -150,8 +144,10 @@ public class CallDetailActivity extends AppCompatActivity mCallButton.setVisibility(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 && !mIsVoicemailNumber; + canPlaceCallsTo && !isSipNumber && !isVoicemailNumber; mEditBeforeCallActionItem.setVisibility( showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE); @@ -160,31 +156,13 @@ public class CallDetailActivity extends AppCompatActivity mReportActionItem.setVisibility( showReportAction ? View.VISIBLE : View.GONE); - updateBlockActionItem(); invalidateOptionsMenu(); mHistoryList.setAdapter( new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details)); - String lookupKey = contactUri == null ? null - : UriUtils.getLookupKeyFromUri(contactUri); - - final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType); + updatePhotoAndBlockActionItem(); - final int contactType = - mIsVoicemailNumber ? ContactPhotoManager.TYPE_VOICEMAIL : - isBusiness ? ContactPhotoManager.TYPE_BUSINESS : - ContactPhotoManager.TYPE_DEFAULT; - - String nameForDefaultImage; - if (TextUtils.isEmpty(mDetails.name)) { - nameForDefaultImage = mDetails.displayNumber; - } else { - nameForDefaultImage = mDetails.name.toString(); - } - - loadContactPhotos( - contactUri, photoUri, nameForDefaultImage, lookupKey, contactType); findViewById(R.id.call_detail).setVisibility(View.VISIBLE); } @@ -217,7 +195,6 @@ public class CallDetailActivity extends AppCompatActivity private PhoneCallDetails mDetails; protected String mNumber; private Uri mVoicemailUri; - private boolean mIsVoicemailNumber; private String mDefaultCountryIso; private String mDisplayNumber; @@ -275,6 +252,9 @@ public class CallDetailActivity extends AppCompatActivity mCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { + if (TextUtils.isEmpty(mNumber)) { + return; + } mContext.startActivity( new CallIntentBuilder(mNumber) .setCallInitiationType(LogState.INITIATION_CALL_DETAILS) @@ -339,21 +319,6 @@ public class CallDetailActivity extends AppCompatActivity return uris; } - /** Load the contact photos and places them in the corresponding views. */ - private void loadContactPhotos(Uri contactUri, Uri photoUri, String displayName, - String lookupKey, int contactType) { - - final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey, - contactType, true /* isCircular */); - - mQuickContactBadge.assignContactUri(contactUri); - mQuickContactBadge.setContentDescription( - mResources.getString(R.string.description_contact_details, displayName)); - - mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, photoUri, - false /* darkTheme */, true /* isCircular */, request); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { final MenuItem deleteMenuItem = menu.add( @@ -399,7 +364,7 @@ public class CallDetailActivity extends AppCompatActivity mBlockedNumberId, null /* normalizedNumber */, mNumber, - null /* countryIso */, + mDetails.countryIso, mDisplayNumber, R.id.call_detail, getFragmentManager(), @@ -420,32 +385,81 @@ public class CallDetailActivity extends AppCompatActivity @Override public void onChangeFilteredNumberSuccess() { - updateBlockActionItem(); + updatePhotoAndBlockActionItem(); } @Override public void onChangeFilteredNumberUndo() { - updateBlockActionItem(); + updatePhotoAndBlockActionItem(); } - private void updateBlockActionItem() { + private void updatePhotoAndBlockActionItem() { + if (mDetails == null || !FilteredNumbersUtil.canBlockNumber(this, mNumber)) { + return; + } + + boolean failed = mFilteredNumberAsyncQueryHandler.startBlockedQuery( + new OnCheckBlockedListener() { + @Override + public void onCheckComplete(Integer id) { + mBlockedNumberId = id; + + updateContactPhoto(); + updateBlockActionItem(); + } + }, null, mNumber, mDetails.countryIso); + + if (failed) { + updateContactPhoto(); + updateBlockActionItem(); + } + } + + // Loads and displays the contact photo. + private void updateContactPhoto() { if (mDetails == null) { return; } - mFilteredNumberAsyncQueryHandler.startBlockedQuery(new OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - mBlockedNumberId = id; - if (mBlockedNumberId == null) { - mBlockNumberActionItem.setText(R.string.action_block_number); - } else { - mBlockNumberActionItem.setText(R.string.action_unblock_number); - } + if (mBlockedNumberId != null) { + mQuickContactBadge.setImageDrawable(mContext.getDrawable(R.drawable.blocked_contact)); + return; + } - mBlockNumberActionItem.setVisibility(View.VISIBLE); - } - }, null, mNumber, mDetails.countryIso); + 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.name) + ? mDetails.displayNumber : mDetails.name.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); + } else { + mBlockNumberActionItem.setText(R.string.action_unblock_number); + } + + mBlockNumberActionItem.setVisibility(View.VISIBLE); } private void closeSystemDialogs() { diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 7f3a779ea..0ee7e935d 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -314,6 +314,10 @@ public class CallLogAdapter extends GroupingListAdapter public void onPause() { pauseCache(); + + // Clear blocked id cache so that changes in blocked status will be reflected in the UI. + mBlockedIdCache.clear(); + if (mHiddenItemUri != null) { CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null); } diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java index 8f2f4ba45..23c77d383 100644 --- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java +++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java @@ -27,6 +27,7 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.telecom.PhoneAccountHandle; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.ContextMenu; import android.view.MenuItem; @@ -48,6 +49,7 @@ import com.android.dialer.DialtactsActivity; import com.android.dialer.R; import com.android.dialer.database.FilteredNumberAsyncQueryHandler; import com.android.dialer.filterednumber.FilterNumberDialogFragment; +import com.android.dialer.filterednumber.FilteredNumbersUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.voicemail.VoicemailPlaybackPresenter; @@ -301,22 +303,24 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder .setOnMenuItemClickListener(this); } - mFilteredNumberAsyncQueryHandler.startBlockedQuery( - 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); - } - }, info.normalizedNumber, number, countryIso); + if (FilteredNumbersUtil.canBlockNumber(mContext, number)) { + mFilteredNumberAsyncQueryHandler.startBlockedQuery( + 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); + } + }, info.normalizedNumber, number, countryIso); + } } @Override diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java index 4eb74bd29..594ceebee 100644 --- a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java +++ b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java @@ -39,9 +39,13 @@ import com.android.contacts.common.util.PermissionsUtil; import com.android.dialer.DialtactsActivity; import com.android.dialer.R; import com.android.dialer.calllog.PhoneAccountUtils; +import com.android.dialer.filterednumber.FilteredNumbersUtil; import com.android.dialer.list.ListsFragment; import com.google.common.collect.Maps; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; /** @@ -96,14 +100,14 @@ public class DefaultVoicemailNotifier { 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 NewCall[] newCalls = mNewCallsQuery.query(); + final List<NewCall> newCalls = mNewCallsQuery.query(); if (newCalls == null) { // Query failed, just return. return; } - if (newCalls.length == 0) { + if (newCalls.size() == 0) { // No voicemails to notify about: clear the notification. clearNotification(); return; @@ -122,7 +126,16 @@ public class DefaultVoicemailNotifier { NewCall callToNotify = null; // Iterate over the new voicemails to determine all the information above. - for (NewCall newCall : newCalls) { + Iterator<NewCall> itr = newCalls.iterator(); + while (itr.hasNext()) { + NewCall newCall = itr.next(); + + // Skip notifying for numbers which are blocked. + if (FilteredNumbersUtil.isBlocked(mContext, newCall.number, newCall.countryIso)) { + itr.remove(); + continue; + } + // Check if we already know the name associated with this number. String name = names.get(newCall.number); if (name == null) { @@ -155,10 +168,14 @@ public class DefaultVoicemailNotifier { } } + if (newCalls.isEmpty()) { + return; + } + // If there is only one voicemail, set its transcription as the "long text". String transcription = null; - if (newCalls.length == 1) { - transcription = newCalls[0].transcription; + if (newCalls.size() == 1) { + transcription = newCalls.get(0).transcription; } if (newCallUri != null && callToNotify == null) { @@ -167,7 +184,7 @@ public class DefaultVoicemailNotifier { // Determine the title of the notification and the icon for it. final String title = resources.getQuantityString( - R.plurals.notification_voicemail_title, newCalls.length, newCalls.length); + 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; @@ -218,6 +235,7 @@ public class DefaultVoicemailNotifier { public final String accountComponentName; public final String accountId; public final String transcription; + public final String countryIso; public NewCall( Uri callsUri, @@ -226,7 +244,8 @@ public class DefaultVoicemailNotifier { int numberPresentation, String accountComponentName, String accountId, - String transcription) { + String transcription, + String countryIso) { this.callsUri = callsUri; this.voicemailUri = voicemailUri; this.number = number; @@ -234,6 +253,7 @@ public class DefaultVoicemailNotifier { this.accountComponentName = accountComponentName; this.accountId = accountId; this.transcription = transcription; + this.countryIso = countryIso; } } @@ -242,7 +262,7 @@ public class DefaultVoicemailNotifier { /** * Returns the new calls for which a notification should be generated. */ - public NewCall[] query(); + public List<NewCall> query(); } /** Create a new instance of {@link NewCallsQuery}. */ @@ -263,7 +283,8 @@ public class DefaultVoicemailNotifier { Calls.NUMBER_PRESENTATION, Calls.PHONE_ACCOUNT_COMPONENT_NAME, Calls.PHONE_ACCOUNT_ID, - Calls.TRANSCRIPTION + Calls.TRANSCRIPTION, + Calls.COUNTRY_ISO }; private static final int ID_COLUMN_INDEX = 0; private static final int NUMBER_COLUMN_INDEX = 1; @@ -272,6 +293,7 @@ public class DefaultVoicemailNotifier { 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 final ContentResolver mContentResolver; private final Context mContext; @@ -282,7 +304,7 @@ public class DefaultVoicemailNotifier { } @Override - public NewCall[] query() { + public List<NewCall> query() { if (!PermissionsUtil.hasPermission(mContext, READ_CALL_LOG)) { Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup."); return null; @@ -296,9 +318,9 @@ public class DefaultVoicemailNotifier { if (cursor == null) { return null; } - NewCall[] newCalls = new NewCall[cursor.getCount()]; + List<NewCall> newCalls = new LinkedList<NewCall>(); while (cursor.moveToNext()) { - newCalls[cursor.getPosition()] = createNewCallsFromCursor(cursor); + newCalls.add(createNewCallsFromCursor(cursor)); } return newCalls; } catch (RuntimeException e) { @@ -322,7 +344,8 @@ public class DefaultVoicemailNotifier { 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(TRANSCRIPTION_COLUMN_INDEX), + cursor.getString(COUNTRY_ISO_COLUMN_INDEX)); } } diff --git a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java index c5d2f6f70..25613a6f8 100644 --- a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java +++ b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java @@ -202,7 +202,9 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { new Listener() { @Override public void onInsertComplete(int token, Object cookie, Uri uri) { - listener.onBlockComplete(uri); + if (listener != null ) { + listener.onBlockComplete(uri); + } } }, getContentUri(null), values); } @@ -241,7 +243,9 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { startDelete(NO_TOKEN, new Listener() { @Override public void onDeleteComplete(int token, Object cookie, int result) { - listener.onUnblockComplete(result, values); + if (listener != null) { + listener.onUnblockComplete(result, values); + } } }, uri, null, null); } diff --git a/src/com/android/dialer/filterednumber/BlockedNumberFragment.java b/src/com/android/dialer/filterednumber/BlockedNumberFragment.java index 10d6ff6ee..43f734d8d 100644 --- a/src/com/android/dialer/filterednumber/BlockedNumberFragment.java +++ b/src/com/android/dialer/filterednumber/BlockedNumberFragment.java @@ -21,36 +21,28 @@ import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; import android.database.Cursor; -import android.os.AsyncTask; import android.os.Bundle; -import android.provider.ContactsContract.Contacts; 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.CheckForSendToVoicemailContactListener; +import com.android.dialer.filterednumber.FilteredNumbersUtil.ImportSendToVoicemailContactsListener; public class BlockedNumberFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener { - private static class SendToVoicemailContactQuery { - static final String[] PROJECTION = { - Contacts._ID - }; - - static final String SELECT_SEND_TO_VOICEMAIL_TRUE = Contacts.SEND_TO_VOICEMAIL + "=1"; - } - private BlockedNumberAdapter mAdapter; + private View mImportSettings; + private View mImportButton; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - LayoutInflater inflater = LayoutInflater.from(getContext()); - getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null)); if (mAdapter == null) { mAdapter = BlockedNumberAdapter.newBlockedNumberAdapter( getContext(), getActivity().getFragmentManager()); @@ -58,9 +50,21 @@ public class BlockedNumberFragment extends ListFragment implements setListAdapter(mAdapter); getActivity().findViewById(R.id.add_number_button).setOnClickListener(this); - getListView().getEmptyView().findViewById(R.id.add_number_button).setOnClickListener(this); - mImportSettings = getActivity().findViewById(R.id.importsettings); + mImportSettings = getActivity().findViewById(R.id.import_settings); + mImportButton = getActivity().findViewById(R.id.import_button); + mImportButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + FilteredNumbersUtil.importSendToVoicemailContacts( + getActivity(), new ImportSendToVoicemailContactsListener() { + @Override + public void onImportComplete() { + mImportSettings.setVisibility(View.GONE); + } + }); + } + }); } @Override @@ -78,7 +82,15 @@ public class BlockedNumberFragment extends ListFragment implements @Override public void onResume() { super.onResume(); - checkForSendToVoicemailContact(); + + FilteredNumbersUtil.checkForSendToVoicemailContact( + getActivity(), new CheckForSendToVoicemailContactListener() { + @Override + public void onComplete(boolean hasSendToVoicemailContact) { + final int visibility = hasSendToVoicemailContact ? View.VISIBLE : View.GONE; + mImportSettings.setVisibility(visibility); + } + }); } @Override @@ -122,44 +134,4 @@ public class BlockedNumberFragment extends ListFragment implements manageBlockedNumbersActivity.enterSearchUi(); } } - - /** - * Checks if there exists a contact with {@code Contacts.SEND_TO_VOICEMAIL} set to true, - * and updates the visibility of the import settings buttons accordingly. - */ - private void checkForSendToVoicemailContact() { - final AsyncTask task = new AsyncTask<Object, Void, Boolean>() { - @Override - public Boolean doInBackground(Object[] params) { - if (getActivity() == null) { - return false; - } - - final Cursor cursor = getActivity().getContentResolver().query( - Contacts.CONTENT_URI, - SendToVoicemailContactQuery.PROJECTION, - SendToVoicemailContactQuery.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) { - final int visibility = hasSendToVoicemailContact ? View.VISIBLE : View.GONE; - mImportSettings.setVisibility(visibility); - } - }; - task.execute(); - } } diff --git a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java index a1260eebf..affb37b15 100644 --- a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java +++ b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java @@ -26,7 +26,9 @@ import android.content.res.Resources; 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.dialer.R; import com.android.dialer.database.FilteredNumberAsyncQueryHandler; @@ -57,6 +59,7 @@ public class FilterNumberDialogFragment extends DialogFragment { private static final String ARG_PARENT_VIEW_ID = "parentViewId"; private String mDisplayNumber; + private String mNormalizedNumber; private FilteredNumberAsyncQueryHandler mHandler; private View mParentView; @@ -105,7 +108,14 @@ public class FilterNumberDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreateDialog(savedInstanceState); final boolean isBlocked = getArguments().containsKey(ARG_BLOCK_ID); + mDisplayNumber = getArguments().getString(ARG_DISPLAY_NUMBER); + if (TextUtils.isEmpty(mNormalizedNumber)) { + String number = getArguments().getString(ARG_NUMBER); + String countryIso = getArguments().getString(ARG_COUNTRY_ISO); + mNormalizedNumber = + FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso); + } mHandler = new FilteredNumberAsyncQueryHandler(getContext().getContentResolver()); mParentView = getActivity().findViewById(getArguments().getInt(ARG_PARENT_VIEW_ID)); @@ -136,6 +146,18 @@ public class FilterNumberDialogFragment extends DialogFragment { } @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + String number = getArguments().getString(ARG_NUMBER); + if (TextUtils.isEmpty(mNormalizedNumber) || + !FilteredNumbersUtil.canBlockNumber(getActivity(), number)) { + dismiss(); + Toast.makeText(getContext(), getString(R.string.invalidNumber, number), + Toast.LENGTH_SHORT).show(); + } + } + + @Override public void onPause() { // Dismiss on rotation. dismiss(); @@ -159,13 +181,14 @@ public class FilterNumberDialogFragment extends DialogFragment { private void blockNumber() { final String message = getBlockedMessage(); final String undoMessage = getUnblockedMessage(); + final Callback callback = mCallback; final OnUnblockNumberListener onUndoListener = new OnUnblockNumberListener() { @Override public void onUnblockComplete(int rows, ContentValues values) { Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show(); - if (mCallback != null) { - mCallback.onChangeFilteredNumberUndo(); + if (callback != null) { + callback.onChangeFilteredNumberUndo(); } } }; @@ -185,8 +208,8 @@ public class FilterNumberDialogFragment extends DialogFragment { .setAction(R.string.block_number_undo, undoListener) .show(); - if (mCallback != null) { - mCallback.onChangeFilteredNumberSuccess(); + if (callback != null) { + callback.onChangeFilteredNumberSuccess(); } } }; @@ -201,13 +224,14 @@ public class FilterNumberDialogFragment extends DialogFragment { private void unblockNumber() { final String message = getUnblockedMessage(); final String undoMessage = getBlockedMessage(); + final Callback callback = mCallback; final OnBlockNumberListener onUndoListener = new OnBlockNumberListener() { @Override public void onBlockComplete(final Uri uri) { Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show(); - if (mCallback != null) { - mCallback.onChangeFilteredNumberUndo(); + if (callback != null) { + callback.onChangeFilteredNumberUndo(); } } }; @@ -227,8 +251,8 @@ public class FilterNumberDialogFragment extends DialogFragment { .setAction(R.string.block_number_undo, undoListener) .show(); - if (mCallback != null) { - mCallback.onChangeFilteredNumberSuccess(); + if (callback != null) { + callback.onChangeFilteredNumberSuccess(); } } }, getArguments().getInt(ARG_BLOCK_ID)); diff --git a/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java new file mode 100644 index 000000000..68f262cf9 --- /dev/null +++ b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java @@ -0,0 +1,232 @@ +/* + * 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.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.os.AsyncTask; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Data; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import java.util.LinkedList; +import java.util.List; + +import com.android.dialer.R; +import com.android.dialer.database.FilteredNumberAsyncQueryHandler; +import com.android.dialer.database.FilteredNumberContract.FilteredNumber; +import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; + +/** + * Utility to help with tasks related to filtered numbers. + */ +public class FilteredNumbersUtil { + + 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; + } + + private static class PhoneQuery { + static final String[] PROJECTION = { + Phone.NORMALIZED_NUMBER, + Phone.NUMBER + }; + + static final int NORMALIZED_NUMBER_COLUMN_INDEX = 0; + static final int NUMBER_COLUMN_INDEX = 1; + + 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) { + 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) { + final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler = + new FilteredNumberAsyncQueryHandler(context.getContentResolver()); + + final AsyncTask 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 isBlocked(Context context, String number, String countryIso) { + final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); + if (TextUtils.isEmpty(normalizedNumber)) { + return false; + } + + final Cursor cursor = context.getContentResolver().query( + FilteredNumber.CONTENT_URI, + new String[] { FilteredNumberColumns._ID }, + FilteredNumberColumns.NORMALIZED_NUMBER + "=?", + new String[] { normalizedNumber }, + null); + + boolean isBlocked = false; + if (cursor != null) { + try { + isBlocked = cursor.getCount() > 0; + } finally { + cursor.close(); + } + } + + return isBlocked; + } + + public static boolean canBlockNumber(Context context, String number) { + if (PhoneNumberUtils.isEmergencyNumber(number)) { + return false; + } + + TelecomManager telecomManager = + (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + List<PhoneAccountHandle> phoneAccountHandles = telecomManager.getCallCapablePhoneAccounts(); + for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) { + if (telecomManager.isVoiceMailNumber(phoneAccountHandle, number)) { + return false; + } + } + + return true; + } +} diff --git a/src/com/android/dialer/list/BlockedListSearchFragment.java b/src/com/android/dialer/list/BlockedListSearchFragment.java index ae64057da..498994f55 100644 --- a/src/com/android/dialer/list/BlockedListSearchFragment.java +++ b/src/com/android/dialer/list/BlockedListSearchFragment.java @@ -87,20 +87,13 @@ public class BlockedListSearchFragment extends RegularSearchFragment private void blockNumber(final String number) { final String countryIso = GeoUtil.getCurrentCountryIso(getContext()); - final String normalizedNumber = - FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso); - if (normalizedNumber == null) { - Toast.makeText(getContext(), getString(R.string.invalidNumber, number), - Toast.LENGTH_SHORT).show(); - return; - } final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() { @Override public void onCheckComplete(Integer id) { if (id == null) { FilterNumberDialogFragment.show( id, - normalizedNumber, + null, number, countryIso, number, @@ -113,8 +106,12 @@ public class BlockedListSearchFragment extends RegularSearchFragment } } }; - mFilteredNumberAsyncQueryHandler.startBlockedQuery( - onCheckListener, normalizedNumber, number, countryIso); + boolean failed = mFilteredNumberAsyncQueryHandler.startBlockedQuery( + onCheckListener, null, number, countryIso); + if (failed) { + Toast.makeText(getContext(), getString(R.string.invalidNumber, number), + Toast.LENGTH_SHORT).show(); + } } @Override @@ -137,14 +134,6 @@ public class BlockedListSearchFragment extends RegularSearchFragment final ContactListItemView view, final String number, final Integer blockId) { - final String countryIso = GeoUtil.getCurrentCountryIso(getContext()); - final String normalizedNumber = - FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso); - if (normalizedNumber == null) { - Toast.makeText(getContext(), getString(R.string.invalidNumber, number), - Toast.LENGTH_SHORT).show(); - return; - } if (blockId != null) { Toast.makeText(getContext(), getString(R.string.alreadyBlocked, number), Toast.LENGTH_SHORT).show(); @@ -153,9 +142,9 @@ public class BlockedListSearchFragment extends RegularSearchFragment FilterNumberDialogFragment.show( blockId, - normalizedNumber, + null, number, - countryIso, + GeoUtil.getCurrentCountryIso(getContext()), number, R.id.blocked_numbers_activity_container, getFragmentManager(), diff --git a/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java b/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java new file mode 100644 index 000000000..801175596 --- /dev/null +++ b/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java @@ -0,0 +1,66 @@ +/* + * 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 static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.test.AndroidTestCase; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.LinkedList; + +public class FilteredNumbersUtilTest extends AndroidTestCase { + + private static final String NORMAL_PHONE_NUMBER = "555-555-5555"; + private static final String VOICEMAIL_NUMBER = "*555"; + + @Mock private Context mContext; + @Mock private TelecomManager mTelecomManager; + private PhoneAccountHandle mHandle; + + @Override + protected void setUp() throws Exception { + super.setUp(); + System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); + + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager); + + LinkedList<PhoneAccountHandle> handles = new LinkedList<PhoneAccountHandle>(); + mHandle = new PhoneAccountHandle(new ComponentName(getContext(), "TestCase"), "0"); + handles.add(mHandle); + + when(mTelecomManager.getCallCapablePhoneAccounts()).thenReturn(handles); + } + + public void testCanBlockNormalNumber() { + assertTrue(FilteredNumbersUtil.canBlockNumber(mContext, NORMAL_PHONE_NUMBER)); + } + + public void testCannotBlockVoicemailNumber() { + when(mTelecomManager.isVoiceMailNumber(Matchers.eq(mHandle), Matchers.anyString())) + .thenReturn(true); + assertFalse(FilteredNumbersUtil.canBlockNumber(mContext, VOICEMAIL_NUMBER)); + } +} |