diff options
author | Yuzhou <jiayuzhou@google.com> | 2018-04-21 17:20:47 -0700 |
---|---|---|
committer | Yuzhou <jiayuzhou@google.com> | 2018-05-01 13:30:47 -0700 |
commit | 02a83b90ad2a990c22b4654fbfa23b84aae0fafc (patch) | |
tree | ea84f616e82ffabe9c95cdd8cd5a25c987c75322 | |
parent | 43d9a9e1f5a60fe13230dc072589c75bbf1d1d29 (diff) | |
download | Dialer-02a83b90ad2a990c22b4654fbfa23b84aae0fafc.tar.gz |
DO NOT MERGE Improve the loading speed of the call history list.
Test: build and load app.
Bug: 77980012.
Change-Id: I6e542beb12e7fe37967f41eead196a503f9f016f
-rw-r--r-- | Android.mk | 3 | ||||
-rw-r--r-- | res/layout-h720dp/dialer_fragment.xml | 124 | ||||
-rw-r--r-- | src/com/android/car/dialer/ContactEntry.java | 191 | ||||
-rw-r--r-- | src/com/android/car/dialer/StrequentsAdapter.java | 9 | ||||
-rw-r--r-- | src/com/android/car/dialer/TelecomActivity.java | 10 | ||||
-rw-r--r-- | src/com/android/car/dialer/telecom/InMemoryPhoneBook.java | 121 | ||||
-rw-r--r-- | src/com/android/car/dialer/telecom/TelecomUtils.java | 47 | ||||
-rw-r--r-- | src/com/android/car/dialer/ui/CallHistoryListItemProvider.java | 3 | ||||
-rw-r--r-- | src/com/android/car/dialer/ui/CallLogListingTask.java | 162 | ||||
-rw-r--r-- | src/com/android/car/dialer/ui/DialerInfoFragment.java | 168 | ||||
-rw-r--r-- | src/com/android/car/dialer/ui/listitem/CallLogListItem.java | 29 | ||||
-rw-r--r-- | src/com/android/car/dialer/ui/listitem/ContactListItem.java | 23 |
12 files changed, 692 insertions, 198 deletions
@@ -40,7 +40,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES += \ android-support-v7-cardview \ LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-constraint-layout-solver + android-support-constraint-layout-solver \ + guava \ LOCAL_PROGUARD_ENABLED := disabled diff --git a/res/layout-h720dp/dialer_fragment.xml b/res/layout-h720dp/dialer_fragment.xml new file mode 100644 index 00000000..97099c55 --- /dev/null +++ b/res/layout-h720dp/dialer_fragment.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<!-- There seems to be a bug in layout inflation where it can't use a resource to inflate a view + group that sets layout_marginTop with a dimension. Work around by putting in a shell layout. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:card_view="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- This CardView is clickable so that clicks do not fall through to the fragment that + is underneath the dialer_fragment. --> + <android.support.v7.widget.CardView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/car_app_bar_height" + android:clickable="true" + card_view:cardBackgroundColor="@color/car_card" + card_view:cardElevation="@dimen/dialer_card_elevation"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/number" + android:layout_width="match_parent" + android:layout_height="@dimen/dialer_number_view_height" + android:paddingTop="@dimen/dialer_number_view_padding" + android:paddingBottom="@dimen/dialer_number_view_padding" + android:gravity="center" + android:focusable="true" + android:text="@string/dial_a_number" + android:layout_alignParentTop="true" + style="@style/TextAppearance.Car.Body1" /> + + <View + android:id="@+id/line_divider" + android:background="@color/car_list_divider" + android:layout_width="match_parent" + android:layout_height="@dimen/line_divider_height" + android:layout_marginLeft="@dimen/car_keyline_1" + android:layout_marginRight="@dimen/car_keyline_1" + android:layout_below="@id/number" /> + + <LinearLayout + android:orientation="horizontal" + android:layout_marginTop="@dimen/dial_container_vertical_margin" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/line_divider" > + + <FrameLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" > + + <ImageButton + android:id="@+id/call" + android:scaleType="center" + android:src="@drawable/ic_phone" + style="@style/DialpadPrimaryButton" + android:elevation="@dimen/call_fab_elevation" + android:layout_gravity="center" /> + </FrameLayout> + + <include + android:layout_gravity="center" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + layout="@layout/dialpad" /> + + <FrameLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" > + + <ImageButton + android:id="@+id/delete" + android:layout_width="@dimen/bksp_button_width" + android:layout_height="@dimen/bksp_button_width" + android:scaleType="centerInside" + android:src="@drawable/ic_backspace" + android:tint="@color/car_tint" + android:background="@drawable/dialpad_delete_button_background" + android:layout_gravity="center" /> + </FrameLayout> + </LinearLayout> + </RelativeLayout> + + <!-- This FrameLayout ensures that the back button is centered within the + exit_dialer_button despite the button's touch target + being smaller. --> + <FrameLayout + android:layout_gravity="start|top" + android:layout_width="@dimen/car_keyline_1" + android:layout_height="@dimen/car_keyline_1"> + + <ImageView + android:id="@+id/exit_dialer_button" + android:background="@drawable/dialpad_button_background" + android:layout_gravity="center" + android:layout_width="@dimen/car_touch_target_size" + android:layout_height="@dimen/car_touch_target_size" + android:scaleType="center" + android:tint="@color/car_tint" + android:src="@drawable/ic_down_outlined" /> + </FrameLayout> + </android.support.v7.widget.CardView> +</FrameLayout> diff --git a/src/com/android/car/dialer/ContactEntry.java b/src/com/android/car/dialer/ContactEntry.java index 34db70fb..2b21dffb 100644 --- a/src/com/android/car/dialer/ContactEntry.java +++ b/src/com/android/car/dialer/ContactEntry.java @@ -20,43 +20,109 @@ import android.database.Cursor; import android.provider.ContactsContract; import android.support.annotation.Nullable; import android.text.TextUtils; +import android.util.Log; + import com.android.car.dialer.telecom.PhoneLoader; import com.android.car.dialer.telecom.TelecomUtils; /** * Encapsulates data about a phone Contact entry. Typically loaded from the local Contact store. */ +// TODO: Refactor to use Builder design pattern. public class ContactEntry implements Comparable<ContactEntry> { private final Context mContext; + /** + * An unique primary key for searching an entry. + */ + private int mId; + + /** + * Whether this contact entry is starred by user. + */ + private boolean mIsStarred; + + /** + * Contact-specific information about whether or not a contact has been pinned by the user at + * a particular position within the system contact application's user interface. + */ + private int mPinnedPosition; + + /** + * Phone number. + */ + private String mNumber; + + /** + * The display name. + */ @Nullable - public String name; - public String number; - public boolean isStarred; - public int pinnedPosition; + private String mDisplayName; + + /** + * A URI that can be used to retrieve a thumbnail of the contact's photo. + */ + private String mAvatarThumbnailUri; + + /** + * A URI that can be used to retrieve the contact's full-size photo. + */ + private String mAvatarUri; + + /** + * An opaque value that contains hints on how to find the contact if its row id changed + * as a result of a sync or aggregation + */ + private String mLookupKey; + + /** + * The type of data, for example Home or Work. + */ + private int mType; + + /** + * The user defined label for the the contact method. + */ + private String mLabel; /** * Parses a Contact entry for a Cursor loaded from the OS Strequents DB. */ public static ContactEntry fromCursor(Cursor cursor, Context context) { - int nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); + int idColumnIndex = PhoneLoader.getIdColumnIndex(cursor); int starredColumn = cursor.getColumnIndex(ContactsContract.Contacts.STARRED); int pinnedColumn = cursor.getColumnIndex("pinned"); + int displayNameColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); + int avatarUriColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI); + int avatarThumbnailColumnIndex = cursor.getColumnIndex( + ContactsContract.Contacts.PHOTO_THUMBNAIL_URI); + int lookupKeyColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); + int typeColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA2); + int labelColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA3); - String name = cursor.getString(nameColumn); + String name = cursor.getString(displayNameColumnIndex); String number = PhoneLoader.getPhoneNumber(cursor, context.getContentResolver()); int starred = cursor.getInt(starredColumn); int pinnedPosition = cursor.getInt(pinnedColumn); - return new ContactEntry(context, name, number, starred > 0, pinnedPosition); + ContactEntry contactEntry = new ContactEntry(context, name, number, starred > 0, + pinnedPosition); + Log.i("test-jia", "phone book number is " + number); + contactEntry.setId(cursor.getInt(idColumnIndex)); + contactEntry.setAvatarUri(cursor.getString(avatarUriColumnIndex)); + contactEntry.setAvatarThumbnailUri(cursor.getString(avatarThumbnailColumnIndex)); + contactEntry.setLookupKey(cursor.getString(lookupKeyColumnIndex)); + contactEntry.setType(cursor.getInt(typeColumnIndex)); + contactEntry.setLabel(cursor.getString(labelColumnIndex)); + return contactEntry; } public ContactEntry( Context context, String name, String number, boolean isStarred, int pinnedPosition) { mContext = context; - this.name = name; - this.number = number; - this.isStarred = isStarred; - this.pinnedPosition = pinnedPosition; + this.mDisplayName = name; + this.mNumber = number; + this.mIsStarred = isStarred; + this.mPinnedPosition = pinnedPosition; } /** @@ -64,13 +130,13 @@ public class ContactEntry implements Comparable<ContactEntry> { * It takes into account the number associated with a name for fail cases. */ public String getDisplayName() { - if (!TextUtils.isEmpty(name)) { - return name; + if (!TextUtils.isEmpty(mDisplayName)) { + return mDisplayName; } if (isVoicemail()) { return mContext.getResources().getString(R.string.voicemail); } else { - String displayName = TelecomUtils.getFormattedNumber(mContext, number); + String displayName = TelecomUtils.getFormattedNumber(mContext, mNumber); if (TextUtils.isEmpty(displayName)) { displayName = mContext.getString(R.string.unknown); } @@ -79,23 +145,23 @@ public class ContactEntry implements Comparable<ContactEntry> { } public boolean isVoicemail() { - return number.equals(TelecomUtils.getVoicemailNumber(mContext)); + return mNumber.equals(TelecomUtils.getVoicemailNumber(mContext)); } @Override public int compareTo(ContactEntry strequentContactEntry) { - if (isStarred == strequentContactEntry.isStarred) { - if (pinnedPosition == strequentContactEntry.pinnedPosition) { - if (name == strequentContactEntry.name) { - return compare(number, strequentContactEntry.number); + if (mIsStarred == strequentContactEntry.mIsStarred) { + if (mPinnedPosition == strequentContactEntry.mPinnedPosition) { + if (mDisplayName == strequentContactEntry.mDisplayName) { + return compare(mNumber, strequentContactEntry.mNumber); } - return compare(name, strequentContactEntry.name); + return compare(mDisplayName, strequentContactEntry.mDisplayName); } else { - if (pinnedPosition > 0 && strequentContactEntry.pinnedPosition > 0) { - return pinnedPosition - strequentContactEntry.pinnedPosition; + if (mPinnedPosition > 0 && strequentContactEntry.mPinnedPosition > 0) { + return mPinnedPosition - strequentContactEntry.mPinnedPosition; } - if (pinnedPosition > 0) { + if (mPinnedPosition > 0) { return -1; } @@ -103,7 +169,7 @@ public class ContactEntry implements Comparable<ContactEntry> { } } - if (isStarred) { + if (mIsStarred) { return -1; } @@ -114,10 +180,10 @@ public class ContactEntry implements Comparable<ContactEntry> { public boolean equals(Object obj) { if (obj instanceof ContactEntry) { ContactEntry other = (ContactEntry) obj; - if (compare(name, other.name) == 0 - && compare(number, other.number) == 0 - && isStarred == other.isStarred - && pinnedPosition == other.pinnedPosition) { + if (compare(mDisplayName, other.mDisplayName) == 0 + && compare(mNumber, other.mNumber) == 0 + && mIsStarred == other.mIsStarred + && mPinnedPosition == other.mPinnedPosition) { return true; } } @@ -127,10 +193,10 @@ public class ContactEntry implements Comparable<ContactEntry> { @Override public int hashCode() { int result = 17; - result = 31 * result + (isStarred ? 1 : 0); - result = 31 * result + pinnedPosition; - result = 31 * result + (name == null ? 0 : name.hashCode()); - result = 31 * result + (number == null ? 0 : number.hashCode()); + result = 31 * result + (mIsStarred ? 1 : 0); + result = 31 * result + mPinnedPosition; + result = 31 * result + (mDisplayName == null ? 0 : mDisplayName.hashCode()); + result = 31 * result + (mNumber == null ? 0 : mNumber.hashCode()); return result; } @@ -145,4 +211,65 @@ public class ContactEntry implements Comparable<ContactEntry> { return one.compareTo(two); } + + public int getId() { + return mId; + } + + private void setId(int id) { + mId = id; + } + + public String getAvatarUri() { + return mAvatarUri; + } + + private void setAvatarUri(String avatarUri) { + mAvatarUri = avatarUri; + } + + public String getLookupKey() { + return mLookupKey; + } + + private void setLookupKey(String lookupKey) { + mLookupKey = lookupKey; + } + + public int getType() { + return mType; + } + + private void setType(int type) { + mType = type; + } + + public String getLabel() { + return mLabel; + } + + private void setLabel(String label) { + mLabel = label; + } + + public String getAvatarThumbnailUri() { + return mAvatarThumbnailUri; + } + + private void setAvatarThumbnailUri(String avatarThumbnailUri) { + mAvatarThumbnailUri = avatarThumbnailUri; + } + + public String getNumber() { + return mNumber; + } + + public boolean isStarred() { + return mIsStarred; + } + + public int getPinnedPosition() { + return mPinnedPosition; + } + } diff --git a/src/com/android/car/dialer/StrequentsAdapter.java b/src/com/android/car/dialer/StrequentsAdapter.java index b321e6ec..8d14019a 100644 --- a/src/com/android/car/dialer/StrequentsAdapter.java +++ b/src/com/android/car/dialer/StrequentsAdapter.java @@ -320,10 +320,9 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder> private void onBindView(final CallLogViewHolder viewHolder, final ContactEntry entry) { viewHolder.itemView.setOnClickListener(v -> onViewClicked(viewHolder)); - final String number = entry.number; - // TODO(mcrico): Why is being a voicemail related to not having a name? - boolean isVoicemail = (entry.name == null) - && (number.equals(TelecomUtils.getVoicemailNumber(mContext))); + final String number = entry.getNumber(); + // TODO: Why is being a voicemail related to not having a name? + boolean isVoicemail = entry.isVoicemail(); String secondaryText = ""; if (!isVoicemail) { secondaryText = String.valueOf(TelecomUtils.getTypeFromNumber(mContext, number)); @@ -338,7 +337,7 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder> TelecomUtils.setContactBitmapAsync(mContext, viewHolder.icon, displayName, number); - if (entry.isStarred) { + if (entry.isStarred()) { viewHolder.smallIcon.setVisibility(View.VISIBLE); final int iconColor = mContext.getColor(android.R.color.white); viewHolder.smallIcon.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); diff --git a/src/com/android/car/dialer/TelecomActivity.java b/src/com/android/car/dialer/TelecomActivity.java index a4b8518c..0eeb7e8c 100644 --- a/src/com/android/car/dialer/TelecomActivity.java +++ b/src/com/android/car/dialer/TelecomActivity.java @@ -30,6 +30,7 @@ import androidx.car.drawer.CarDrawerActivity; import androidx.car.drawer.CarDrawerAdapter; import androidx.car.drawer.DrawerItemViewHolder; +import com.android.car.dialer.telecom.InMemoryPhoneBook; import com.android.car.dialer.telecom.PhoneLoader; import com.android.car.dialer.telecom.UiCall; import com.android.car.dialer.telecom.UiCallManager; @@ -94,6 +95,8 @@ public class TelecomActivity extends CarDrawerActivity implements CallListener { mUiCallManager = UiCallManager.init(getApplicationContext()); mUiBluetoothMonitor = new UiBluetoothMonitor(this); + InMemoryPhoneBook.init(getApplicationContext()); + findViewById(R.id.search).setOnClickListener( v -> startActivity(new Intent(this, ContactSearchActivity.class))); } @@ -105,6 +108,7 @@ public class TelecomActivity extends CarDrawerActivity implements CallListener { Log.d(TAG, "onDestroy"); } mUiBluetoothMonitor.tearDown(); + InMemoryPhoneBook.tearDown(); mUiCallManager.tearDown(); mUiCallManager = null; } @@ -249,11 +253,7 @@ public class TelecomActivity extends CarDrawerActivity implements CallListener { } Fragment fragment = StrequentsFragment.newInstance(mUiCallManager); - if (getCurrentFragment() instanceof DialerFragment) { - setContentFragmentWithSlideAndDelayAnimation(fragment); - } else { - setContentFragmentWithFadeAnimation(fragment); - } + setContentFragment(fragment); } private void showOngoingCallFragment() { diff --git a/src/com/android/car/dialer/telecom/InMemoryPhoneBook.java b/src/com/android/car/dialer/telecom/InMemoryPhoneBook.java new file mode 100644 index 00000000..3aaa21fc --- /dev/null +++ b/src/com/android/car/dialer/telecom/InMemoryPhoneBook.java @@ -0,0 +1,121 @@ +package com.android.car.dialer.telecom; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.telephony.PhoneNumberUtils; + +import com.android.car.dialer.ContactEntry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A singleton statically accessible helper class which pre-loads contacts list into memory so + * that they can be accessed more easily and quickly. + */ +public class InMemoryPhoneBook implements Loader.OnLoadCompleteListener<Cursor> { + private static InMemoryPhoneBook sInMemoryPhoneBook; + + private final Context mContext; + + private boolean mIsLoaded = false; + private List<ContactEntry> mContactEntries = new ArrayList<>(); + Map<Integer, List<ContactEntry>> mIdToContactEntryMap; + + private InMemoryPhoneBook(Context context) { + mContext = context; + } + + public static InMemoryPhoneBook init(Context context) { + if (sInMemoryPhoneBook == null) { + sInMemoryPhoneBook = new InMemoryPhoneBook(context); + sInMemoryPhoneBook.onInit(); + } else { + throw new IllegalStateException("Call teardown before reinitialized PhoneBook"); + } + return get(); + } + + public static InMemoryPhoneBook get() { + if (sInMemoryPhoneBook != null) { + return sInMemoryPhoneBook; + } else { + throw new IllegalStateException("Call init before get InMemoryPhoneBook"); + } + } + + public static void tearDown() { + sInMemoryPhoneBook = null; + } + + private void onInit() { + CursorLoader cursorLoader = createPhoneBookCursorLoader(); + cursorLoader.registerListener(0, this); + cursorLoader.startLoading(); + } + + public boolean isLoaded() { + return mIsLoaded; + } + + /** + * Returns a alphabetically sorted contact list. + */ + public List<ContactEntry> getOrderedContactEntries() { + return mContactEntries; + } + + @Nullable + public ContactEntry lookupContactEntry(String phoneNumber) { + for (ContactEntry contactEntry : mContactEntries) { + if (PhoneNumberUtils.compare(mContext, phoneNumber, contactEntry.getNumber())) { + return contactEntry; + } + } + return null; + } + + public Map<Integer, List<ContactEntry>> getIdToContactEntryMap() { + if (mIdToContactEntryMap == null) { + mIdToContactEntryMap = new HashMap<>(); + for (ContactEntry contactEntry : mContactEntries) { + List<ContactEntry> list; + if (mIdToContactEntryMap.containsKey(contactEntry.getId())) { + list = mIdToContactEntryMap.get(contactEntry.getId()); + } else { + list = new ArrayList<>(); + } + list.add(contactEntry); + } + } + return mIdToContactEntryMap; + } + + @Override + public void onLoadComplete(@NonNull Loader<Cursor> loader, @Nullable Cursor cursor) { + if (cursor != null) { + while (cursor.moveToNext()) { + mContactEntries.add(ContactEntry.fromCursor(cursor, mContext)); + } + } + mIsLoaded = true; + } + + private CursorLoader createPhoneBookCursorLoader() { + return new CursorLoader(mContext, + ContactsContract.Data.CONTENT_URI, + null, + ContactsContract.Data.MIMETYPE + " = '" + + ContactsContract.CommonDataKinds.Phone + .CONTENT_ITEM_TYPE + "'", + null, + ContactsContract.Contacts.DISPLAY_NAME + " ASC "); + } +} diff --git a/src/com/android/car/dialer/telecom/TelecomUtils.java b/src/com/android/car/dialer/telecom/TelecomUtils.java index ffae7654..78335b70 100644 --- a/src/com/android/car/dialer/telecom/TelecomUtils.java +++ b/src/com/android/car/dialer/telecom/TelecomUtils.java @@ -38,6 +38,7 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.widget.ImageView; + import com.android.car.apps.common.CircleBitmapDrawable; import com.android.car.apps.common.LetterTileDrawable; import com.android.car.dialer.R; @@ -48,7 +49,7 @@ import java.util.Locale; public class TelecomUtils { private final static String TAG = "Em.TelecomUtils"; - private static final String[] CONTACT_ID_PROJECTION = new String[] { + private static final String[] CONTACT_ID_PROJECTION = new String[]{ ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup.TYPE, ContactsContract.PhoneLookup.LABEL, @@ -73,6 +74,7 @@ public class TelecomUtils { /** * Return the contact id for the given contact id + * * @param id the contact id to get the photo for * @return the contact photo if it is found, null otherwise. */ @@ -97,6 +99,7 @@ public class TelecomUtils { /** * Return the contact id for the given phone number. + * * @param number Caller phone number * @return the contact id if it is found, 0 otherwise. */ @@ -115,8 +118,7 @@ public class TelecomUtils { int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID)); return id; } - } - finally { + } finally { if (cursor != null) { cursor.close(); } @@ -126,6 +128,7 @@ public class TelecomUtils { /** * Return the label for the given phone number. + * * @param number Caller phone number * @return the label if it is found, 0 otherwise. */ @@ -154,8 +157,7 @@ public class TelecomUtils { Phone.getTypeLabel(res, type, label); return typeLabel; } - } - finally { + } finally { if (cursor != null) { cursor.close(); } @@ -220,7 +222,8 @@ public class TelecomUtils { return getDisplayName(context, number, null); } - private static String getDisplayName(Context context, String number, Uri gatewayOriginalAddress) { + private static String getDisplayName(Context context, String number, + Uri gatewayOriginalAddress) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "getDisplayName: " + number + ", gatewayOriginalAddress: " + gatewayOriginalAddress); @@ -257,7 +260,7 @@ public class TelecomUtils { String name = null; try { cursor = cr.query(uri, - new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); + new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); if (cursor != null && cursor.moveToFirst()) { name = cursor.getString(0); } @@ -306,7 +309,7 @@ public class TelecomUtils { */ public static String callStateToUiString(Context context, int state) { Resources res = context.getResources(); - switch(state) { + switch (state) { case Call.STATE_ACTIVE: return res.getString(R.string.call_state_call_active); case Call.STATE_HOLDING: @@ -348,23 +351,21 @@ public class TelecomUtils { * @param number A key to have a consisten color per phone number. * @return A worker task if a new one was needed to load the bitmap. */ - @Nullable public static ContactBitmapWorker setContactBitmapAsync(Context context, + @Nullable + public static ContactBitmapWorker setContactBitmapAsync(Context context, final ImageView icon, final @Nullable String name, final String number) { return ContactBitmapWorker.loadBitmap(context.getContentResolver(), icon, number, - new ContactBitmapWorker.BitmapWorkerListener() { - @Override - public void onBitmapLoaded(@Nullable Bitmap bitmap) { - Resources r = icon.getResources(); - if (bitmap != null) { - icon.setScaleType(ImageView.ScaleType.CENTER_CROP); - icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap)); - } else { - icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); - letterTileDrawable.setContactDetails(name, number); - letterTileDrawable.setIsCircular(true); - icon.setImageDrawable(letterTileDrawable); - } + bitmap -> { + Resources r = icon.getResources(); + if (bitmap != null) { + icon.setScaleType(ImageView.ScaleType.CENTER_CROP); + icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap)); + } else { + icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); + letterTileDrawable.setContactDetails(name, number); + letterTileDrawable.setIsCircular(true); + icon.setImageDrawable(letterTileDrawable); } }); } diff --git a/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java b/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java index 6dc0c3d4..2eaff9f9 100644 --- a/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java +++ b/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java @@ -23,6 +23,7 @@ import androidx.car.widget.ListItemProvider; import androidx.car.widget.TextListItem; import com.android.car.dialer.telecom.UiCallManager; +import com.android.car.dialer.ui.listitem.CallLogListItem; import java.util.ArrayList; import java.util.List; @@ -34,7 +35,7 @@ public class CallHistoryListItemProvider extends ListItemProvider { public void setCallHistoryListItems(Context context, List<CallLogListingTask.CallLogItem> items) { for (CallLogListingTask.CallLogItem callLogItem : items) { - TextListItem callLogListItem = new TextListItem(context); + TextListItem callLogListItem = new CallLogListItem(context, callLogItem); callLogListItem.setPrimaryActionIcon( new BitmapDrawable(context.getResources(), callLogItem.mIcon), true); diff --git a/src/com/android/car/dialer/ui/CallLogListingTask.java b/src/com/android/car/dialer/ui/CallLogListingTask.java index a4211d6b..5a73d1bb 100644 --- a/src/com/android/car/dialer/ui/CallLogListingTask.java +++ b/src/com/android/car/dialer/ui/CallLogListingTask.java @@ -15,9 +15,9 @@ */ package com.android.car.dialer.ui; -import android.content.ContentResolver; +import static android.provider.ContactsContract.CommonDataKinds.Phone.getTypeLabel; + import android.content.Context; -import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.os.AsyncTask; @@ -27,15 +27,18 @@ import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.text.format.DateUtils; -import com.android.car.apps.common.CircleBitmapDrawable; -import com.android.car.apps.common.LetterTileDrawable; +import com.android.car.dialer.ContactEntry; import com.android.car.dialer.R; +import com.android.car.dialer.telecom.InMemoryPhoneBook; import com.android.car.dialer.telecom.PhoneLoader; import com.android.car.dialer.telecom.TelecomUtils; import java.util.ArrayList; import java.util.List; +/** + * Async task which loads call history. + */ public class CallLogListingTask extends AsyncTask<Void, Void, Void> { public static class CallLogItem { public final String mTitle; @@ -55,10 +58,6 @@ public class CallLogListingTask extends AsyncTask<Void, Void, Void> { void onLoadComplete(List<CallLogItem> items); } - - // Like a constant but needs a context so not static. - private final String VOICEMAIL_NUMBER; - private Context mContext; private Cursor mCursor; private List<CallLogItem> mItems; @@ -70,7 +69,6 @@ public class CallLogListingTask extends AsyncTask<Void, Void, Void> { mCursor = cursor; mItems = new ArrayList<>(mCursor.getCount()); mListener = listener; - VOICEMAIL_NUMBER = TelecomUtils.getVoicemailNumber(mContext); } private String maybeAppendCount(StringBuilder sb, int count) { @@ -80,42 +78,21 @@ public class CallLogListingTask extends AsyncTask<Void, Void, Void> { return sb.toString(); } - private String getContactName(String cachedName, String number, - int count, boolean isVoicemail) { - if (cachedName != null) { - return maybeAppendCount(new StringBuilder(cachedName), count); - } - + private String getContactName(String cachedName, String number, int count, + boolean isVoicemail) { StringBuilder sb = new StringBuilder(); if (isVoicemail) { sb.append(mContext.getString(R.string.voicemail)); + } else if (cachedName != null) { + sb.append(cachedName); + } else if (!TextUtils.isEmpty(number)) { + sb.append(TelecomUtils.getFormattedNumber(mContext, number)); } else { - String displayName = TelecomUtils.getDisplayName(mContext, number); - if (TextUtils.isEmpty(displayName)) { - displayName = mContext.getString(R.string.unknown); - } - sb.append(displayName); + sb.append(mContext.getString(R.string.unknown)); } - return maybeAppendCount(sb, count); } - private Bitmap getContactImage(Context context, ContentResolver contentResolver, - String name, String number) { - Resources r = context.getResources(); - int size = r.getDimensionPixelSize(R.dimen.dialer_menu_icon_container_width); - - Bitmap bitmap = TelecomUtils.getContactPhotoFromNumber(contentResolver, number); - if (bitmap != null) { - return new CircleBitmapDrawable(r, bitmap).toBitmap(size); - } - - LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); - letterTileDrawable.setContactDetails(name, number); - letterTileDrawable.setIsCircular(true); - return letterTileDrawable.toBitmap(size); - } - private static CharSequence getRelativeTime(long millis) { boolean validTimestamp = millis > 0; @@ -126,71 +103,71 @@ public class CallLogListingTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { - ContentResolver resolver = mContext.getContentResolver(); - - try { - if (mCursor != null) { - int cachedNameColumn = PhoneLoader.getNameColumnIndex(mCursor); + if (mCursor != null) { + try { int numberColumn = PhoneLoader.getNumberColumnIndex(mCursor); int dateColumn = mCursor.getColumnIndex(CallLog.Calls.DATE); while (mCursor.moveToNext()) { int count = 1; String number = mCursor.getString(numberColumn); - - // We want to group calls to the same number into one so seek forward as many + // We want to group calls to the same number into one so seek + // forward as many // entries as possible as long as the number is the same. int position = mCursor.getPosition(); while (mCursor.moveToNext()) { String nextNumber = mCursor.getString(numberColumn); - if (equalNumbers(number, nextNumber)) { + if (PhoneNumberUtils.compare(mContext, number, nextNumber)) { count++; } else { break; } } + mCursor.moveToPosition(position); - boolean isVoicemail = number.equals(VOICEMAIL_NUMBER); - String name = getContactName(mCursor.getString(cachedNameColumn), - number, count, isVoicemail); + boolean isVoicemail = PhoneNumberUtils.isVoiceMailNumber(number); + ContactEntry contactEntry = InMemoryPhoneBook.get().lookupContactEntry(number); + String nameWithCount = getContactName( + contactEntry != null ? contactEntry.getDisplayName() : null, + number, + count, + isVoicemail); - // Not sure why this is the only column checked here but I'm assuming this was - // to work around some bug on some device. + // Not sure why this is the only column checked here but I'm + // assuming this was to work around some bug on some device. long millis = dateColumn == -1 ? 0 : mCursor.getLong(dateColumn); - StringBuffer secondaryText = new StringBuffer(); + StringBuffer secondaryTextStringBuilder = new StringBuffer(); CharSequence relativeDate = getRelativeTime(millis); - // Append the type (work, mobile etc.) if it isnt voicemail. + // Append the type (work, mobile etc.) if it isn't voicemail. if (!isVoicemail) { - CharSequence type = TelecomUtils.getTypeFromNumber(mContext, number); - secondaryText.append(type); + CharSequence type = contactEntry != null + ? getTypeLabel(mContext.getResources(), contactEntry.getType(), + contactEntry.getLabel()) + : ""; + secondaryTextStringBuilder.append(type); if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(relativeDate)) { - secondaryText.append(", "); + secondaryTextStringBuilder.append(", "); } } - // Add in the timestamp. if (relativeDate != null) { - secondaryText.append(relativeDate); + secondaryTextStringBuilder.append(relativeDate); } - Bitmap contactImage = getContactImage(mContext, resolver, name, number); - - CallLogItem item = - new CallLogItem(name, secondaryText.toString(), number, contactImage); + CallLogItem item = new CallLogItem(nameWithCount, + secondaryTextStringBuilder.toString(), + number, null); mItems.add(item); - // Since we deduplicated count rows, we can move all the way to that row so the // next iteration takes us to the row following the last duplicate row. if (count > 1) { mCursor.moveToPosition(position + count - 1); } } - } - } finally { - if (mCursor != null) { + } finally { mCursor.close(); } } @@ -201,59 +178,4 @@ public class CallLogListingTask extends AsyncTask<Void, Void, Void> { protected void onPostExecute(Void aVoid) { mListener.onLoadComplete(mItems); } - - /** - * Determines if the specified number is actually a URI - * (i.e. a SIP address) rather than a regular PSTN phone number, - * based on whether or not the number contains an "@" character. - * - * @return true if number contains @ - * - * from android.telephony.PhoneNumberUtils - */ - public static boolean isUriNumber(String number) { - // Note we allow either "@" or "%40" to indicate a URI, in case - // the passed-in string is URI-escaped. (Neither "@" nor "%40" - // will ever be found in a legal PSTN number.) - return number != null && (number.contains("@") || number.contains("%40")); - } - - private static boolean equalNumbers(String number1, String number2) { - if (isUriNumber(number1) || isUriNumber(number2)) { - return compareSipAddresses(number1, number2); - } else { - return PhoneNumberUtils.compare(number1, number2); - } - } - - private static boolean compareSipAddresses(String number1, String number2) { - if (number1 == null || number2 == null) { - return number1 == null && number2 == null; - } - - String[] address1 = splitSipAddress(number1); - String[] address2 = splitSipAddress(number2); - - return address1[0].equals(address2[0]) && address1[1].equals(address2[1]); - } - - /** - * Splits a sip address on either side of the @ sign and returns both halves. - * If there is no @ sign, user info will be number and rest will be empty string - * @param number the sip number to split - * @return a string array of size 2. Element 0 is the user info (left side of @ sign) and - * element 1 is the rest (right side of @ sign). - */ - private static String[] splitSipAddress(String number) { - String[] values = new String[2]; - int index = number.indexOf('@'); - if (index == -1) { - values[0] = number; - values[1] = ""; - } else { - values[0] = number.substring(0, index); - values[1] = number.substring(index); - } - return values; - } } diff --git a/src/com/android/car/dialer/ui/DialerInfoFragment.java b/src/com/android/car/dialer/ui/DialerInfoFragment.java new file mode 100644 index 00000000..f9906563 --- /dev/null +++ b/src/com/android/car/dialer/ui/DialerInfoFragment.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 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.car.dialer.ui; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.telecom.Call; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.android.car.apps.common.FabDrawable; +import com.android.car.dialer.R; +import com.android.car.dialer.telecom.TelecomUtils; +import com.android.car.dialer.telecom.UiCall; +import com.android.car.dialer.telecom.UiCallManager; + +/** + * Holds dialer information such as dialed number and shows proper action based on current call + * state such as call/mute. + */ +public class DialerInfoFragment extends Fragment { + private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY"; + private static final int MAX_DIAL_NUMBER = 20; + + private TextView mTitleView; + private TextView mBodyView; + + private ImageButton mCallButton; + private ImageButton mDeleteButton; + + private ImageButton mEndCallButton; + private ImageButton mMuteButton; + + private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER); + + public static DialerInfoFragment newInstance(@Nullable String dialNumber) { + DialerInfoFragment fragment = new DialerInfoFragment(); + + if (!TextUtils.isEmpty(dialNumber)) { + Bundle args = new Bundle(); + args.putString(DIAL_NUMBER_KEY, dialNumber); + fragment.setArguments(args); + } + + return fragment; + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View fragmentView = inflater.inflate(R.layout.dialer_info_fragment, container, false); + mTitleView = fragmentView.findViewById(R.id.title); + mBodyView = fragmentView.findViewById(R.id.body); + mCallButton = fragmentView.findViewById(R.id.call_button); + mDeleteButton = fragmentView.findViewById(R.id.delete_button); + mEndCallButton = fragmentView.findViewById(R.id.end_call_button); + mMuteButton = fragmentView.findViewById(R.id.mute_button); + + FabDrawable answerCallDrawable = new FabDrawable(getContext()); + answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_call)); + mCallButton.setBackground(answerCallDrawable); + mCallButton.setOnClickListener((unusedView) -> { + if (!TextUtils.isEmpty(mNumber.toString())) { + UiCallManager.get().safePlaceCall(mNumber.toString(), false); + } + }); + mDeleteButton.setOnClickListener(v -> { + removeLastDigit(); + }); + mDeleteButton.setOnLongClickListener(v -> { + // Clear all on long-press + clearDialedNumber(); + return true; + }); + + updateView(); + + Bundle args = getArguments(); + if (args != null) { + clearDialedNumber(); + appendDialedNumber(args.getString(DIAL_NUMBER_KEY)); + } + + return fragmentView; + } + + /** + * Append more number to the end of dialed number. + */ + public void appendDialedNumber(String number) { + mNumber.append(number); + mTitleView.setText(getFormattedNumber(mNumber.toString())); + } + + /** + * Remove last digit of the dialed number. If there's no number left to delete, there's no + * operation to be take. + */ + public void removeLastDigit() { + if (mNumber.length() != 0) { + mNumber.deleteCharAt(mNumber.length() - 1); + mTitleView.setText(getFormattedNumber(mNumber.toString())); + } + } + + private void updateView() { + UiCall onGoingCall = UiCallManager.get().getPrimaryCall(); + if (onGoingCall == null) { + showPreDialUi(); + } else if (onGoingCall.getState() == Call.STATE_CONNECTING) { + showDialingUi(onGoingCall); + } else if (onGoingCall.getState() == Call.STATE_ACTIVE) { + showInCallUi(); + } + } + + private void showPreDialUi() { + mCallButton.setVisibility(View.VISIBLE); + mDeleteButton.setVisibility(View.VISIBLE); + + mEndCallButton.setVisibility(View.GONE); + mMuteButton.setVisibility(View.GONE); + } + + private void showDialingUi(UiCall uiCall) { + FabDrawable answerCallDrawable = new FabDrawable(getContext()); + answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_end_call)); + mEndCallButton.setBackground(answerCallDrawable); + mEndCallButton.setVisibility(View.VISIBLE); + mMuteButton.setVisibility(View.VISIBLE); + mBodyView.setVisibility(View.VISIBLE); + + mDeleteButton.setVisibility(View.GONE); + mCallButton.setVisibility(View.GONE); + } + + private void showInCallUi() { + // TODO: Implement this function. + } + + private String getFormattedNumber(String number) { + return TelecomUtils.getFormattedNumber(getContext(), number); + } + + private void clearDialedNumber() { + mNumber.setLength(0); + mTitleView.setText(getFormattedNumber(mNumber.toString())); + } +} diff --git a/src/com/android/car/dialer/ui/listitem/CallLogListItem.java b/src/com/android/car/dialer/ui/listitem/CallLogListItem.java index c0cb4143..72939c5a 100644 --- a/src/com/android/car/dialer/ui/listitem/CallLogListItem.java +++ b/src/com/android/car/dialer/ui/listitem/CallLogListItem.java @@ -16,10 +16,15 @@ package com.android.car.dialer.ui.listitem; import android.content.Context; +import android.content.res.Resources; import android.graphics.drawable.BitmapDrawable; import androidx.car.widget.TextListItem; +import com.android.car.apps.common.CircleBitmapDrawable; +import com.android.car.apps.common.LetterTileDrawable; +import com.android.car.dialer.telecom.ContactBitmapWorker; +import com.android.car.dialer.telecom.TelecomUtils; import com.android.car.dialer.telecom.UiCallManager; import com.android.car.dialer.ui.CallHistoryListItemProvider; import com.android.car.dialer.ui.CallLogListingTask; @@ -41,13 +46,21 @@ public class CallLogListItem extends TextListItem { @Override public void onBind(ViewHolder viewHolder) { super.onBind(viewHolder); - setPrimaryActionIcon( - new BitmapDrawable(mContext.getResources(), mCallLogItem.mIcon), true); - setTitle(mCallLogItem.mTitle); - setBody(mCallLogItem.mText); - - viewHolder.itemView.setOnClickListener((v) -> { - UiCallManager.get().safePlaceCall(mCallLogItem.mNumber, false); - }); + ContactBitmapWorker.loadBitmap(mContext.getContentResolver(), viewHolder.getPrimaryIcon(), + mCallLogItem.mNumber, + bitmap -> { + Resources r = mContext.getResources(); + if (bitmap != null) { + setPrimaryActionIcon(new CircleBitmapDrawable(r, bitmap), true); + } else { + LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); + letterTileDrawable.setContactDetails(mCallLogItem.mTitle, + mCallLogItem.mNumber); + letterTileDrawable.setIsCircular(true); + setPrimaryActionIcon(letterTileDrawable, true); + } + // force rebind the view. + super.onBind(viewHolder); + }); } } diff --git a/src/com/android/car/dialer/ui/listitem/ContactListItem.java b/src/com/android/car/dialer/ui/listitem/ContactListItem.java index 03c5575b..a076013b 100644 --- a/src/com/android/car/dialer/ui/listitem/ContactListItem.java +++ b/src/com/android/car/dialer/ui/listitem/ContactListItem.java @@ -16,10 +16,13 @@ package com.android.car.dialer.ui.listitem; import android.content.Context; +import android.content.res.Resources; import androidx.car.widget.TextListItem; -import com.android.car.dialer.telecom.TelecomUtils; +import com.android.car.apps.common.CircleBitmapDrawable; +import com.android.car.apps.common.LetterTileDrawable; +import com.android.car.dialer.telecom.ContactBitmapWorker; import com.android.car.dialer.ui.ContactListFragment; /** @@ -38,7 +41,21 @@ public class ContactListItem extends TextListItem { @Override public void onBind(ViewHolder viewHolder) { super.onBind(viewHolder); - TelecomUtils.setContactBitmapAsync(mContext, viewHolder.getPrimaryIcon(), - mContactItem.mDisplayName, mContactItem.mNumber); + ContactBitmapWorker.loadBitmap(mContext.getContentResolver(), viewHolder.getPrimaryIcon(), + mContactItem.mNumber, + bitmap -> { + Resources r = mContext.getResources(); + if (bitmap != null) { + setPrimaryActionIcon(new CircleBitmapDrawable(r, bitmap), true); + } else { + LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); + letterTileDrawable.setContactDetails(mContactItem.mDisplayName, + mContactItem.mNumber); + letterTileDrawable.setIsCircular(true); + setPrimaryActionIcon(letterTileDrawable, true); + } + // force rebind the view. + super.onBind(viewHolder); + }); } } |