diff options
author | Matt Garnes <matt@cyngn.com> | 2015-04-30 11:17:19 -0700 |
---|---|---|
committer | Matt Garnes <matt@cyngn.com> | 2015-04-30 11:17:19 -0700 |
commit | aaa411b7f1294089da76732cb5b21da467e48422 (patch) | |
tree | b51f4f45c46917dfb631186538901ca13f940a54 /src/com/android | |
parent | 61899450eb05c33835d3df99e6c75b3115c559ea (diff) | |
parent | 77e9bb4ba4b9b49b95a26d80ead59aa37d016232 (diff) | |
download | android_packages_apps_Dialer-caf/cm-12.1.tar.gz android_packages_apps_Dialer-caf/cm-12.1.tar.bz2 android_packages_apps_Dialer-caf/cm-12.1.zip |
Merge remote-tracking branch 'caf/LA.BR.1.2.3_1' into caf/cm-12.1caf/cm-12.1
Diffstat (limited to 'src/com/android')
19 files changed, 380 insertions, 110 deletions
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java index b5645d5e9..475d398ae 100644..100755 --- a/src/com/android/dialer/DialtactsActivity.java +++ b/src/com/android/dialer/DialtactsActivity.java @@ -187,6 +187,11 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O */ private ListsFragment mListsFragment; + /** + * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can + * be commited. + */ + private boolean mStateSaved; private boolean mInDialpadSearch; private boolean mInRegularSearch; private boolean mClearSearchOnPause; @@ -236,6 +241,12 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O private int mActionBarHeight; + /** + * The text returned from a voice search query. Set in {@link #onActivityResult} and used in + * {@link #onResume()} to populate the search box. + */ + private String mVoiceSearchQuery; + private class OptionsPopupMenu extends PopupMenu { public OptionsPopupMenu(Context context, View anchor) { super(context, anchor, Gravity.END); @@ -483,6 +494,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O @Override protected void onResume() { super.onResume(); + mStateSaved = false; if (mFirstLaunch) { displayFragment(getIntent()); } else if (!phoneIsInUse() && mInCallDialpadUp) { @@ -492,6 +504,16 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O showDialpadFragment(false); mShowDialpadOnResume = false; } + + // If there was a voice query result returned in the {@link #onActivityResult} callback, it + // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be + // shown until onResume has completed. Active the search UI and set the search term now. + if (!TextUtils.isEmpty(mVoiceSearchQuery)) { + mActionBarController.onSearchBoxTapped(); + mSearchView.setText(mVoiceSearchQuery); + mVoiceSearchQuery = null; + } + mFirstLaunch = false; prepareVoiceSearchButton(); mDialerDatabaseHelper.startSmartDialUpdateThread(); @@ -506,6 +528,9 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O hideDialpadAndSearchUi(); mClearSearchOnPause = false; } + if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) { + commitDialpadFragmentHide(); + } super.onPause(); } @@ -518,6 +543,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown); mActionBarController.saveInstanceState(outState); + mStateSaved = true; } @Override @@ -645,7 +671,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O RecognizerIntent.EXTRA_RESULTS); if (matches.size() > 0) { final String match = matches.get(0); - mSearchView.setText(match); + mVoiceSearchQuery = match; } else { Log.e(TAG, "Voice search - nothing heard"); } @@ -732,7 +758,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O * @see #onDialpadShown */ private void showDialpadFragment(boolean animate) { - if (mIsDialpadShown) { + if (mIsDialpadShown || mStateSaved) { return; } mIsDialpadShown = true; @@ -818,10 +844,11 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O * Finishes hiding the dialpad fragment after any animations are completed. */ public void commitDialpadFragmentHide() { - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.hide(mDialpadFragment); - ft.commit(); - + if (!mStateSaved && !mDialpadFragment.isHidden()) { + final FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.hide(mDialpadFragment); + ft.commit(); + } mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); } @@ -940,6 +967,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O @Override public void onNewIntent(Intent newIntent) { setIntent(newIntent); + mStateSaved = false; displayFragment(newIntent); invalidateOptionsMenu(); @@ -974,7 +1002,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O * Shows the search fragment */ private void enterSearchUi(boolean smartDialSearch, String query) { - if (getFragmentManager().isDestroyed()) { + if (mStateSaved || getFragmentManager().isDestroyed()) { // Weird race condition where fragment is doing work after the activity is destroyed // due to talkback being on (b/10209937). Just return since we can't do any // constructive here. @@ -1027,7 +1055,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O */ private void exitSearchUi() { // See related bug in enterSearchUI(); - if (getFragmentManager().isDestroyed() || !isSafeToCommitTransactions()) { + if (getFragmentManager().isDestroyed() || mStateSaved) { return; } @@ -1064,6 +1092,10 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O mDialpadFragment.hideAndClearDialConference(); } + if (mStateSaved) { + return; + } + if (mIsDialpadShown || mIsRecipientsShown) { if (TextUtils.isEmpty(mSearchQuery) || (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible() @@ -1162,8 +1194,22 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O } public static Intent getAddNumberToContactIntent(CharSequence text) { - final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Intents.Insert.PHONE, text); + return getAddToContactIntent(null /* name */, text /* phoneNumber */, + -1 /* phoneNumberType */); + } + + public static Intent getAddToContactIntent(CharSequence name, CharSequence phoneNumber, + int phoneNumberType) { + Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Intents.Insert.PHONE, phoneNumber); + // Only include the name and phone type extras if they are specified (the method + // getAddNumberToContactIntent does not use them). + if (name != null) { + intent.putExtra(Intents.Insert.NAME, name); + } + if (phoneNumberType != -1) { + intent.putExtra(Intents.Insert.PHONE_TYPE, phoneNumberType); + } intent.setType(Contacts.CONTENT_ITEM_TYPE); return intent; } diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java index f6ae7da41..1956085c5 100644..100755 --- a/src/com/android/dialer/PhoneCallDetailsHelper.java +++ b/src/com/android/dialer/PhoneCallDetailsHelper.java @@ -16,6 +16,7 @@ package com.android.dialer; +import android.content.Context; import android.content.res.Resources; import android.provider.CallLog; import android.provider.CallLog.Calls; @@ -50,6 +51,7 @@ public class PhoneCallDetailsHelper { /** The maximum number of icons will be shown to represent the call types in a group. */ private static final int MAX_CALL_TYPE_ICONS = 3; + private final Context mContext; private final Resources mResources; /** The injected current time in milliseconds since the epoch. Used only by tests. */ private Long mCurrentTimeMillisForTest; @@ -69,8 +71,9 @@ public class PhoneCallDetailsHelper { * * @param resources used to look up strings */ - public PhoneCallDetailsHelper(Resources resources, CallTypeHelper callTypeHelper, + public PhoneCallDetailsHelper(Context context, Resources resources, PhoneNumberUtilsWrapper phoneUtils) { + mContext = context; mResources = resources; mPhoneNumberUtilsWrapper = phoneUtils; mPhoneNumberHelper = new PhoneNumberDisplayHelper(mPhoneNumberUtilsWrapper, resources); @@ -129,6 +132,24 @@ public class PhoneCallDetailsHelper { views.callAccountIcon.setVisibility(View.GONE); } + /* + // Set the account label if it exists. + String accountLabel = details.accountLabel; + if (accountLabel != null) { + views.callAccountLabel.setVisibility(View.VISIBLE); + views.callAccountLabel.setText(accountLabel); + int color = PhoneAccountUtils.getAccountColor(mContext, details.accountHandle); + if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { + int defaultColor = R.color.dialtacts_secondary_text_color; + views.callAccountLabel.setTextColor(mContext.getResources().getColor(defaultColor)); + } else { + views.callAccountLabel.setTextColor(color); + } + } else { + views.callAccountLabel.setVisibility(View.GONE); + } + */ + CharSequence nameText; CharSequence displayNumber = mPhoneNumberHelper.getDisplayNumber(details.accountId, details.number, diff --git a/src/com/android/dialer/SpeedDialListActivity.java b/src/com/android/dialer/SpeedDialListActivity.java index d481bbab9..7ab8deecf 100755 --- a/src/com/android/dialer/SpeedDialListActivity.java +++ b/src/com/android/dialer/SpeedDialListActivity.java @@ -62,6 +62,7 @@ import android.widget.ListView; import android.widget.PopupMenu; import android.widget.QuickContactBadge; import android.widget.TextView; +import android.view.Gravity; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; @@ -306,7 +307,7 @@ public class SpeedDialListActivity extends ListActivity implements if (record == null) { showAddSpeedDialDialog(number); } else { - PopupMenu pm = new PopupMenu(this, view); + PopupMenu pm = new PopupMenu(this, view, Gravity.START); pm.getMenu().add(number, MENU_REPLACE, 0, R.string.speed_dial_replace); pm.getMenu().add(number, MENU_DELETE, 0, R.string.speed_dial_delete); pm.setOnMenuItemClickListener(this); diff --git a/src/com/android/dialer/calllog/CallLogActivity.java b/src/com/android/dialer/calllog/CallLogActivity.java index 78bd2e29b..2e55974fb 100755 --- a/src/com/android/dialer/calllog/CallLogActivity.java +++ b/src/com/android/dialer/calllog/CallLogActivity.java @@ -33,6 +33,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.ViewGroup; import android.widget.SearchView; import android.widget.SearchView.OnCloseListener; import android.widget.SearchView.OnQueryTextListener; @@ -76,17 +77,28 @@ public class CallLogActivity extends AnalyticsActivity implements public Fragment getItem(int position) { switch (position) { case TAB_INDEX_MSIM: - mMSimCallsFragment = new MSimCallLogFragment(); - mMSimCallsFragment.setHasOptionsMenu(true); - return mMSimCallsFragment; + return new MSimCallLogFragment(); case TAB_INDEX_MSIM_STATS: - mStatsFragment = new CallStatsFragment(); - return mStatsFragment; + return new CallStatsFragment(); } throw new IllegalStateException("No fragment at position " + position); } @Override + public Object instantiateItem(ViewGroup container, int position) { + final Object fragment = super.instantiateItem(container, position); + switch (position) { + case TAB_INDEX_MSIM: + mMSimCallsFragment = (MSimCallLogFragment)fragment; + break; + case TAB_INDEX_MSIM_STATS: + mStatsFragment = (CallStatsFragment)fragment; + break; + } + return fragment; + } + + @Override public CharSequence getPageTitle(int position) { return mTabTitles[position]; } diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 65e149a47..01b07b68f 100755 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -20,16 +20,20 @@ import android.accounts.Account; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.Loader; import android.content.res.Resources; import android.database.Cursor; +import android.database.sqlite.SQLiteFullException; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.CallLog.Calls; +import android.provider.ContactsContract; import android.provider.ContactsContract.PhoneLookup; import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionManager; import android.text.TextUtils; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.AccessibilityDelegate; @@ -37,6 +41,8 @@ import android.view.ViewGroup; import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -45,6 +51,8 @@ import com.android.common.widget.GroupingListAdapter; import com.android.contacts.common.CallUtil; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; +import com.android.contacts.common.model.Contact; +import com.android.contacts.common.model.ContactLoader; import com.android.contacts.common.util.UriUtils; import com.android.dialer.DialtactsActivity; import com.android.dialer.PhoneCallDetails; @@ -57,6 +65,7 @@ import com.android.dialer.calllog.CallLogAdapterHelper.NumberWithCountryIso; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; @@ -64,8 +73,10 @@ import java.util.LinkedList; * Adapter class to fill in data for the Call Log. */ public class CallLogAdapter extends GroupingListAdapter - implements CallLogAdapterHelper.Callback, CallLogGroupBuilder.GroupCreator { + implements CallLogAdapterHelper.Callback, CallLogGroupBuilder.GroupCreator, + OnScrollListener { + private static final String TAG = CallLogAdapter.class.getSimpleName(); private static final int VOICEMAIL_TRANSCRIPTION_MAX_LINES = 10; /** The enumeration of {@link android.os.AsyncTask} objects used in this class. */ @@ -225,7 +236,6 @@ public class CallLogAdapter extends GroupingListAdapter Toast.LENGTH_SHORT); Resources resources = mContext.getResources(); - CallTypeHelper callTypeHelper = new CallTypeHelper(resources); mCallLogBackgroundColor = resources.getColor(R.color.background_dialer_list_items); mExpandedBackgroundColor = resources.getColor(R.color.call_log_expanded_background_color); mExpandedTranslationZ = resources.getDimension(R.dimen.call_log_expanded_translation_z); @@ -235,7 +245,7 @@ public class CallLogAdapter extends GroupingListAdapter mAdapterHelper = new CallLogAdapterHelper(context, this, contactInfoHelper, mPhoneNumberHelper); PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper( - resources, callTypeHelper, new PhoneNumberUtilsWrapper()); + mContext, resources, new PhoneNumberUtilsWrapper()); mCallLogViewsHelper = new CallLogListItemHelper( phoneCallDetailsHelper, mPhoneNumberHelper, resources); @@ -463,7 +473,10 @@ public class CallLogAdapter extends GroupingListAdapter Calls.DURATION_TYPE_ACTIVE, subId, operator); } - mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details); + if (!mAdapterHelper.isBusy()) { + // Only update views when ListView's scroll state is not SCROLL_STATE_FLING. + mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details); + } int contactType = ContactPhotoManager.TYPE_DEFAULT; @@ -529,10 +542,8 @@ public class CallLogAdapter extends GroupingListAdapter * @return The day group for the call. */ private int getDayGroupForCall(long callId) { - if (mDayGroups.containsKey(callId)) { - return mDayGroups.get(callId); - } - return CallLogGroupBuilder.DAY_GROUP_NONE; + Integer result = mDayGroups.get(callId); + return result == null ? CallLogGroupBuilder.DAY_GROUP_NONE : result.intValue(); } /** * Determines if a call log row with the given Id is expanded. @@ -736,7 +747,7 @@ public class CallLogAdapter extends GroupingListAdapter } protected void bindBadge( - View view, ContactInfo info, final PhoneCallDetails details, int callType) { + View view, final ContactInfo info, final PhoneCallDetails details, int callType) { // Do not show badge in call log. if (!mIsCallLog) { final ViewStub stub = (ViewStub) view.findViewById(R.id.link_stub); @@ -749,14 +760,30 @@ public class CallLogAdapter extends GroupingListAdapter mBadgeText = (TextView) inflated.findViewById(R.id.badge_text); } - mBadgeContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - final Intent intent = - DialtactsActivity.getAddNumberToContactIntent(details.number); - mContext.startActivity(intent); - } - }); + mBadgeContainer.setVisibility(View.VISIBLE); + mBadgeImageView = (ImageView) mBadgeContainer.findViewById(R.id.badge_image); + mBadgeText = (TextView) mBadgeContainer.findViewById(R.id.badge_text); + + final View clickableArea = mBadgeContainer.findViewById(R.id.badge_link_container); + if (clickableArea != null) { + clickableArea.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // If no lookup uri is provided, we need to rely on what information + // we have available; namely the phone number and name. + if (info.lookupUri == null) { + final Intent intent = + DialtactsActivity.getAddToContactIntent(details.name, + details.number, + details.numberType); + DialerUtils.startActivityWithErrorToast(mContext, intent, + R.string.add_contact_not_available); + } else { + addContactFromLookupUri(info.lookupUri); + } + } + }); + } mBadgeImageView.setImageResource(R.drawable.ic_person_add_24dp); mBadgeText.setText(R.string.recentCalls_addToContact); } else { @@ -835,14 +862,18 @@ public class CallLogAdapter extends GroupingListAdapter if (!needsUpdate) return; - if (countryIso == null) { - mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values, - Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL", - new String[]{ number }); - } else { - mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values, - Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?", - new String[]{ number, countryIso }); + try { + if (countryIso == null) { + mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values, + Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL", + new String[]{ number }); + } else { + mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values, + Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?", + new String[]{ number, countryIso }); + } + } catch (SQLiteFullException e) { + Log.e(TAG, "Unable to update contact info in call log db", e); } } @@ -1079,4 +1110,76 @@ public class CallLogAdapter extends GroupingListAdapter public void setQueryString(String filter) { mFilterString = filter; } + + /** + * Invokes the "add contact" activity given the expanded contact information stored in a + * lookup URI. This can include, for example, address and website information. + * + * @param lookupUri The lookup URI. + */ + private void addContactFromLookupUri(Uri lookupUri) { + Contact contactToSave = ContactLoader.parseEncodedContactEntity(lookupUri); + if (contactToSave == null) { + return; + } + + // Note: This code mirrors code in Contacts/QuickContactsActivity. + final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); + + ArrayList<ContentValues> values = contactToSave.getContentValues(); + // Only pre-fill the name field if the provided display name is an nickname + // or better (e.g. structured name, nickname) + if (contactToSave.getDisplayNameSource() + >= ContactsContract.DisplayNameSources.NICKNAME) { + intent.putExtra(ContactsContract.Intents.Insert.NAME, + contactToSave.getDisplayName()); + } else if (contactToSave.getDisplayNameSource() + == ContactsContract.DisplayNameSources.ORGANIZATION) { + // This is probably an organization. Instead of copying the organization + // name into a name entry, copy it into the organization entry. This + // way we will still consider the contact an organization. + final ContentValues organization = new ContentValues(); + organization.put(ContactsContract.CommonDataKinds.Organization.COMPANY, + contactToSave.getDisplayName()); + organization.put(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + values.add(organization); + } + + // Last time used and times used are aggregated values from the usage stat + // table. They need to be removed from data values so the SQL table can insert + // properly + for (ContentValues value : values) { + value.remove(ContactsContract.Data.LAST_TIME_USED); + value.remove(ContactsContract.Data.TIMES_USED); + } + intent.putExtra(ContactsContract.Intents.Insert.DATA, values); + + DialerUtils.startActivityWithErrorToast(mContext, intent, + R.string.add_contact_not_available); + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + switch (scrollState) { + case OnScrollListener.SCROLL_STATE_IDLE: + mAdapterHelper.setBusy(false); + dataSetChanged(); + break; + case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: + mAdapterHelper.setBusy(false); + break; + case OnScrollListener.SCROLL_STATE_FLING: + // Do not update views when scroll state is SCROLL_STATE_FLING + mAdapterHelper.setBusy(true); + break; + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + // no-op + } } diff --git a/src/com/android/dialer/calllog/CallLogAdapterHelper.java b/src/com/android/dialer/calllog/CallLogAdapterHelper.java index a16935cfe..90b458442 100644 --- a/src/com/android/dialer/calllog/CallLogAdapterHelper.java +++ b/src/com/android/dialer/calllog/CallLogAdapterHelper.java @@ -137,6 +137,9 @@ public class CallLogAdapterHelper implements ViewTreeObserver.OnPreDrawListener // Check if thread is finished, and if so return immediately. if (mDone) return; + // only update contact info when scroll state is not fling. + if (mBusy) continue; + // Obtain next request, if any is available. // Keep synchronized section small. ContactInfoRequest req = null; @@ -201,6 +204,8 @@ public class CallLogAdapterHelper implements ViewTreeObserver.OnPreDrawListener /** Can be set to true by tests to disable processing of requests. */ private volatile boolean mRequestProcessingDisabled = false; + private boolean mBusy; + /** * List of requests to update contact details. * <p> @@ -228,6 +233,14 @@ public class CallLogAdapterHelper implements ViewTreeObserver.OnPreDrawListener } }; + public void setBusy(boolean isBusy) { + mBusy = isBusy; + } + + public boolean isBusy(){ + return mBusy; + } + /** * Enqueues a request to look up the contact details for the given phone number. * <p> diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java index de1220dd0..4dc168556 100644 --- a/src/com/android/dialer/calllog/CallLogFragment.java +++ b/src/com/android/dialer/calllog/CallLogFragment.java @@ -312,6 +312,7 @@ public class CallLogFragment extends AnalyticsListFragment public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); getListView().setEmptyView(view.findViewById(R.id.empty_list_view)); + getListView().setOnScrollListener(mAdapter); getListView().setItemsCanFocus(true); maybeAddFooterView(); @@ -535,7 +536,7 @@ public class CallLogFragment extends AnalyticsListFragment if (mFooterView == null) { mFooterView = getActivity().getLayoutInflater().inflate( - R.layout.recents_list_footer, (ViewGroup) getView(), false); + R.layout.recents_list_footer, getListView(), false); mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java index f4ea07e84..4e30cfdef 100644 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.provider.CallLog.Calls; import android.text.TextUtils; +import android.util.Log; import com.android.contacts.common.CallUtil; import com.android.dialer.PhoneCallDetails; @@ -30,6 +31,8 @@ import com.android.dialer.R; * Helper class to fill in the views of a call log entry. */ /* package */class CallLogListItemHelper { + private static final String TAG = "CallLogListItemHelper"; + /** Helper for populating the details of a phone call. */ private final PhoneCallDetailsHelper mPhoneCallDetailsHelper; /** Helper for handling phone numbers. */ @@ -78,17 +81,25 @@ import com.android.dialer.R; * @param views The views associated with the current call log entry. */ public void setActionContentDescriptions(CallLogListItemViews views) { + if (views.nameOrNumber == null) { + Log.e(TAG, "setActionContentDescriptions; name or number is null."); + } + + // Calling expandTemplate with a null parameter will cause a NullPointerException. + // Although we don't expect a null name or number, it is best to protect against it. + CharSequence nameOrNumber = views.nameOrNumber == null ? "" : views.nameOrNumber; + views.callBackButtonView.setContentDescription( - mResources.getString(R.string.description_call_back_action, views.nameOrNumber)); + mResources.getString(R.string.description_call_back_action, nameOrNumber)); views.videoCallButtonView.setContentDescription( - mResources.getString(R.string.description_video_call_action, views.nameOrNumber)); + mResources.getString(R.string.description_video_call_action, nameOrNumber)); views.voicemailButtonView.setContentDescription( - mResources.getString(R.string.description_voicemail_action, views.nameOrNumber)); + mResources.getString(R.string.description_voicemail_action, nameOrNumber)); views.detailsButtonView.setContentDescription( - mResources.getString(R.string.description_details_action, views.nameOrNumber)); + mResources.getString(R.string.description_details_action, nameOrNumber)); } /** diff --git a/src/com/android/dialer/calllog/ClearCallLogDialog.java b/src/com/android/dialer/calllog/ClearCallLogDialog.java index d6a1a10f9..ec28aec62 100644 --- a/src/com/android/dialer/calllog/ClearCallLogDialog.java +++ b/src/com/android/dialer/calllog/ClearCallLogDialog.java @@ -16,6 +16,7 @@ package com.android.dialer.calllog; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -57,6 +58,7 @@ public class ClearCallLogDialog extends DialogFragment { final ProgressDialog progressDialog = ProgressDialog.show(getActivity(), getString(R.string.clearCallLogProgress_title), "", true, false); + progressDialog.setOwnerActivity(getActivity()); final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { @@ -69,7 +71,15 @@ public class ClearCallLogDialog extends DialogFragment { } @Override protected void onPostExecute(Void result) { - progressDialog.dismiss(); + final Activity activity = progressDialog.getOwnerActivity(); + + if (activity == null || activity.isDestroyed() || activity.isFinishing()) { + return; + } + + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } } }; // TODO: Once we have the API, we should configure this ProgressDialog diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java index 11c1b178e..3493ce1d6 100644 --- a/src/com/android/dialer/calllog/PhoneAccountUtils.java +++ b/src/com/android/dialer/calllog/PhoneAccountUtils.java @@ -63,6 +63,14 @@ public class PhoneAccountUtils { } /** + * Extract account color from PhoneAccount object. + */ + public static int getAccountColor(Context context, PhoneAccountHandle accountHandle) { + PhoneAccount account = getAccountOrNull(context, accountHandle); + return account == null ? PhoneAccount.NO_HIGHLIGHT_COLOR : account.getHighlightColor(); + } + + /** * Retrieve the account metadata, but if the account does not exist or the device has only a * single registered and enabled account, return null. */ diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java index 8c93309d8..d2e8ed0c7 100755 --- a/src/com/android/dialer/dialpad/DialpadFragment.java +++ b/src/com/android/dialer/dialpad/DialpadFragment.java @@ -94,6 +94,7 @@ import com.android.phone.common.dialpad.DialpadView; import com.google.common.annotations.VisibleForTesting; import java.util.HashSet; +import java.util.Locale; import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY; @@ -395,6 +396,13 @@ public class DialpadFragment extends AnalyticsFragment mDelete.setOnLongClickListener(this); } + // Populate the overflow menu in onCreate instead of onResume to avoid PopupMenu's memory leak. + mOverflowMenuButton = mDialpadView.getOverflowMenuButton(); + mOverflowPopupMenu = buildOptionsMenu(mOverflowMenuButton); + mOverflowMenuButton.setOnTouchListener(mOverflowPopupMenu.getDragToOpenListener()); + mOverflowMenuButton.setOnClickListener(this); + mOverflowMenuButton.setVisibility(isDigitsEmpty() ? View.INVISIBLE : View.VISIBLE); + mSpacer = fragmentView.findViewById(R.id.spacer); mSpacer.setOnTouchListener(new View.OnTouchListener() { @Override @@ -611,6 +619,28 @@ public class DialpadFragment extends AnalyticsFragment } @Override + public void onStart() { + super.onStart(); + // if the mToneGenerator creation fails, just continue without it. It is + // a local audio signal, and is not as important as the dtmf tone itself. + final long start = System.currentTimeMillis(); + synchronized (mToneGeneratorLock) { + if (mToneGenerator == null) { + try { + mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME); + } catch (RuntimeException e) { + Log.w(TAG, "Exception caught while creating local tone generator: " + e); + mToneGenerator = null; + } + } + } + final long total = System.currentTimeMillis() - start; + if (total > 50) { + Log.i(TAG, "Time for ToneGenerator creation: " + total); + } + }; + + @Override public void onResume() { super.onResume(); @@ -638,20 +668,6 @@ public class DialpadFragment extends AnalyticsFragment stopWatch.lap("hptc"); - // if the mToneGenerator creation fails, just continue without it. It is - // a local audio signal, and is not as important as the dtmf tone itself. - synchronized (mToneGeneratorLock) { - if (mToneGenerator == null) { - try { - mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME); - } catch (RuntimeException e) { - Log.w(TAG, "Exception caught while creating local tone generator: " + e); - mToneGenerator = null; - } - } - } - stopWatch.lap("tg"); - mPressedDialpadKeys.clear(); configureScreenFromIntent(getActivity()); @@ -700,15 +716,6 @@ public class DialpadFragment extends AnalyticsFragment mSmsPackageComponentName = DialerUtils.getSmsComponent(activity); - // Populate the overflow menu in onResume instead of onCreate, so that if the SMS activity - // is disabled while Dialer is paused, the "Send a text message" option can be correctly - // removed when resumed. - mOverflowMenuButton = mDialpadView.getOverflowMenuButton(); - mOverflowPopupMenu = buildOptionsMenu(mOverflowMenuButton); - mOverflowMenuButton.setOnTouchListener(mOverflowPopupMenu.getDragToOpenListener()); - mOverflowMenuButton.setOnClickListener(this); - mOverflowMenuButton.setVisibility(isDigitsEmpty() ? View.INVISIBLE : View.VISIBLE); - if (getTelephonyManager().isMultiSimEnabled() && MoreContactUtils.shouldShowOperator(mContext)) { if (SubscriptionManager.isVoicePromptEnabled()) { @@ -735,12 +742,6 @@ public class DialpadFragment extends AnalyticsFragment stopTone(); mPressedDialpadKeys.clear(); - synchronized (mToneGeneratorLock) { - if (mToneGenerator != null) { - mToneGenerator.release(); - mToneGenerator = null; - } - } // TODO: I wonder if we should not check if the AsyncTask that // lookup the last dialed number has completed. mLastNumberDialed = EMPTY_NUMBER; // Since we are going to query again, free stale number. @@ -752,6 +753,13 @@ public class DialpadFragment extends AnalyticsFragment public void onStop() { super.onStop(); + synchronized (mToneGeneratorLock) { + if (mToneGenerator != null) { + mToneGenerator.release(); + mToneGenerator = null; + } + } + if (mClearDigitsOnStop) { mClearDigitsOnStop = false; clearDialpad(); @@ -913,6 +921,7 @@ public class DialpadFragment extends AnalyticsFragment * @param invoker the View that invoked the options menu, to act as an anchor location. */ private PopupMenu buildOptionsMenu(View invoker) { + invoker.setLayoutDirection(TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())); final PopupMenu popupMenu = new PopupMenu(getActivity(), invoker) { @Override public void show() { @@ -1335,7 +1344,7 @@ public class DialpadFragment extends AnalyticsFragment // must be dial conference add extra intent.putExtra(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, true); } - intent.putExtra(ADD_PARTICIPANT_KEY, mAddParticipant); + intent.putExtra(ADD_PARTICIPANT_KEY, (mAddParticipant && isPhoneInUse())); DialerUtils.startActivityWithErrorToast(getActivity(), intent); hideAndClearDialpad(false); } @@ -2047,12 +2056,14 @@ public class DialpadFragment extends AnalyticsFragment PhoneStateListener phoneStateListener = new PhoneStateListener(subId[0]) { @Override public void onCallStateChanged(int state, String incomingNumber) { - if ((state == TelephonyManager.CALL_STATE_IDLE) + if ((getActivity() != null) && + (getTelephonyManager().getCallState() == TelephonyManager.CALL_STATE_IDLE) && isDialpadChooserVisible()) { showDialpadChooser(false); } - if (state == TelephonyManager.CALL_STATE_IDLE - && getActivity() != null) { + if ((getActivity() != null) + && (getTelephonyManager().getCallState() + == TelephonyManager.CALL_STATE_IDLE)) { ((HostInterface) getActivity()).setConferenceDialButtonVisibility(true); } } diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java index 024e3d914..734c0b9ed 100644 --- a/src/com/android/dialer/list/ListsFragment.java +++ b/src/com/android/dialer/list/ListsFragment.java @@ -90,6 +90,7 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand private ShortcutCardsAdapter mMergedAdapter; private CallLogAdapter mCallLogAdapter; private CallLogQueryHandler mCallLogQueryHandler; + private OverlappingPaneLayout mOverlappingPaneLayout; private boolean mIsPanelOpen = true; @@ -303,6 +304,7 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand mRemoveViewContent = parentView.findViewById(R.id.remove_view_content); setupPaneLayout((OverlappingPaneLayout) parentView); + mOverlappingPaneLayout = (OverlappingPaneLayout) parentView; return parentView; } @@ -323,6 +325,11 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand mCallLogAdapter.changeCursor(cursor); mMergedAdapter.notifyDataSetChanged(); + + // Refresh the overlapping pane to ensure that any changes in the shortcut card height + // are appropriately reflected in the overlap position. + mOverlappingPaneLayout.refresh(); + // Return true; took ownership of cursor return true; } diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java index f58d72faa..62e7dce08 100644 --- a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java +++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java @@ -449,7 +449,7 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements * @param itemIndex Position of the contact in {@link #mContactEntries}. * @return True if the given index is valid for {@link #mContactEntries}. */ - private boolean isIndexInBound(int itemIndex) { + public boolean isIndexInBound(int itemIndex) { return itemIndex >= 0 && itemIndex < mContactEntries.size(); } @@ -459,7 +459,8 @@ public class PhoneFavoritesTileAdapter extends BaseAdapter implements * @param itemIndex Position of the contact in {@link #mContactEntries}. */ private void markDropArea(int itemIndex) { - if (isIndexInBound(mDragEnteredEntryIndex) && isIndexInBound(itemIndex)) { + if (mDraggedEntry != null && isIndexInBound(mDragEnteredEntryIndex) && + isIndexInBound(itemIndex)) { mDataSetChangedListener.cacheOffsetsForDatasetChange(); // Remove the old placeholder item and place the new placeholder item. final int oldIndex = mDragEnteredEntryIndex; diff --git a/src/com/android/dialer/list/SearchFragment.java b/src/com/android/dialer/list/SearchFragment.java index 4603d0359..70141a73a 100644 --- a/src/com/android/dialer/list/SearchFragment.java +++ b/src/com/android/dialer/list/SearchFragment.java @@ -142,22 +142,6 @@ public class SearchFragment extends PhoneNumberPickerFragment { } /** - * Adds a plus sign to the query string if original typed number was an international number. - */ - private String addPlusSignPrefixToNumber(String actualNumber) { - final StringBuilder number = new StringBuilder(); - - // Check if actual number had + sign as the first character and query string does not, then - // return a number with a + sign prefixed to the query string. That will be the dial string. - if ((actualNumber != null) && actualNumber.startsWith("+") && (getQueryString() != null) - && !(getQueryString().startsWith("+"))) { - number.append('+'); - } - number.append(getQueryString()); - return number.toString(); - } - - /** * Return true if phone number is prohibited by a value - * (R.string.config_prohibited_phone_number_regexp) in the config files. False otherwise. */ @@ -197,8 +181,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { final int shortcutType = adapter.getShortcutTypeFromPosition(position); final OnPhoneNumberPickerActionListener listener; - String dialingNumber = addPlusSignPrefixToNumber(mAddToContactNumber); - boolean ret = checkForProhibitedPhoneNumber(dialingNumber); + boolean ret = checkForProhibitedPhoneNumber(mAddToContactNumber); switch (shortcutType) { case DialerPhoneNumberListAdapter.SHORTCUT_INVALID: @@ -207,7 +190,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { case DialerPhoneNumberListAdapter.SHORTCUT_DIRECT_CALL: listener = getOnPhoneNumberPickerListener(); if (listener != null && !ret) { - listener.onCallNumberDirectly(dialingNumber); + listener.onCallNumberDirectly(getQueryString()); } break; case DialerPhoneNumberListAdapter.SHORTCUT_ADD_NUMBER_TO_CONTACTS: @@ -220,7 +203,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { case DialerPhoneNumberListAdapter.SHORTCUT_MAKE_VIDEO_CALL: listener = getOnPhoneNumberPickerListener(); if (listener != null && !ret) { - listener.onCallNumberDirectly(dialingNumber, true /* isVideoCall */); + listener.onCallNumberDirectly(mAddToContactNumber, true /* isVideoCall */); } break; } diff --git a/src/com/android/dialer/list/ShortcutCardsAdapter.java b/src/com/android/dialer/list/ShortcutCardsAdapter.java index 4fe638ea1..d642418bf 100644 --- a/src/com/android/dialer/list/ShortcutCardsAdapter.java +++ b/src/com/android/dialer/list/ShortcutCardsAdapter.java @@ -202,6 +202,7 @@ public class ShortcutCardsAdapter extends BaseAdapter { wrapper.removeAllViews(); wrapper.prepareChildView(view); wrapper.addView(view); + wrapper.setVisibility(View.VISIBLE); return wrapper; } diff --git a/src/com/android/dialer/list/SpeedDialFragment.java b/src/com/android/dialer/list/SpeedDialFragment.java index c02c3d7f9..03613d017 100644 --- a/src/com/android/dialer/list/SpeedDialFragment.java +++ b/src/com/android/dialer/list/SpeedDialFragment.java @@ -240,14 +240,16 @@ public class SpeedDialFragment extends AnalyticsFragment implements OnItemClickL /* package */ void setEmptyViewVisibility(final boolean visible) { final int previousVisibility = mEmptyView.getVisibility(); - final int newVisibility = visible ? View.VISIBLE : View.GONE; + final int emptyViewVisibility = visible ? View.VISIBLE : View.GONE; + final int listViewVisibility = visible ? View.GONE : View.VISIBLE; - if (previousVisibility != newVisibility) { + if (previousVisibility != emptyViewVisibility) { final RelativeLayout.LayoutParams params = (LayoutParams) mContactTileFrame .getLayoutParams(); params.height = visible ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; mContactTileFrame.setLayoutParams(params); - mEmptyView.setVisibility(newVisibility); + mEmptyView.setVisibility(emptyViewVisibility); + mListView.setVisibility(listViewVisibility); } } @@ -315,6 +317,12 @@ public class SpeedDialFragment extends AnalyticsFragment implements OnItemClickL for (int i = 0; i < mListView.getChildCount(); i++) { final View child = mListView.getChildAt(i); final int position = firstVisiblePosition + i; + // Since we are getting the position from mListView and then querying + // mContactTileAdapter, its very possible that things are out of sync + // and we might index out of bounds. Let's make sure that this doesn't happen. + if (!mContactTileAdapter.isIndexInBound(position)) { + continue; + } final long itemId = mContactTileAdapter.getItemId(position); if (DEBUG) { Log.d(TAG, "Saving itemId: " + itemId + " for listview child " + i + " Top: " @@ -323,7 +331,6 @@ public class SpeedDialFragment extends AnalyticsFragment implements OnItemClickL mItemIdTopMap.put(itemId, child.getTop()); mItemIdLeftMap.put(itemId, child.getLeft()); } - mItemIdTopMap.put(KEY_REMOVED_ITEM_HEIGHT, removedItemHeight); } @@ -351,6 +358,13 @@ public class SpeedDialFragment extends AnalyticsFragment implements OnItemClickL final View child = mListView.getChildAt(i); int position = firstVisiblePosition + i; + // Since we are getting the position from mListView and then querying + // mContactTileAdapter, its very possible that things are out of sync + // and we might index out of bounds. Let's make sure that this doesn't happen. + if (!mContactTileAdapter.isIndexInBound(position)) { + continue; + } + final long itemId = mContactTileAdapter.getItemId(position); if (containsId(idsInPlace, itemId)) { diff --git a/src/com/android/dialer/util/DialerUtils.java b/src/com/android/dialer/util/DialerUtils.java index a747c74aa..48d969904 100644 --- a/src/com/android/dialer/util/DialerUtils.java +++ b/src/com/android/dialer/util/DialerUtils.java @@ -124,7 +124,7 @@ public class DialerUtils { (ImageView) emptyListView.findViewById(R.id.emptyListViewImage); emptyListViewImage.setImageDrawable(res.getDrawable(imageResId)); - emptyListViewImage.setContentDescription(res.getString(strResId)); + emptyListViewImage.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); TextView emptyListViewMessage = (TextView) emptyListView.findViewById(R.id.emptyListViewMessage); diff --git a/src/com/android/dialer/widget/OverlappingPaneLayout.java b/src/com/android/dialer/widget/OverlappingPaneLayout.java index 95a0e43de..167b849f2 100644 --- a/src/com/android/dialer/widget/OverlappingPaneLayout.java +++ b/src/com/android/dialer/widget/OverlappingPaneLayout.java @@ -230,7 +230,6 @@ public class OverlappingPaneLayout extends ViewGroup { setWillNotDraw(false); ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); - ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density); @@ -718,6 +717,22 @@ public class OverlappingPaneLayout extends ViewGroup { return wantTouchEvents; } + /** + * Refreshes the {@link OverlappingPaneLayout} be attempting to re-open or re-close the pane. + * This ensures that the overlapping pane is repositioned based on any changes to the view + * which is being overlapped. + * <p> + * The {@link #openPane()} and {@link #closePane()} methods do not perform any animation if the + * pane has already been positioned appropriately. + */ + public void refresh() { + if (isOpen()) { + openPane(); + } else { + closePane(); + } + } + private boolean closePane(View pane, int initialVelocity) { if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) { mPreservedOpenState = false; diff --git a/src/com/android/dialer/widget/ViewDragHelper.java b/src/com/android/dialer/widget/ViewDragHelper.java index a0e1d801b..fe3ab8230 100644 --- a/src/com/android/dialer/widget/ViewDragHelper.java +++ b/src/com/android/dialer/widget/ViewDragHelper.java @@ -21,6 +21,7 @@ import android.content.Context; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.VelocityTrackerCompat; import android.support.v4.view.ViewCompat; +import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -1176,7 +1177,12 @@ public class ViewDragHelper { case MotionEvent.ACTION_MOVE: { if (mDragState == STATE_DRAGGING) { - final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); + int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); + if (index < 0) { + Log.e(TAG, "Pointer index for id " + mActivePointerId + " not found." + + " Skipping MotionEvent"); + return; + } final float x = MotionEventCompat.getX(ev, index); final float y = MotionEventCompat.getY(ev, index); final int idx = (int) (x - mLastMotionX[mActivePointerId]); @@ -1548,6 +1554,12 @@ public class ViewDragHelper { * deltas that it consumed. */ public void processNestedScroll(View target, int dx, int dy, int[] consumed) { + if (mCapturedView == null) { + // This is safe because consumed array is null when called from + // onNestedScroll, and pre-initialized to {0, 0} when called from + // onNestedPreScroll. + return; + } final int targetX = mCapturedView.getLeft() + dx; final int targetY = mCapturedView.getTop() + dy; dragTo(targetX, targetY, dx, dy); |