diff options
Diffstat (limited to 'java/com/android/dialer')
18 files changed, 593 insertions, 112 deletions
diff --git a/java/com/android/dialer/app/settings/DialerSettingsActivity.java b/java/com/android/dialer/app/settings/DialerSettingsActivity.java index 10ef9ed60..706f0985a 100644 --- a/java/com/android/dialer/app/settings/DialerSettingsActivity.java +++ b/java/com/android/dialer/app/settings/DialerSettingsActivity.java @@ -33,9 +33,12 @@ import android.view.MenuItem; import android.widget.Toast; import com.android.dialer.about.AboutPhoneFragment; import com.android.dialer.app.R; +import com.android.dialer.assisteddialing.ConcreteCreator; +import com.android.dialer.assisteddialing.ui.AssistedDialingSettingFragment; import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.telephony.TelephonyManagerCompat; +import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.proguard.UsedByReflection; import com.android.voicemail.VoicemailClient; import com.android.voicemail.VoicemailComponent; @@ -135,6 +138,21 @@ public class DialerSettingsActivity extends AppCompatPreferenceActivity { target.add(accessibilitySettingsHeader); } + boolean isAssistedDialingEnabled = + ConcreteCreator.isAssistedDialingEnabled( + ConfigProviderBindings.get(getApplicationContext())); + LogUtil.i( + "DialerSettingsActivity.onBuildHeaders", + "showing assisted dialing header: " + isAssistedDialingEnabled); + if (isAssistedDialingEnabled) { + + Header assistedDialingSettingsHeader = new Header(); + assistedDialingSettingsHeader.titleRes = + com.android.dialer.assisteddialing.ui.R.string.assisted_dialing_setting_title; + assistedDialingSettingsHeader.fragment = AssistedDialingSettingFragment.class.getName(); + target.add(assistedDialingSettingsHeader); + } + if (showAbout()) { Header aboutPhoneHeader = new Header(); aboutPhoneHeader.titleRes = R.string.about_phone_label; diff --git a/java/com/android/dialer/assisteddialing/ConcreteCreator.java b/java/com/android/dialer/assisteddialing/ConcreteCreator.java index a8a9d2ab3..1790b8f3f 100644 --- a/java/com/android/dialer/assisteddialing/ConcreteCreator.java +++ b/java/com/android/dialer/assisteddialing/ConcreteCreator.java @@ -20,8 +20,10 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.os.Build.VERSION_CODES; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.telephony.TelephonyManager; +import com.android.dialer.assisteddialing.ui.R; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProvider; import com.android.dialer.configprovider.ConfigProviderBindings; @@ -63,8 +65,15 @@ public final class ConcreteCreator { throw new NullPointerException("Provided context was null"); } - if ((Build.VERSION.SDK_INT < BUILD_CODE_FLOOR || Build.VERSION.SDK_INT > BUILD_CODE_CEILING) - || !configProvider.getBoolean("assisted_dialing_enabled", false)) { + if (!isAssistedDialingEnabled(configProvider)) { + LogUtil.i("ConcreteCreator.createNewAssistedDialingMediator", "feature not enabled"); + return new AssistedDialingMediatorStub(); + } + + if (!PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.assisted_dialing_setting_toggle_key), false)) { + LogUtil.i("ConcreteCreator.createNewAssistedDialingMediator", "disabled by local setting"); + return new AssistedDialingMediatorStub(); } @@ -74,4 +83,16 @@ public final class ConcreteCreator { return new AssistedDialingMediatorImpl( new LocationDetector(telephonyManager), new NumberTransformer(constraints)); } + + /** Returns a boolean indicating whether or not the assisted dialing feature is enabled. */ + public static boolean isAssistedDialingEnabled(@NonNull ConfigProvider configProvider) { + if (configProvider == null) { + LogUtil.i("ConcreteCreator.isAssistedDialingEnabled", "provided configProvider was null"); + throw new NullPointerException("Provided configProvider was null"); + } + + return (Build.VERSION.SDK_INT >= BUILD_CODE_FLOOR + && Build.VERSION.SDK_INT <= BUILD_CODE_CEILING) + && configProvider.getBoolean("assisted_dialing_enabled", false); + } } diff --git a/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml new file mode 100644 index 000000000..6625dff07 --- /dev/null +++ b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml @@ -0,0 +1,22 @@ +<!-- Copyright (C) 2017 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dialer.assisteddialing"> + + <uses-sdk + android:minSdkVersion="23" + android:targetSdkVersion="24"/> + +</manifest>
\ No newline at end of file diff --git a/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java new file mode 100644 index 000000000..8847448cd --- /dev/null +++ b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 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.assisteddialing.ui; + +import android.os.Bundle; +import android.preference.PreferenceFragment; + +/** The setting for Assisted Dialing */ +public class AssistedDialingSettingFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.assisted_dialing_setting); + } +} diff --git a/java/com/android/dialer/assisteddialing/ui/res/values/string.xml b/java/com/android/dialer/assisteddialing/ui/res/values/string.xml new file mode 100644 index 000000000..cd159bfd5 --- /dev/null +++ b/java/com/android/dialer/assisteddialing/ui/res/values/string.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> + +<resources> + <!-- Label for a setting enabling assisted dialing [CHAR LIMIT=40]--> + <string name="assisted_dialing_setting_title">Assisted dialing</string> + + <!-- Label for a setting enabling assisted dialing switch preference--> + <string name="assisted_dialing_setting_summary">Automatically correct the phone number prefix when traveling and calling international numbers</string> + + <!-- Key for the assisted dialing setting toggle--> + <string name="assisted_dialing_setting_toggle_key" translatable="false">assisted_dialing_setting_toggle_key</string> +</resources>
\ No newline at end of file diff --git a/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml b/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml new file mode 100644 index 000000000..8e3c62dde --- /dev/null +++ b/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2017 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. + --> + +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android"> + + <SwitchPreference + android:defaultValue="false" + android:key="@string/assisted_dialing_setting_toggle_key" + android:title="@string/assisted_dialing_setting_title" + android:summary="@string/assisted_dialing_setting_summary" /> + +</PreferenceScreen>
\ No newline at end of file diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java index a5f1425f8..589fe6384 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java @@ -60,6 +60,7 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { .toString(); /** Deletes all but the first maxRows rows (by timestamp) to keep the table a manageable size. */ + // TODO(zachh): Prevent voicemails from being garbage collected. private static final String CREATE_TRIGGER_SQL = "create trigger delete_old_rows after insert on " + AnnotatedCallLog.TABLE @@ -79,12 +80,20 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { + AnnotatedCallLog.TABLE + " )); end;"; + private static final String CREATE_INDEX_ON_CALL_TYPE_SQL = + "create index call_type_index on " + + AnnotatedCallLog.TABLE + + " (" + + AnnotatedCallLog.CALL_TYPE + + ");"; + @Override public void onCreate(SQLiteDatabase db) { LogUtil.enterBlock("AnnotatedCallLogDatabaseHelper.onCreate"); long startTime = System.currentTimeMillis(); db.execSQL(CREATE_TABLE_SQL); db.execSQL(String.format(Locale.US, CREATE_TRIGGER_SQL, maxRows, maxRows)); + db.execSQL(CREATE_INDEX_ON_CALL_TYPE_SQL); // TODO(zachh): Consider logging impression. LogUtil.i( "AnnotatedCallLogDatabaseHelper.onCreate", diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java index b680bd57d..f501792f9 100644 --- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java +++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java @@ -337,30 +337,30 @@ public class ContactInfoHelper { return ContactInfo.EMPTY; } - Cursor phoneLookupCursor = null; - try { - String[] projection = PhoneQuery.getPhoneLookupProjection(uri); - phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, null); - } catch (NullPointerException e) { - LogUtil.e("ContactInfoHelper.lookupContactFromUri", "phone lookup", e); - // Trap NPE from pre-N CP2 - return null; - } - if (phoneLookupCursor == null) { - LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null"); - return null; - } + try (Cursor phoneLookupCursor = + mContext + .getContentResolver() + .query(uri, PhoneQuery.getPhoneLookupProjection(uri), null, null, null)) { + if (phoneLookupCursor == null) { + LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null"); + return null; + } - try { if (!phoneLookupCursor.moveToFirst()) { return ContactInfo.EMPTY; } - String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY); - ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey); + + Cursor matchedCursor = + PhoneNumberHelper.getCursorMatchForContactLookupUri( + phoneLookupCursor, PhoneQuery.MATCHED_NUMBER, uri); + if (matchedCursor == null) { + return ContactInfo.EMPTY; + } + + String lookupKey = matchedCursor.getString(PhoneQuery.LOOKUP_KEY); + ContactInfo contactInfo = createPhoneLookupContactInfo(matchedCursor, lookupKey); fillAdditionalContactInfo(mContext, contactInfo); return contactInfo; - } finally { - phoneLookupCursor.close(); } } diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java index cc9b73081..84cbb6947 100644 --- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java +++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java @@ -17,6 +17,8 @@ package com.android.dialer.phonenumberutil; import android.content.Context; +import android.database.Cursor; +import android.net.Uri; import android.os.Trace; import android.provider.CallLog; import android.support.annotation.Nullable; @@ -24,6 +26,7 @@ import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; import com.android.dialer.compat.telephony.TelephonyManagerCompat; @@ -47,6 +50,78 @@ public class PhoneNumberHelper { } /** + * Find the cursor pointing to a number that matches the number in a contact lookup URI. + * + * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the + * Contacts Provider uses {@link PhoneNumberUtils#compare(String, String)}, which ignores special + * dialable characters such as '#', '*', '+', etc. This makes it possible for the cursor returned + * by the Contacts Provider to have multiple rows even when the URI asks for a specific number. + * + * <p>For example, suppose the user has two contacts whose numbers are "#123" and "123", + * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, the + * following strategy is employed to find a match. + * + * <p>If the cursor points to a global phone number (i.e., a number that can be accepted by {@link + * PhoneNumberUtils#isGlobalPhoneNumber(String)}) and the lookup number in the URI is a PARTIAL + * match, return the cursor. + * + * <p>If the cursor points to a number that is not a global phone number, return the cursor iff + * the lookup number in the URI is an EXACT match. + * + * <p>Return null in all other circumstances. + * + * @param cursor A cursor returned by the Contacts Provider. + * @param columnIndexForNumber The index of the column where phone numbers are stored. It is the + * caller's responsibility to pass the correct column index. + * @param contactLookupUri A URI used to retrieve a contact via the Contacts Provider. It is the + * caller's responsibility to ensure the URI is one that asks for a specific phone number. + * @return The cursor considered as a match by the description above or null if no such cursor can + * be found. + */ + public static Cursor getCursorMatchForContactLookupUri( + Cursor cursor, int columnIndexForNumber, Uri contactLookupUri) { + if (cursor == null || contactLookupUri == null) { + return null; + } + + if (!cursor.moveToFirst()) { + return null; + } + + Assert.checkArgument( + 0 <= columnIndexForNumber && columnIndexForNumber < cursor.getColumnCount()); + + String lookupNumber = contactLookupUri.getLastPathSegment(); + if (lookupNumber == null) { + return null; + } + + boolean isMatchFound; + do { + // All undialable characters should be converted/removed before comparing the lookup number + // and the existing contact number. + String rawExistingContactNumber = + PhoneNumberUtils.stripSeparators( + PhoneNumberUtils.convertKeypadLettersToDigits( + cursor.getString(columnIndexForNumber))); + String rawQueryNumber = + PhoneNumberUtils.stripSeparators( + PhoneNumberUtils.convertKeypadLettersToDigits(lookupNumber)); + + isMatchFound = + PhoneNumberUtils.isGlobalPhoneNumber(rawExistingContactNumber) + ? rawExistingContactNumber.contains(rawQueryNumber) + : rawExistingContactNumber.equals(rawQueryNumber); + + if (isMatchFound) { + return cursor; + } + } while (cursor.moveToNext()); + + return null; + } + + /** * 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. */ diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java index b40c8630e..71178ceba 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogAdapter.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java @@ -15,41 +15,40 @@ */ package com.android.dialer.voicemail.listui; +import android.database.Cursor; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.dialer.common.LogUtil; -import com.android.dialer.voicemail.datasources.VoicemailData; -import java.util.List; /** {@link RecyclerView.Adapter} for the new voicemail call log fragment. */ -final class NewVoicemailCallLogAdapter extends RecyclerView.Adapter<NewVoicemailCallLogViewHolder> { +final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHolder> { - private final List<VoicemailData> voicemailData; + private final Cursor cursor; - NewVoicemailCallLogAdapter(List<VoicemailData> dataSet) { - voicemailData = dataSet; + /** @param cursor whose projection is {@link VoicemailCursorLoader.VOICEMAIL_COLUMNS} */ + NewVoicemailAdapter(Cursor cursor) { + this.cursor = cursor; } @Override - public NewVoicemailCallLogViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - + public NewVoicemailViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); - View v = inflater.inflate(R.layout.new_voicemail_call_log_entry, viewGroup, false); - NewVoicemailCallLogViewHolder newVoicemailCallLogViewHolder = - new NewVoicemailCallLogViewHolder(v); - return newVoicemailCallLogViewHolder; + View view = inflater.inflate(R.layout.new_voicemail_call_log_entry, viewGroup, false); + NewVoicemailViewHolder newVoicemailViewHolder = new NewVoicemailViewHolder(view); + return newVoicemailViewHolder; } @Override - public void onBindViewHolder(NewVoicemailCallLogViewHolder viewHolder, int position) { + public void onBindViewHolder(NewVoicemailViewHolder viewHolder, int position) { LogUtil.i("onBindViewHolder", "position" + position); - viewHolder.bind(voicemailData.get(position)); + cursor.moveToPosition(position); + viewHolder.bind(cursor); } @Override public int getItemCount() { - return voicemailData.size(); + return cursor.getCount(); } } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogViewHolder.java deleted file mode 100644 index 9ec768464..000000000 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogViewHolder.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2017 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.listui; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.TextView; -import com.android.dialer.voicemail.datasources.VoicemailData; - -/** {@link RecyclerView.ViewHolder} for the new voicemail call log. */ -final class NewVoicemailCallLogViewHolder extends RecyclerView.ViewHolder { - - private final TextView primaryTextView; - private final TextView secondaryTextView; - private final TextView transcriptionTextView; - - NewVoicemailCallLogViewHolder(View view) { - super(view); - primaryTextView = (TextView) view.findViewById(R.id.primary_text); - secondaryTextView = (TextView) view.findViewById(R.id.secondary_text); - transcriptionTextView = (TextView) view.findViewById(R.id.transcription_text); - } - - void bind(VoicemailData voicemailData) { - primaryTextView.setText(voicemailData.name()); - secondaryTextView.setText(getVoicemailLocationDateAndDuration(voicemailData)); - transcriptionTextView.setText(voicemailData.transcription()); - } - - private String getVoicemailLocationDateAndDuration(VoicemailData voicemailData) { - return voicemailData.location() - + " · " - + voicemailData.date() - + " · " - + voicemailData.duration(); - } -} diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java index 3629b7551..1912e1e3f 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -16,49 +16,50 @@ package com.android.dialer.voicemail.listui; +import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.Loader; 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.common.LogUtil; -import com.android.dialer.voicemail.datasources.VoicemailData; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; /** Fragment for Dialer Voicemail Tab. */ -public final class NewVoicemailFragment extends Fragment { +public final class NewVoicemailFragment extends Fragment implements LoaderCallbacks<Cursor> { + + private RecyclerView recyclerView; + @Nullable @Override public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.new_voicemail_call_log_fragment, container, false); - RecyclerView recyclerView = - (RecyclerView) view.findViewById(R.id.new_voicemail_call_log_recycler_view); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView = view.findViewById(R.id.new_voicemail_call_log_recycler_view); + getLoaderManager().restartLoader(0, null, this); + return view; + } - // TODO(uabdullah): To be removed once we hook up the UI to the voicemail backend - List<VoicemailData> voicemailData = new ArrayList<>(); - Random rand = new Random(); - for (int i = 0; i < 50; i++) { - VoicemailData mocked = - VoicemailData.builder() - .setName("Fatima Abdullah " + i) - .setLocation("San Francisco, CA") - .setDate("March " + (rand.nextInt(30) + 1)) - .setDuration("00:" + (rand.nextInt(50) + 10)) - .setTranscription( - "This is a transcription text message that literally means nothing.") - .build(); - voicemailData.add(mocked); - } + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + LogUtil.enterBlock("NewVoicemailFragment.onCreateLoader"); + return new VoicemailCursorLoader(getContext()); + } - LogUtil.i("onCreateView", "size of input:" + voicemailData.size()); - recyclerView.setAdapter(new NewVoicemailCallLogAdapter(voicemailData)); - return view; + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount()); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setAdapter(new NewVoicemailAdapter(data)); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + LogUtil.enterBlock("NewVoicemailFragment.onLoaderReset"); + recyclerView.setAdapter(null); } } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java new file mode 100644 index 000000000..daa24c86c --- /dev/null +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.listui; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.QuickContactBadge; +import android.widget.TextView; +import com.android.dialer.contactphoto.ContactPhotoManager; +import com.android.dialer.lettertile.LetterTileDrawable; +import com.android.dialer.voicemail.model.VoicemailEntry; + +/** {@link RecyclerView.ViewHolder} for the new voicemail tab. */ +final class NewVoicemailViewHolder extends RecyclerView.ViewHolder { + + private final Context context; + private final TextView primaryTextView; + private final QuickContactBadge quickContactBadge; + + NewVoicemailViewHolder(View view) { + super(view); + this.context = view.getContext(); + primaryTextView = (TextView) view.findViewById(R.id.primary_text); + quickContactBadge = view.findViewById(R.id.quick_contact_photo); + } + + void bind(Cursor cursor) { + VoicemailEntry voicemailEntry = VoicemailCursorLoader.toVoicemailEntry(cursor); + primaryTextView.setText(VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry)); + setPhoto(voicemailEntry); + } + + // TODO(uabdullah): Consider/Implement TYPE (e.g Spam, TYPE_VOICEMAIL) + private void setPhoto(VoicemailEntry voicemailEntry) { + ContactPhotoManager.getInstance(context) + .loadDialerThumbnailOrPhoto( + quickContactBadge, + voicemailEntry.lookupUri() == null ? null : Uri.parse(voicemailEntry.lookupUri()), + voicemailEntry.photoId(), + voicemailEntry.photoUri() == null ? null : Uri.parse(voicemailEntry.photoUri()), + voicemailEntry.name(), + LetterTileDrawable.TYPE_DEFAULT); + } +} diff --git a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java new file mode 100644 index 000000000..5a4176542 --- /dev/null +++ b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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.listui; + +import android.content.Context; +import android.database.Cursor; +import android.provider.CallLog.Calls; +import android.support.v4.content.CursorLoader; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; +import com.android.dialer.voicemail.model.VoicemailEntry; +import com.google.protobuf.InvalidProtocolBufferException; + +/** CursorLoader for the annotated call log (voicemails only). */ +final class VoicemailCursorLoader extends CursorLoader { + + // When adding columns be sure to update {@link #VoicemailCursorLoader.toVoicemailEntry}. + public static final String[] VOICEMAIL_COLUMNS = + new String[] { + AnnotatedCallLog._ID, + AnnotatedCallLog.TIMESTAMP, + AnnotatedCallLog.NAME, + AnnotatedCallLog.NUMBER, + AnnotatedCallLog.FORMATTED_NUMBER, + AnnotatedCallLog.PHOTO_URI, + AnnotatedCallLog.PHOTO_ID, + AnnotatedCallLog.LOOKUP_URI, + AnnotatedCallLog.GEOCODED_LOCATION, + AnnotatedCallLog.CALL_TYPE + }; + + // Indexes for VOICEMAIL_COLUMNS + private static final int ID = 0; + private static final int TIMESTAMP = 1; + private static final int NAME = 2; + private static final int NUMBER = 3; + private static final int FORMATTED_NUMBER = 4; + private static final int PHOTO_URI = 5; + private static final int PHOTO_ID = 6; + private static final int LOOKUP_URI = 7; + private static final int GEOCODED_LOCATION = 8; + private static final int CALL_TYPE = 9; + + // TODO(zachh): Optimize indexes + VoicemailCursorLoader(Context context) { + super( + context, + AnnotatedCallLog.CONTENT_URI, + VOICEMAIL_COLUMNS, + AnnotatedCallLog.CALL_TYPE + " = ?", + new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}, + AnnotatedCallLog.TIMESTAMP + " DESC"); + } + + /** Creates a new {@link VoicemailEntry} from the provided cursor using the current position. */ + static VoicemailEntry toVoicemailEntry(Cursor cursor) { + DialerPhoneNumber number; + try { + number = DialerPhoneNumber.parseFrom(cursor.getBlob(NUMBER)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes"); + } + + return VoicemailEntry.builder() + .setId(cursor.getInt(ID)) + .setTimestamp(cursor.getLong(TIMESTAMP)) + .setName(cursor.getString(NAME)) + .setNumber(number) + .setFormattedNumber(cursor.getString(FORMATTED_NUMBER)) + .setPhotoUri(cursor.getString(PHOTO_URI)) + .setPhotoId(cursor.getLong(PHOTO_ID)) + .setLookupUri(cursor.getString(LOOKUP_URI)) + .setGeocodedLocation(cursor.getString(GEOCODED_LOCATION)) + .setCallType(cursor.getInt(CALL_TYPE)) + .build(); + } +} diff --git a/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java b/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java new file mode 100644 index 000000000..cf2fef253 --- /dev/null +++ b/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 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.listui; + +import android.content.Context; +import android.text.TextUtils; +import com.android.dialer.voicemail.model.VoicemailEntry; + +/** + * Computes the primary text for voicemail entries. + * + * <p>These text values are shown in the voicemail tab. + */ +public class VoicemailEntryText { + + public static String buildPrimaryVoicemailText(Context context, VoicemailEntry data) { + StringBuilder primaryText = new StringBuilder(); + if (!TextUtils.isEmpty(data.name())) { + primaryText.append(data.name()); + } else if (!TextUtils.isEmpty(data.formattedNumber())) { + primaryText.append(data.formattedNumber()); + } else { + // TODO(uabdullah): Handle CallLog.Calls.PRESENTATION_*, including Verizon restricted numbers. + primaryText.append(context.getText(R.string.voicemail_entry_unknown)); + } + return primaryText.toString(); + } +} diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml index 01c0ee12e..82ccf88d2 100644 --- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml +++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml @@ -31,7 +31,6 @@ android:layout_height="@dimen/call_log_entry_photo_size" android:layout_centerVertical="true" android:padding="@dimen/call_log_entry_photo_padding" - android:background="@color/dialer_secondary_color" android:focusable="true"/> <LinearLayout @@ -42,6 +41,7 @@ android:layout_toStartOf="@+id/menu_button" android:orientation="vertical"> + <!--TODO(uabdullah): Resolve text clipping on top of primary text --> <TextView android:id="@+id/primary_text" style="@style/PrimaryText" diff --git a/java/com/android/dialer/voicemail/listui/res/values/strings.xml b/java/com/android/dialer/voicemail/listui/res/values/strings.xml new file mode 100644 index 000000000..39c368a5a --- /dev/null +++ b/java/com/android/dialer/voicemail/listui/res/values/strings.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<resources> + <!-- String used to display voicemails from unknown numbers in the voicemail tab. [CHAR LIMIT=30] --> + <string name="voicemail_entry_unknown">Unknown</string> +</resources>
\ No newline at end of file diff --git a/java/com/android/dialer/voicemail/model/VoicemailEntry.java b/java/com/android/dialer/voicemail/model/VoicemailEntry.java new file mode 100644 index 000000000..00e1757dc --- /dev/null +++ b/java/com/android/dialer/voicemail/model/VoicemailEntry.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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.model; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.DialerPhoneNumber; +import com.google.auto.value.AutoValue; + +/** Data class containing the contents of a voicemail entry from the AnnotatedCallLog. */ +@AutoValue +public abstract class VoicemailEntry { + + public static Builder builder() { + return new AutoValue_VoicemailEntry.Builder() + .setId(0) + .setTimestamp(0) + .setNumber(DialerPhoneNumber.getDefaultInstance()) + .setPhotoId(0) + .setCallType(0); + } + + public abstract int id(); + + public abstract long timestamp(); + + @NonNull + public abstract DialerPhoneNumber number(); + + @Nullable + public abstract String name(); + + @Nullable + public abstract String formattedNumber(); + + @Nullable + public abstract String photoUri(); + + public abstract long photoId(); + + @Nullable + public abstract String lookupUri(); + + @Nullable + public abstract String geocodedLocation(); + + public abstract int callType(); + + /** Builder for {@link VoicemailEntry}. */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setId(int id); + + public abstract Builder setTimestamp(long timestamp); + + public abstract Builder setNumber(@NonNull DialerPhoneNumber number); + + public abstract Builder setName(@Nullable String name); + + public abstract Builder setFormattedNumber(@Nullable String formattedNumber); + + public abstract Builder setPhotoUri(@Nullable String photoUri); + + public abstract Builder setPhotoId(long photoId); + + public abstract Builder setLookupUri(@Nullable String lookupUri); + + public abstract Builder setGeocodedLocation(@Nullable String geocodedLocation); + + public abstract Builder setCallType(int callType); + + public abstract VoicemailEntry build(); + } +} |