summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/dialer/CallDetailActivity.java102
-rw-r--r--src/com/android/dialer/DialerApplication.java22
-rw-r--r--src/com/android/dialer/DialtactsActivity.java83
-rw-r--r--src/com/android/dialer/PhoneCallDetailsHelper.java1
-rw-r--r--src/com/android/dialer/ProximitySensorManager.java237
-rw-r--r--src/com/android/dialer/SpecialCharSequenceMgr.java29
-rw-r--r--src/com/android/dialer/calllog/CallLogAdapter.java1018
-rw-r--r--src/com/android/dialer/calllog/CallLogFragment.java318
-rw-r--r--src/com/android/dialer/calllog/CallLogGroupBuilder.java1
-rw-r--r--src/com/android/dialer/calllog/CallLogListItemViews.java258
-rw-r--r--src/com/android/dialer/calllog/CallLogQuery.java4
-rw-r--r--src/com/android/dialer/calllog/ContactInfo.java5
-rw-r--r--src/com/android/dialer/calllog/ContactInfoHelper.java147
-rw-r--r--src/com/android/dialer/calllog/GroupingListAdapter.java490
-rw-r--r--src/com/android/dialer/calllog/IntentProvider.java80
-rw-r--r--src/com/android/dialer/calllog/PhoneNumberDisplayHelper.java1
-rw-r--r--src/com/android/dialer/contactinfo/ContactInfoCache.java342
-rw-r--r--src/com/android/dialer/contactinfo/ContactInfoRequest.java65
-rw-r--r--src/com/android/dialer/contactinfo/NumberWithCountryIso.java53
-rw-r--r--src/com/android/dialer/database/DialerDatabaseHelper.java6
-rw-r--r--src/com/android/dialer/dialpad/DialpadFragment.java65
-rw-r--r--src/com/android/dialer/dialpad/SmartDialCursorLoader.java20
-rw-r--r--src/com/android/dialer/interactions/PhoneNumberInteraction.java4
-rw-r--r--src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java2
-rw-r--r--src/com/android/dialer/list/AllContactsFragment.java4
-rw-r--r--src/com/android/dialer/list/DialerPhoneNumberListAdapter.java22
-rw-r--r--src/com/android/dialer/list/ListsFragment.java211
-rw-r--r--src/com/android/dialer/list/PhoneFavoriteSquareTileView.java3
-rw-r--r--src/com/android/dialer/list/RegularSearchListAdapter.java9
-rw-r--r--src/com/android/dialer/list/SearchFragment.java65
-rw-r--r--src/com/android/dialer/list/ShortcutCardsAdapter.java393
-rw-r--r--src/com/android/dialer/list/SmartDialNumberListAdapter.java8
-rw-r--r--src/com/android/dialer/list/SmartDialSearchFragment.java7
-rw-r--r--src/com/android/dialer/list/SpeedDialFragment.java16
-rw-r--r--src/com/android/dialer/list/SwipeHelper.java470
-rw-r--r--src/com/android/dialer/settings/DialerSettingsActivity.java111
-rw-r--r--src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java31
-rw-r--r--src/com/android/dialer/settings/GeneralSettingsFragment.java159
-rw-r--r--src/com/android/dialer/settings/SoundSettingsFragment.java215
-rw-r--r--src/com/android/dialer/util/PrivilegedCallUtil.java145
-rw-r--r--src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java6
-rw-r--r--src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java52
-rw-r--r--src/com/android/dialer/widget/OverlappingPaneLayout.java1358
-rw-r--r--src/com/android/dialerbind/ObjectFactory.java14
44 files changed, 2331 insertions, 4321 deletions
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 6c576508c..5dae9d07d 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -27,6 +27,7 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.PowerManager;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
@@ -50,9 +51,9 @@ import android.widget.TextView;
import android.widget.Toast;
import com.android.contacts.common.ContactPhotoManager;
-import com.android.contacts.common.CallUtil;
import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.CallUtil;
import com.android.dialer.calllog.CallDetailHistoryAdapter;
import com.android.dialer.calllog.CallTypeHelper;
import com.android.dialer.calllog.ContactInfo;
@@ -62,6 +63,7 @@ import com.android.dialer.calllog.PhoneNumberDisplayHelper;
import com.android.dialer.calllog.PhoneNumberUtilsWrapper;
import com.android.dialer.util.AsyncTaskExecutor;
import com.android.dialer.util.AsyncTaskExecutors;
+import com.android.dialer.util.PrivilegedCallUtil;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.voicemail.VoicemailPlaybackFragment;
import com.android.dialer.voicemail.VoicemailStatusHelper;
@@ -82,11 +84,6 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware
private static final char LEFT_TO_RIGHT_EMBEDDING = '\u202A';
private static final char POP_DIRECTIONAL_FORMATTING = '\u202C';
- /** The time to wait before enabling the blank the screen due to the proximity sensor. */
- private static final long PROXIMITY_BLANK_DELAY_MILLIS = 100;
- /** The time to wait before disabling the blank the screen due to the proximity sensor. */
- private static final long PROXIMITY_UNBLANK_DELAY_MILLIS = 500;
-
/** The enumeration of {@link AsyncTask} objects used in this class. */
public enum Tasks {
MARK_VOICEMAIL_READ,
@@ -143,59 +140,7 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware
/** Whether we should show "remove from call log" in the options menu. */
private boolean mHasRemoveFromCallLogOption;
- private ProximitySensorManager mProximitySensorManager;
- private final ProximitySensorListener mProximitySensorListener = new ProximitySensorListener();
-
- /** Listener to changes in the proximity sensor state. */
- private class ProximitySensorListener implements ProximitySensorManager.Listener {
- /** Used to show a blank view and hide the action bar. */
- private final Runnable mBlankRunnable = new Runnable() {
- @Override
- public void run() {
- View blankView = findViewById(R.id.blank);
- blankView.setVisibility(View.VISIBLE);
- getActionBar().hide();
- }
- };
- /** Used to remove the blank view and show the action bar. */
- private final Runnable mUnblankRunnable = new Runnable() {
- @Override
- public void run() {
- View blankView = findViewById(R.id.blank);
- blankView.setVisibility(View.GONE);
- getActionBar().show();
- }
- };
-
- @Override
- public synchronized void onNear() {
- clearPendingRequests();
- postDelayed(mBlankRunnable, PROXIMITY_BLANK_DELAY_MILLIS);
- }
-
- @Override
- public synchronized void onFar() {
- clearPendingRequests();
- postDelayed(mUnblankRunnable, PROXIMITY_UNBLANK_DELAY_MILLIS);
- }
-
- /** Removed any delayed requests that may be pending. */
- public synchronized void clearPendingRequests() {
- View blankView = findViewById(R.id.blank);
- blankView.removeCallbacks(mBlankRunnable);
- blankView.removeCallbacks(mUnblankRunnable);
- }
-
- /** Post a {@link Runnable} with a delay on the main thread. */
- private synchronized void postDelayed(Runnable runnable, long delayMillis) {
- // Post these instead of executing immediately so that:
- // - They are guaranteed to be executed on the main thread.
- // - If the sensor values changes rapidly for some time, the UI will not be
- // updated immediately.
- View blankView = findViewById(R.id.blank);
- blankView.postDelayed(runnable, delayMillis);
- }
- }
+ private PowerManager.WakeLock mProximityWakeLock;
static final String[] CALL_LOG_PROJECTION = new String[] {
CallLog.Calls.DATE,
@@ -244,12 +189,20 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware
mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo);
mQuickContactBadge.setOverlay(null);
+ mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
mCallerName = (TextView) findViewById(R.id.caller_name);
mCallerNumber = (TextView) findViewById(R.id.caller_number);
mAccountLabel = (TextView) findViewById(R.id.phone_account_label);
mDefaultCountryIso = GeoUtil.getCurrentCountryIso(this);
mContactPhotoManager = ContactPhotoManager.getInstance(this);
- mProximitySensorManager = new ProximitySensorManager(this, mProximitySensorListener);
+ final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
+ mProximityWakeLock = powerManager.newWakeLock(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
+ } else {
+ mProximityWakeLock = null;
+ }
+
mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this));
getActionBar().setDisplayHomeAsUpEnabled(true);
@@ -364,8 +317,8 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware
getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
DialerUtils.startActivityWithErrorToast(this,
- CallUtil.getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_TEL, mNumber,
- null)), R.string.call_not_available);
+ PrivilegedCallUtil.getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_TEL,
+ mNumber, null)), R.string.call_not_available);
return true;
}
}
@@ -414,7 +367,8 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware
// We know that all calls are from the same number and the same contact, so pick the
// first.
PhoneCallDetails firstDetails = details[0];
- mNumber = firstDetails.number.toString();
+ mNumber = TextUtils.isEmpty(firstDetails.number) ?
+ null : firstDetails.number.toString();
final int numberPresentation = firstDetails.numberPresentation;
final Uri contactUri = firstDetails.contactUri;
final Uri photoUri = firstDetails.photoUri;
@@ -750,18 +704,34 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware
protected void onPause() {
// Immediately stop the proximity sensor.
disableProximitySensor(false);
- mProximitySensorListener.clearPendingRequests();
super.onPause();
}
@Override
public void enableProximitySensor() {
- mProximitySensorManager.enable();
+ if (mProximityWakeLock == null) {
+ return;
+ }
+ if (!mProximityWakeLock.isHeld()) {
+ Log.i(TAG, "Acquiring proximity wake lock");
+ mProximityWakeLock.acquire();
+ } else {
+ Log.i(TAG, "Proximity wake lock already acquired");
+ }
}
@Override
public void disableProximitySensor(boolean waitForFarState) {
- mProximitySensorManager.disable(waitForFarState);
+ if (mProximityWakeLock == null) {
+ return;
+ }
+ if (mProximityWakeLock.isHeld()) {
+ Log.i(TAG, "Releasing proximity wake lock");
+ int flags = (waitForFarState ? PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY : 0);
+ mProximityWakeLock.release(flags);
+ } else {
+ Log.i(TAG, "Proximity wake lock already released");
+ }
}
private void closeSystemDialogs() {
diff --git a/src/com/android/dialer/DialerApplication.java b/src/com/android/dialer/DialerApplication.java
index c64530829..7bc3bb4d3 100644
--- a/src/com/android/dialer/DialerApplication.java
+++ b/src/com/android/dialer/DialerApplication.java
@@ -17,6 +17,7 @@
package com.android.dialer;
import android.app.Application;
+import android.os.Trace;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.extensions.ExtensionsFactory;
@@ -24,26 +25,19 @@ import com.android.contacts.commonbind.analytics.AnalyticsUtil;
public class DialerApplication extends Application {
+ private static final String TAG = "DialerApplication";
private ContactPhotoManager mContactPhotoManager;
@Override
public void onCreate() {
+ Trace.beginSection(TAG + " onCreate");
super.onCreate();
+ Trace.beginSection(TAG + " ExtensionsFactory initialization");
ExtensionsFactory.init(getApplicationContext());
+ Trace.endSection();
+ Trace.beginSection(TAG + " Analytics initialization");
AnalyticsUtil.initialize(this);
- }
-
- @Override
- public Object getSystemService(String name) {
- if (ContactPhotoManager.CONTACT_PHOTO_SERVICE.equals(name)) {
- if (mContactPhotoManager == null) {
- mContactPhotoManager = ContactPhotoManager.createContactPhotoManager(this);
- registerComponentCallbacks(mContactPhotoManager);
- mContactPhotoManager.preloadPhotosInBackground();
- }
- return mContactPhotoManager;
- }
-
- return super.getSystemService(name);
+ Trace.endSection();
+ Trace.endSection();
}
}
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 95b72157e..787b6fae4 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Trace;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.speech.RecognizerIntent;
@@ -58,7 +59,6 @@ import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.Toast;
-import com.android.contacts.common.CallUtil;
import com.android.contacts.common.activity.TransactionSafeActivity;
import com.android.contacts.common.dialog.ClearFrequentsDialog;
import com.android.contacts.common.interactions.ImportExportDialogFragment;
@@ -82,6 +82,7 @@ import com.android.dialer.list.SearchFragment;
import com.android.dialer.list.SmartDialSearchFragment;
import com.android.dialer.list.SpeedDialFragment;
import com.android.dialer.settings.DialerSettingsActivity;
+import com.android.dialer.util.PrivilegedCallUtil;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.widget.ActionBarController;
import com.android.dialer.widget.SearchEditTextLayout;
@@ -90,6 +91,8 @@ import com.android.dialerbind.DatabaseHelperManager;
import com.android.phone.common.animation.AnimUtils;
import com.android.phone.common.animation.AnimationListenerAdapter;
+import junit.framework.Assert;
+
import java.util.ArrayList;
import java.util.List;
@@ -235,7 +238,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
*/
private String mVoiceSearchQuery;
- private class OptionsPopupMenu extends PopupMenu {
+ protected class OptionsPopupMenu extends PopupMenu {
public OptionsPopupMenu(Context context, View anchor) {
super(context, anchor, Gravity.END);
}
@@ -350,27 +353,30 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
@Override
protected void onCreate(Bundle savedInstanceState) {
+ Trace.beginSection(TAG + " onCreate");
super.onCreate(savedInstanceState);
mFirstLaunch = true;
final Resources resources = getResources();
mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large);
+ Trace.beginSection(TAG + " setContentView");
setContentView(R.layout.dialtacts_activity);
+ Trace.endSection();
getWindow().setBackgroundDrawable(null);
+ Trace.beginSection(TAG + " setup Views");
final ActionBar actionBar = getActionBar();
actionBar.setCustomView(R.layout.search_edittext);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setBackgroundDrawable(null);
- mActionBarController = new ActionBarController(this,
- (SearchEditTextLayout) actionBar.getCustomView());
-
SearchEditTextLayout searchEditTextLayout =
- (SearchEditTextLayout) actionBar.getCustomView();
+ (SearchEditTextLayout) actionBar.getCustomView().findViewById(R.id.search_view_container);
searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener);
+ mActionBarController = new ActionBarController(this, searchEditTextLayout);
+
mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button);
@@ -401,12 +407,11 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
mOverflowMenu = buildOptionsMenu(searchEditTextLayout);
optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener());
- // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState
- // is null. Otherwise the fragment manager takes care of recreating these fragments.
+ // Add the favorites fragment but only if savedInstanceState is null. Otherwise the
+ // fragment manager is responsible for recreating it.
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
- .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT)
.commit();
} else {
mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
@@ -453,8 +458,13 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
setupActivityOverlay();
+ Trace.endSection();
+
+ Trace.beginSection(TAG + " initialize smart dialing");
mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
SmartDialPrefix.initializeNanpSettings(this);
+ Trace.endSection();
+ Trace.endSection();
}
private void setupActivityOverlay() {
@@ -472,6 +482,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
@Override
protected void onResume() {
+ Trace.beginSection(TAG + " onResume");
super.onResume();
mStateSaved = false;
if (mFirstLaunch) {
@@ -506,6 +517,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
prepareVoiceSearchButton();
mDialerDatabaseHelper.startSmartDialUpdateThread();
updateFloatingActionButtonControllerAlignment(false /* animate */);
+ Trace.endSection();
}
@Override
@@ -542,7 +554,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof DialpadFragment) {
mDialpadFragment = (DialpadFragment) fragment;
- if (!mShowDialpadOnResume) {
+ if (!mIsDialpadShown && !mShowDialpadOnResume) {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.hide(mDialpadFragment);
transaction.commit();
@@ -655,12 +667,19 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
return;
}
mIsDialpadShown = true;
- mDialpadFragment.setAnimate(animate);
+
mListsFragment.setUserVisibleHint(false);
- AnalyticsUtil.sendScreenView(mDialpadFragment);
final FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.show(mDialpadFragment);
+ if (mDialpadFragment == null) {
+ mDialpadFragment = new DialpadFragment();
+ ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
+ } else {
+ ft.show(mDialpadFragment);
+ }
+
+ mDialpadFragment.setAnimate(animate);
+ AnalyticsUtil.sendScreenView(mDialpadFragment);
ft.commit();
if (animate) {
@@ -679,6 +698,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
* Callback from child DialpadFragment when the dialpad is shown.
*/
public void onDialpadShown() {
+ Assert.assertNotNull(mDialpadFragment);
if (mDialpadFragment.getAnimate()) {
mDialpadFragment.getView().startAnimation(mSlideIn);
} else {
@@ -730,7 +750,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
* Finishes hiding the dialpad fragment after any animations are completed.
*/
private void commitDialpadFragmentHide() {
- if (!mStateSaved && !mDialpadFragment.isHidden()) {
+ if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) {
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(mDialpadFragment);
ft.commit();
@@ -788,7 +808,7 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
}
}
- private OptionsPopupMenu buildOptionsMenu(View invoker) {
+ protected OptionsPopupMenu buildOptionsMenu(View invoker) {
final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
popupMenu.inflate(R.menu.dialtacts_options);
final Menu menu = popupMenu.getMenu();
@@ -838,14 +858,12 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
return;
}
- if (mDialpadFragment != null) {
- final boolean phoneIsInUse = phoneIsInUse();
- if (phoneIsInUse || (intent.getData() != null && isDialIntent(intent))) {
- mDialpadFragment.setStartedFromNewIntent(true);
- if (phoneIsInUse && !mDialpadFragment.isVisible()) {
- mInCallDialpadUp = true;
- }
- showDialpadFragment(false);
+ final boolean phoneIsInUse = phoneIsInUse();
+ if (phoneIsInUse || (intent.getData() != null && isDialIntent(intent))) {
+ showDialpadFragment(false);
+ mDialpadFragment.setStartedFromNewIntent(true);
+ if (phoneIsInUse && !mDialpadFragment.isVisible()) {
+ mInCallDialpadUp = true;
}
}
}
@@ -947,7 +965,11 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
}
mSearchView.setText(null);
- mDialpadFragment.clearDialpad();
+
+ if (mDialpadFragment != null) {
+ mDialpadFragment.clearDialpad();
+ }
+
setNotInSearchUi();
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
@@ -960,7 +982,8 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
transaction.commit();
mListsFragment.getView().animate().alpha(1).withLayer();
- if (!mDialpadFragment.isVisible()) {
+
+ if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
// If the dialpad fragment wasn't previously visible, then send a screen view because
// we are exiting regular search. Otherwise, the screen view will be sent by
// {@link #hideDialpadFragment}.
@@ -1090,9 +1113,6 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
*/
@Override
public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
- if (mListsFragment.isPaneOpen()) {
- mActionBarController.setAlpha(ListsFragment.REMOVE_VIEW_SHOWN_ALPHA);
- }
mListsFragment.showRemoveView(true);
}
@@ -1105,9 +1125,6 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
*/
@Override
public void onDragFinished(int x, int y) {
- if (mListsFragment.isPaneOpen()) {
- mActionBarController.setAlpha(ListsFragment.REMOVE_VIEW_HIDDEN_ALPHA);
- }
mListsFragment.showRemoveView(false);
}
@@ -1141,8 +1158,8 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
@Override
public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall) {
Intent intent = isVideoCall ?
- CallUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) :
- CallUtil.getCallIntent(phoneNumber, getCallOrigin());
+ PrivilegedCallUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) :
+ PrivilegedCallUtil.getCallIntent(phoneNumber, getCallOrigin());
DialerUtils.startActivityWithErrorToast(this, intent);
mClearSearchOnPause = true;
}
diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java
index b4e817174..7855a1d13 100644
--- a/src/com/android/dialer/PhoneCallDetailsHelper.java
+++ b/src/com/android/dialer/PhoneCallDetailsHelper.java
@@ -28,7 +28,6 @@ import android.text.format.DateUtils;
import android.view.View;
import android.widget.TextView;
-import com.android.contacts.common.CallUtil;
import com.android.contacts.common.testing.NeededForTesting;
import com.android.contacts.common.util.PhoneNumberHelper;
import com.android.dialer.calllog.ContactInfo;
diff --git a/src/com/android/dialer/ProximitySensorManager.java b/src/com/android/dialer/ProximitySensorManager.java
deleted file mode 100644
index 42d740fc1..000000000
--- a/src/com/android/dialer/ProximitySensorManager.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Manages the proximity sensor and notifies a listener when enabled.
- */
-public class ProximitySensorManager {
- /**
- * Listener of the state of the proximity sensor.
- * <p>
- * This interface abstracts two possible states for the proximity sensor, near and far.
- * <p>
- * The actual meaning of these states depends on the actual sensor.
- */
- public interface Listener {
- /** Called when the proximity sensor transitions from the far to the near state. */
- public void onNear();
- /** Called when the proximity sensor transitions from the near to the far state. */
- public void onFar();
- }
-
- public static enum State {
- NEAR, FAR
- }
-
- private final ProximitySensorEventListener mProximitySensorListener;
-
- /**
- * The current state of the manager, i.e., whether it is currently tracking the state of the
- * sensor.
- */
- private boolean mManagerEnabled;
-
- /**
- * The listener to the state of the sensor.
- * <p>
- * Contains most of the logic concerning tracking of the sensor.
- * <p>
- * After creating an instance of this object, one should call {@link #register()} and
- * {@link #unregister()} to enable and disable the notifications.
- * <p>
- * Instead of calling unregister, one can call {@link #unregisterWhenFar()} to unregister the
- * listener the next time the sensor reaches the {@link State#FAR} state if currently in the
- * {@link State#NEAR} state.
- */
- private static class ProximitySensorEventListener implements SensorEventListener {
- private static final float FAR_THRESHOLD = 5.0f;
-
- private final SensorManager mSensorManager;
- private final Sensor mProximitySensor;
- private final float mMaxValue;
- private final Listener mListener;
-
- /**
- * The last state of the sensor.
- * <p>
- * Before registering and after unregistering we are always in the {@link State#FAR} state.
- */
- @GuardedBy("this") private State mLastState;
- /**
- * If this flag is set to true, we are waiting to reach the {@link State#FAR} state and
- * should notify the listener and unregister when that happens.
- */
- @GuardedBy("this") private boolean mWaitingForFarState;
-
- public ProximitySensorEventListener(SensorManager sensorManager, Sensor proximitySensor,
- Listener listener) {
- mSensorManager = sensorManager;
- mProximitySensor = proximitySensor;
- mMaxValue = proximitySensor.getMaximumRange();
- mListener = listener;
- // Initialize at far state.
- mLastState = State.FAR;
- mWaitingForFarState = false;
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- // Make sure we have a valid value.
- if (event.values == null) return;
- if (event.values.length == 0) return;
- float value = event.values[0];
- // Convert the sensor into a NEAR/FAR state.
- State state = getStateFromValue(value);
- synchronized (this) {
- // No change in state, do nothing.
- if (state == mLastState) return;
- // Keep track of the current state.
- mLastState = state;
- // If we are waiting to reach the far state and we are now in it, unregister.
- if (mWaitingForFarState && mLastState == State.FAR) {
- unregisterWithoutNotification();
- }
- }
- // Notify the listener of the state change.
- switch (state) {
- case NEAR:
- mListener.onNear();
- break;
-
- case FAR:
- mListener.onFar();
- break;
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Nothing to do here.
- }
-
- /** Returns the state of the sensor given its current value. */
- private State getStateFromValue(float value) {
- // Determine if the current value corresponds to the NEAR or FAR state.
- // Take case of the case where the proximity sensor is binary: if the current value is
- // equal to the maximum, we are always in the FAR state.
- return (value > FAR_THRESHOLD || value == mMaxValue) ? State.FAR : State.NEAR;
- }
-
- /**
- * Unregister the next time the sensor reaches the {@link State#FAR} state.
- */
- public synchronized void unregisterWhenFar() {
- if (mLastState == State.FAR) {
- // We are already in the far state, just unregister now.
- unregisterWithoutNotification();
- } else {
- mWaitingForFarState = true;
- }
- }
-
- /** Register the listener and call the listener as necessary. */
- public synchronized void register() {
- // It is okay to register multiple times.
- mSensorManager.registerListener(this, mProximitySensor, SensorManager.SENSOR_DELAY_UI);
- // We should no longer be waiting for the far state if we are registering again.
- mWaitingForFarState = false;
- }
-
- public void unregister() {
- State lastState;
- synchronized (this) {
- unregisterWithoutNotification();
- lastState = mLastState;
- // Always go back to the FAR state. That way, when we register again we will get a
- // transition when the sensor gets into the NEAR state.
- mLastState = State.FAR;
- }
- // Notify the listener if we changed the state to FAR while unregistering.
- if (lastState != State.FAR) {
- mListener.onFar();
- }
- }
-
- @GuardedBy("this")
- private void unregisterWithoutNotification() {
- mSensorManager.unregisterListener(this);
- mWaitingForFarState = false;
- }
- }
-
- public ProximitySensorManager(Context context, Listener listener) {
- SensorManager sensorManager =
- (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- Sensor proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
- if (proximitySensor == null) {
- // If there is no sensor, we should not do anything.
- mProximitySensorListener = null;
- } else {
- mProximitySensorListener =
- new ProximitySensorEventListener(sensorManager, proximitySensor, listener);
- }
- }
-
- /**
- * Enables the proximity manager.
- * <p>
- * The listener will start getting notifications of events.
- * <p>
- * This method is idempotent.
- */
- public void enable() {
- if (mProximitySensorListener != null && !mManagerEnabled) {
- mProximitySensorListener.register();
- mManagerEnabled = true;
- }
- }
-
- /**
- * Disables the proximity manager.
- * <p>
- * The listener will stop receiving notifications of events, possibly after receiving a last
- * {@link Listener#onFar()} callback.
- * <p>
- * If {@code waitForFarState} is true, if the sensor is not currently in the {@link State#FAR}
- * state, the listener will receive a {@link Listener#onFar()} callback the next time the sensor
- * actually reaches the {@link State#FAR} state.
- * <p>
- * If {@code waitForFarState} is false, the listener will receive a {@link Listener#onFar()}
- * callback immediately if the sensor is currently not in the {@link State#FAR} state.
- * <p>
- * This method is idempotent.
- */
- public void disable(boolean waitForFarState) {
- if (mProximitySensorListener != null && mManagerEnabled) {
- if (waitForFarState) {
- mProximitySensorListener.unregisterWhenFar();
- } else {
- mProximitySensorListener.unregister();
- }
- mManagerEnabled = false;
- }
- }
-}
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java
index f466c417c..1e686125f 100644
--- a/src/com/android/dialer/SpecialCharSequenceMgr.java
+++ b/src/com/android/dialer/SpecialCharSequenceMgr.java
@@ -18,6 +18,7 @@ package com.android.dialer;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.DialogFragment;
import android.app.KeyguardManager;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
@@ -33,6 +34,7 @@ import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManager;
import android.widget.EditText;
@@ -63,6 +65,8 @@ import java.util.List;
public class SpecialCharSequenceMgr {
private static final String TAG = "SpecialCharSequenceMgr";
+ private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
+
private static final String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE";
private static final String MMI_IMEI_DISPLAY = "*#06#";
private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#";
@@ -228,9 +232,12 @@ public class SpecialCharSequenceMgr {
public void onDialogDismissed() {}
};
- SelectPhoneAccountDialogFragment.showAccountDialog(
- ((Activity) context).getFragmentManager(), subscriptionAccountHandles,
- listener);
+ // NOTE: If you want to support rotation of this dialog need
+ // to refactor the listener and set it in DialpadFragment.onCreate()
+ DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
+ subscriptionAccountHandles, listener);
+ dialogFragment.show(((Activity) context).getFragmentManager(),
+ TAG_SELECT_ACCT_FRAGMENT);
} else {
return false;
}
@@ -289,9 +296,12 @@ public class SpecialCharSequenceMgr {
public void onDialogDismissed() {}
};
- SelectPhoneAccountDialogFragment.showAccountDialog(
- ((Activity) context).getFragmentManager(), subscriptionAccountHandles,
- listener);
+ // NOTE: If you want to support rotation of this dialog need
+ // to refactor the listener and set it in DialpadFragment.onCreate()
+ DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
+ subscriptionAccountHandles, listener);
+ dialogFragment.show(((Activity) context).getFragmentManager(),
+ TAG_SELECT_ACCT_FRAGMENT);
}
return true;
}
@@ -310,13 +320,16 @@ public class SpecialCharSequenceMgr {
List<String> deviceIds = new ArrayList<String>();
for (int slot = 0; slot < telephonyManager.getPhoneCount(); slot++) {
- deviceIds.add(telephonyManager.getDeviceId(slot));
+ String deviceId = telephonyManager.getDeviceId(slot);
+ if (!TextUtils.isEmpty(deviceId)) {
+ deviceIds.add(deviceId);
+ }
}
AlertDialog alert = new AlertDialog.Builder(context)
.setTitle(labelResId)
.setItems(deviceIds.toArray(new String[deviceIds.size()]), null)
- .setPositiveButton(R.string.ok, null)
+ .setPositiveButton(android.R.string.ok, null)
.setCancelable(false)
.show();
return true;
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 225d34948..3e8efa0b7 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -16,18 +16,12 @@
package com.android.dialer.calllog;
-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.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.PhoneLookup;
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
@@ -37,34 +31,22 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
-import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
-
-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.util.PhoneNumberHelper;
-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;
import com.android.dialer.PhoneCallDetailsHelper;
import com.android.dialer.R;
+import com.android.dialer.contactinfo.ContactInfoCache;
+import com.android.dialer.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
import com.android.dialer.util.DialerUtils;
-import com.android.dialer.util.ExpirableCache;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
/**
* Adapter class to fill in data for the Call Log.
@@ -73,13 +55,6 @@ public class CallLogAdapter extends GroupingListAdapter
implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
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. */
- public enum Tasks {
- REMOVE_CALL_LOG_ENTRIES,
- }
-
/** Interface used to inform a parent UI element that a list item has been expanded. */
public interface CallItemExpandedListener {
/**
@@ -108,68 +83,16 @@ public class CallLogAdapter extends GroupingListAdapter
public void onReportButtonClick(String number);
}
- /**
- * Stores a phone number of a call with the country code where it originally occurred.
- * <p>
- * Note the country does not necessarily specifies the country of the phone number itself, but
- * it is the country in which the user was in when the call was placed or received.
- */
- private static final class NumberWithCountryIso {
- public final String number;
- public final String countryIso;
-
- public NumberWithCountryIso(String number, String countryIso) {
- this.number = number;
- this.countryIso = countryIso;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null) return false;
- if (!(o instanceof NumberWithCountryIso)) return false;
- NumberWithCountryIso other = (NumberWithCountryIso) o;
- return TextUtils.equals(number, other.number)
- && TextUtils.equals(countryIso, other.countryIso);
- }
-
- @Override
- public int hashCode() {
- return (number == null ? 0 : number.hashCode())
- ^ (countryIso == null ? 0 : countryIso.hashCode());
- }
- }
-
- /** The time in millis to delay starting the thread processing requests. */
- private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
-
- /** The size of the cache of contact info. */
- private static final int CONTACT_INFO_CACHE_SIZE = 100;
-
/** Constant used to indicate no row is expanded. */
private static final long NONE_EXPANDED = -1;
protected final Context mContext;
private final ContactInfoHelper mContactInfoHelper;
private final CallFetcher mCallFetcher;
- private final Toast mReportedToast;
private final OnReportButtonClickListener mOnReportButtonClickListener;
private ViewTreeObserver mViewTreeObserver = null;
- /**
- * A cache of the contact details for the phone numbers in the call log.
- * <p>
- * The content of the cache is expired (but not purged) whenever the application comes to
- * the foreground.
- * <p>
- * The key is number with the country in which the call was placed or received.
- */
- private ExpirableCache<NumberWithCountryIso, ContactInfo> mContactInfoCache;
-
- /**
- * Tracks the call log row which was previously expanded. Used so that the closure of a
- * previously expanded call log entry can be animated on rebind.
- */
- private long mPreviouslyExpanded = NONE_EXPANDED;
+ protected ContactInfoCache mContactInfoCache;
/**
* Tracks the currently expanded call log row.
@@ -190,71 +113,11 @@ public class CallLogAdapter extends GroupingListAdapter
*/
private HashMap<Long,Integer> mDayGroups = new HashMap<Long, Integer>();
- /**
- * A request for contact details for the given number.
- */
- private static final class ContactInfoRequest {
- /** The number to look-up. */
- public final String number;
- /** The country in which a call to or from this number was placed or received. */
- public final String countryIso;
- /** The cached contact information stored in the call log. */
- public final ContactInfo callLogInfo;
-
- public ContactInfoRequest(String number, String countryIso, ContactInfo callLogInfo) {
- this.number = number;
- this.countryIso = countryIso;
- this.callLogInfo = callLogInfo;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (!(obj instanceof ContactInfoRequest)) return false;
-
- ContactInfoRequest other = (ContactInfoRequest) obj;
-
- if (!TextUtils.equals(number, other.number)) return false;
- if (!TextUtils.equals(countryIso, other.countryIso)) return false;
- if (!Objects.equal(callLogInfo, other.callLogInfo)) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((callLogInfo == null) ? 0 : callLogInfo.hashCode());
- result = prime * result + ((countryIso == null) ? 0 : countryIso.hashCode());
- result = prime * result + ((number == null) ? 0 : number.hashCode());
- return result;
- }
- }
-
- /**
- * List of requests to update contact details.
- * <p>
- * Each request is made of a phone number to look up, and the contact info currently stored in
- * the call log for this number.
- * <p>
- * The requests are added when displaying the contacts and are processed by a background
- * thread.
- */
- private final LinkedList<ContactInfoRequest> mRequests;
-
private boolean mLoading = true;
- private static final int REDRAW = 1;
- private static final int START_THREAD = 2;
-
- private QueryThread mCallerIdThread;
/** Instance of helper class for managing views. */
private final CallLogListItemHelper mCallLogViewsHelper;
- /** Helper to set up contact photos. */
- private final ContactPhotoManager mContactPhotoManager;
/** Helper to parse and process phone numbers. */
private PhoneNumberDisplayHelper mPhoneNumberHelper;
/** Helper to access Telephony phone number utils class */
@@ -264,20 +127,6 @@ public class CallLogAdapter extends GroupingListAdapter
private CallItemExpandedListener mCallItemExpandedListener;
- /** Can be set to true by tests to disable processing of requests. */
- private volatile boolean mRequestProcessingDisabled = false;
-
- private boolean mIsCallLog = true;
-
- private View mBadgeContainer;
- private ImageView mBadgeImageView;
- private TextView mBadgeText;
-
- private int mCallLogBackgroundColor;
- private int mExpandedBackgroundColor;
- private float mExpandedTranslationZ;
- private int mPhotoSize;
-
/** Listener for the primary or secondary actions in the list.
* Primary opens the call details.
* Secondary calls or plays.
@@ -285,7 +134,14 @@ public class CallLogAdapter extends GroupingListAdapter
private final View.OnClickListener mActionListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
- startActivityForAction(view);
+ final IntentProvider intentProvider = (IntentProvider) view.getTag();
+ if (intentProvider != null) {
+ final Intent intent = intentProvider.getIntent(mContext);
+ // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
+ if (intent != null) {
+ DialerUtils.startActivityWithErrorToast(mContext, intent);
+ }
+ }
}
};
@@ -297,94 +153,63 @@ public class CallLogAdapter extends GroupingListAdapter
@Override
public void onClick(View v) {
final View callLogItem = (View) v.getParent().getParent();
- handleRowExpanded(callLogItem, true /* animate */, false /* forceExpand */);
+ handleRowExpanded(callLogItem, false /* forceExpand */);
}
};
+ protected final OnContactInfoChangedListener mOnContactInfoChangedListener =
+ new OnContactInfoChangedListener() {
+ @Override
+ public void onContactInfoChanged() {
+ notifyDataSetChanged();
+ }
+ };
+
private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
- handleRowExpanded(host, false /* animate */,
- true /* forceExpand */);
+ handleRowExpanded(host, true /* forceExpand */);
}
return super.onRequestSendAccessibilityEvent(host, child, event);
}
};
- private void startActivityForAction(View view) {
- final IntentProvider intentProvider = (IntentProvider) view.getTag();
- if (intentProvider != null) {
- final Intent intent = intentProvider.getIntent(mContext);
- // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
- if (intent != null) {
- DialerUtils.startActivityWithErrorToast(mContext, intent);
- }
- }
- }
-
@Override
public boolean onPreDraw() {
// We only wanted to listen for the first draw (and this is it).
unregisterPreDrawListener();
- // Only schedule a thread-creation message if the thread hasn't been
- // created yet. This is purely an optimization, to queue fewer messages.
- if (mCallerIdThread == null) {
- mHandler.sendEmptyMessageDelayed(START_THREAD, START_PROCESSING_REQUESTS_DELAY_MILLIS);
- }
-
+ mContactInfoCache.start();
return true;
}
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case REDRAW:
- notifyDataSetChanged();
- break;
- case START_THREAD:
- startRequestProcessing();
- break;
- }
- }
- };
-
- public CallLogAdapter(Context context, CallFetcher callFetcher,
- ContactInfoHelper contactInfoHelper, CallItemExpandedListener callItemExpandedListener,
- OnReportButtonClickListener onReportButtonClickListener, boolean isCallLog) {
+ public CallLogAdapter(
+ Context context,
+ CallFetcher callFetcher,
+ ContactInfoHelper contactInfoHelper,
+ OnReportButtonClickListener onReportButtonClickListener) {
super(context);
mContext = context;
mCallFetcher = callFetcher;
mContactInfoHelper = contactInfoHelper;
- mIsCallLog = isCallLog;
- mCallItemExpandedListener = callItemExpandedListener;
mOnReportButtonClickListener = onReportButtonClickListener;
- mReportedToast = Toast.makeText(mContext, R.string.toast_caller_id_reported,
- Toast.LENGTH_SHORT);
- mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
- mRequests = new LinkedList<ContactInfoRequest>();
+ mContactInfoCache = new ContactInfoCache(
+ mContactInfoHelper, mOnContactInfoChangedListener);
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);
- mPhotoSize = resources.getDimensionPixelSize(R.dimen.contact_photo_size);
- mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
mPhoneNumberHelper = new PhoneNumberDisplayHelper(mContext, resources);
mPhoneNumberUtilsWrapper = new PhoneNumberUtilsWrapper(mContext);
PhoneCallDetailsHelper phoneCallDetailsHelper =
new PhoneCallDetailsHelper(mContext, resources, mPhoneNumberUtilsWrapper);
mCallLogViewsHelper =
- new CallLogListItemHelper(
- phoneCallDetailsHelper, mPhoneNumberHelper, resources);
+ new CallLogListItemHelper(phoneCallDetailsHelper, mPhoneNumberHelper, resources);
mCallLogGroupBuilder = new CallLogGroupBuilder(this);
}
@@ -411,37 +236,6 @@ public class CallLogAdapter extends GroupingListAdapter
}
/**
- * Starts a background thread to process contact-lookup requests, unless one
- * has already been started.
- */
- private synchronized void startRequestProcessing() {
- // For unit-testing.
- if (mRequestProcessingDisabled) return;
-
- // Idempotence... if a thread is already started, don't start another.
- if (mCallerIdThread != null) return;
-
- mCallerIdThread = new QueryThread();
- mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
- mCallerIdThread.start();
- }
-
- /**
- * Stops the background thread that processes updates and cancels any
- * pending requests to start it.
- */
- public synchronized void stopRequestProcessing() {
- // Remove any pending requests to start the processing thread.
- mHandler.removeMessages(START_THREAD);
- if (mCallerIdThread != null) {
- // Stop the thread; we are finished with it.
- mCallerIdThread.stopProcessing();
- mCallerIdThread.interrupt();
- mCallerIdThread = null;
- }
- }
-
- /**
* Stop receiving onPreDraw() notifications.
*/
private void unregisterPreDrawListener() {
@@ -452,133 +246,14 @@ public class CallLogAdapter extends GroupingListAdapter
}
public void invalidateCache() {
- mContactInfoCache.expireAll();
+ mContactInfoCache.invalidate();
// Restart the request-processing thread after the next draw.
- stopRequestProcessing();
unregisterPreDrawListener();
}
- /**
- * Enqueues a request to look up the contact details for the given phone number.
- * <p>
- * It also provides the current contact info stored in the call log for this number.
- * <p>
- * If the {@code immediate} parameter is true, it will start immediately the thread that looks
- * up the contact information (if it has not been already started). Otherwise, it will be
- * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}.
- */
- protected void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
- boolean immediate) {
- ContactInfoRequest request = new ContactInfoRequest(number, countryIso, callLogInfo);
- synchronized (mRequests) {
- if (!mRequests.contains(request)) {
- mRequests.add(request);
- mRequests.notifyAll();
- }
- }
- if (immediate) startRequestProcessing();
- }
-
- /**
- * Queries the appropriate content provider for the contact associated with the number.
- * <p>
- * Upon completion it also updates the cache in the call log, if it is different from
- * {@code callLogInfo}.
- * <p>
- * The number might be either a SIP address or a phone number.
- * <p>
- * It returns true if it updated the content of the cache and we should therefore tell the
- * view to update its content.
- */
- private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) {
- final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
-
- if (info == null) {
- // The lookup failed, just return without requesting to update the view.
- return false;
- }
-
- // Check the existing entry in the cache: only if it has changed we should update the
- // view.
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(numberCountryIso);
-
- final boolean isRemoteSource = info.sourceType != 0;
-
- // Don't force redraw if existing info in the cache is equal to {@link ContactInfo#EMPTY}
- // to avoid updating the data set for every new row that is scrolled into view.
- // see (https://googleplex-android-review.git.corp.google.com/#/c/166680/)
-
- // Exception: Photo uris for contacts from remote sources are not cached in the call log
- // cache, so we have to force a redraw for these contacts regardless.
- boolean updated = (existingInfo != ContactInfo.EMPTY || isRemoteSource) &&
- !info.equals(existingInfo);
-
- // Store the data in the cache so that the UI thread can use to display it. Store it
- // even if it has not changed so that it is marked as not expired.
- mContactInfoCache.put(numberCountryIso, info);
- // Update the call log even if the cache it is up-to-date: it is possible that the cache
- // contains the value from a different call log entry.
- updateCallLogContactInfoCache(number, countryIso, info, callLogInfo);
- return updated;
- }
-
- /*
- * Handles requests for contact name and number type.
- */
- private class QueryThread extends Thread {
- private volatile boolean mDone = false;
-
- public QueryThread() {
- super("CallLogAdapter.QueryThread");
- }
-
- public void stopProcessing() {
- mDone = true;
- }
-
- @Override
- public void run() {
- boolean needRedraw = false;
- while (true) {
- // Check if thread is finished, and if so return immediately.
- if (mDone) return;
-
- // Obtain next request, if any is available.
- // Keep synchronized section small.
- ContactInfoRequest req = null;
- synchronized (mRequests) {
- if (!mRequests.isEmpty()) {
- req = mRequests.removeFirst();
- }
- }
-
- if (req != null) {
- // Process the request. If the lookup succeeds, schedule a
- // redraw.
- needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo);
- } else {
- // Throttle redraw rate by only sending them when there are
- // more requests.
- if (needRedraw) {
- needRedraw = false;
- mHandler.sendEmptyMessage(REDRAW);
- }
-
- // Wait until another request is available, or until this
- // thread is no longer needed (as indicated by being
- // interrupted).
- try {
- synchronized (mRequests) {
- mRequests.wait(1000);
- }
- } catch (InterruptedException ie) {
- // Ignore, and attempt to continue processing requests.
- }
- }
- }
- }
+ public void pauseCache() {
+ mContactInfoCache.stop();
}
@Override
@@ -602,7 +277,7 @@ public class CallLogAdapter extends GroupingListAdapter
View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
// Get the views to bind to and cache them.
- CallLogListItemViews views = CallLogListItemViews.fromView(view);
+ CallLogListItemViews views = CallLogListItemViews.fromView(context, view);
view.setTag(views);
// Set text height to false on the TextViews so they don't have extra padding.
@@ -640,7 +315,7 @@ public class CallLogAdapter extends GroupingListAdapter
* @param c the cursor pointing to the entry in the call log
* @param count the number of entries in the current item, greater than 1 if it is a group
*/
- private void bindView(View callLogItemView, Cursor c, int count) {
+ public void bindView(View callLogItemView, Cursor c, int count) {
callLogItemView.setAccessibilityDelegate(mAccessibilityDelegate);
final CallLogListItemViews views = (CallLogListItemViews) callLogItemView.getTag();
@@ -660,17 +335,12 @@ public class CallLogAdapter extends GroupingListAdapter
final long rowId = c.getLong(CallLogQuery.ID);
views.rowId = rowId;
- // For entries in the call log, check if the day group has changed and display a header
- // if necessary.
- if (mIsCallLog) {
- int currentGroup = getDayGroupForCall(rowId);
- int previousGroup = getPreviousDayGroup(c);
- if (currentGroup != previousGroup) {
- views.dayGroupHeader.setVisibility(View.VISIBLE);
- views.dayGroupHeader.setText(getGroupDescription(currentGroup));
- } else {
- views.dayGroupHeader.setVisibility(View.GONE);
- }
+ // Check if the day group has changed and display a header if necessary.
+ int currentGroup = getDayGroupForCall(rowId);
+ int previousGroup = getPreviousDayGroup(c);
+ if (currentGroup != previousGroup) {
+ views.dayGroupHeader.setVisibility(View.VISIBLE);
+ views.dayGroupHeader.setText(getGroupDescription(currentGroup));
} else {
views.dayGroupHeader.setVisibility(View.GONE);
}
@@ -685,74 +355,22 @@ public class CallLogAdapter extends GroupingListAdapter
// Stash away the Ids of the calls so that we can support deleting a row in the call log.
views.callIds = getCallIds(c, count);
- final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
+ final ContactInfo cachedContactInfo = mContactInfoHelper.getContactInfo(c);
final boolean isVoicemailNumber =
mPhoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number);
- // Where binding and not in the call log, use default behaviour of invoking a call when
- // tapping the primary view.
- if (!mIsCallLog) {
- views.primaryActionView.setOnClickListener(this.mActionListener);
-
- // Set return call intent, otherwise null.
- if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) {
- // Sets the primary action to call the number.
- if (isVoicemailNumber) {
- views.primaryActionView.setTag(
- IntentProvider.getReturnVoicemailCallIntentProvider());
- } else {
- views.primaryActionView.setTag(
- IntentProvider.getReturnCallIntentProvider(number));
- }
- } else {
- // Number is not callable, so hide button.
- views.primaryActionView.setTag(null);
- }
- } else {
- // In the call log, expand/collapse an actions section for the call log entry when
- // the primary view is tapped.
- views.primaryActionView.setOnClickListener(this.mExpandCollapseListener);
-
- // Note: Binding of the action buttons is done as required in configureActionViews
- // when the user expands the actions ViewStub.
- }
+ // Expand/collapse an actions section for the call log entry when the primary view is tapped.
+ views.primaryActionView.setOnClickListener(mExpandCollapseListener);
- // Lookup contacts with this number
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ExpirableCache.CachedValue<ContactInfo> cachedInfo =
- mContactInfoCache.getCachedValue(numberCountryIso);
- ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
- if (!PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)
- || isVoicemailNumber) {
- // If this is a number that cannot be dialed, there is no point in looking up a contact
- // for it.
- info = ContactInfo.EMPTY;
- } else if (cachedInfo == null) {
- mContactInfoCache.put(numberCountryIso, ContactInfo.EMPTY);
- // Use the cached contact info from the call log.
- info = cachedContactInfo;
- // The db request should happen on a non-UI thread.
- // Request the contact details immediately since they are currently missing.
- enqueueRequest(number, countryIso, cachedContactInfo, true);
- // We will format the phone number when we make the background request.
- } else {
- if (cachedInfo.isExpired()) {
- // The contact info is no longer up to date, we should request it. However, we
- // do not need to request them immediately.
- enqueueRequest(number, countryIso, cachedContactInfo, false);
- } else if (!callLogInfoMatches(cachedContactInfo, info)) {
- // The call log information does not match the one we have, look it up again.
- // We could simply update the call log directly, but that needs to be done in a
- // background thread, so it is easier to simply request a new lookup, which will, as
- // a side-effect, update the call log.
- enqueueRequest(number, countryIso, cachedContactInfo, false);
- }
+ // Note: Binding of the action buttons is done as required in configureActionViews when the
+ // user expands the actions ViewStub.
- if (info == ContactInfo.EMPTY) {
- // Use the cached contact info from the call log.
- info = cachedContactInfo;
- }
+ ContactInfo info = ContactInfo.EMPTY;
+ if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)
+ && !isVoicemailNumber) {
+ // Lookup contacts with this number
+ info = mContactInfoCache.getValue(number, countryIso, cachedContactInfo);
}
final Uri lookupUri = info.lookupUri;
@@ -762,7 +380,7 @@ public class CallLogAdapter extends GroupingListAdapter
final long photoId = info.photoId;
final Uri photoUri = info.photoUri;
CharSequence formattedNumber = info.formattedNumber == null
- ? null : PhoneNumberUtils.ttsSpanAsPhoneNumber(info.formattedNumber);
+ ? null : PhoneNumberUtils.getPhoneTtsSpannable(info.formattedNumber);
final int[] callTypes = getCallTypes(c, count);
final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
final int sourceType = info.sourceType;
@@ -784,7 +402,12 @@ public class CallLogAdapter extends GroupingListAdapter
// Restore expansion state of the row on rebind. Inflate the actions ViewStub if required,
// and set its visibility state accordingly.
- expandOrCollapseActions(callLogItemView, isExpanded(rowId));
+ views.expandOrCollapseActions(
+ isExpanded(rowId),
+ mOnReportButtonClickListener,
+ mActionListener,
+ mPhoneNumberUtilsWrapper,
+ mCallLogViewsHelper);
if (TextUtils.isEmpty(name)) {
details = new PhoneCallDetails(number, numberPresentation, formattedNumber, countryIso,
@@ -798,17 +421,6 @@ public class CallLogAdapter extends GroupingListAdapter
mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details);
- int contactType = ContactPhotoManager.TYPE_DEFAULT;
-
- if (isVoicemailNumber) {
- contactType = ContactPhotoManager.TYPE_VOICEMAIL;
- } else if (mContactInfoHelper.isBusiness(info.sourceType)) {
- contactType = ContactPhotoManager.TYPE_BUSINESS;
- }
-
- String lookupKey = lookupUri == null ? null
- : ContactInfoHelper.getLookupKeyFromUri(lookupUri);
-
String nameForDefaultImage = null;
if (TextUtils.isEmpty(name)) {
nameForDefaultImage = mPhoneNumberHelper.getDisplayNumber(details.accountHandle,
@@ -817,19 +429,15 @@ public class CallLogAdapter extends GroupingListAdapter
nameForDefaultImage = name;
}
- if (photoId == 0 && photoUri != null) {
- setPhoto(views, photoUri, lookupUri, nameForDefaultImage, lookupKey, contactType);
- } else {
- setPhoto(views, photoId, lookupUri, nameForDefaultImage, lookupKey, contactType);
- }
+ views.setPhoto(photoId, photoUri, lookupUri, nameForDefaultImage, isVoicemailNumber,
+ mContactInfoHelper.isBusiness(info.sourceType));
+ views.quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
// Listen for the first draw
if (mViewTreeObserver == null) {
mViewTreeObserver = callLogItemView.getViewTreeObserver();
mViewTreeObserver.addOnPreDrawListener(this);
}
-
- bindBadge(callLogItemView, info, details, callType);
}
/**
@@ -864,6 +472,7 @@ public class CallLogAdapter extends GroupingListAdapter
}
return CallLogGroupBuilder.DAY_GROUP_NONE;
}
+
/**
* Determines if a call log row with the given Id is expanded.
* @param rowId The row Id of the call.
@@ -884,342 +493,16 @@ public class CallLogAdapter extends GroupingListAdapter
private boolean toggleExpansion(long rowId) {
if (rowId == mCurrentlyExpanded) {
// Collapsing currently expanded row.
- mPreviouslyExpanded = NONE_EXPANDED;
mCurrentlyExpanded = NONE_EXPANDED;
-
return false;
} else {
// Expanding a row (collapsing current expanded one).
-
- mPreviouslyExpanded = mCurrentlyExpanded;
mCurrentlyExpanded = rowId;
return true;
}
}
/**
- * Expands or collapses the view containing the CALLBACK/REDIAL, VOICEMAIL and DETAILS action
- * buttons.
- *
- * @param callLogItem The call log entry parent view.
- * @param isExpanded The new expansion state of the view.
- */
- private void expandOrCollapseActions(View callLogItem, boolean isExpanded) {
- final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
-
- expandVoicemailTranscriptionView(views, isExpanded);
- if (isExpanded) {
- // Inflate the view stub if necessary, and wire up the event handlers.
- inflateActionViewStub(callLogItem);
-
- views.actionsView.setVisibility(View.VISIBLE);
- views.actionsView.setAlpha(1.0f);
- views.callLogEntryView.setBackgroundColor(mExpandedBackgroundColor);
- views.callLogEntryView.setTranslationZ(mExpandedTranslationZ);
- callLogItem.setTranslationZ(mExpandedTranslationZ); // WAR
- } else {
- // When recycling a view, it is possible the actionsView ViewStub was previously
- // inflated so we should hide it in this case.
- if (views.actionsView != null) {
- views.actionsView.setVisibility(View.GONE);
- }
-
- views.callLogEntryView.setBackgroundColor(mCallLogBackgroundColor);
- views.callLogEntryView.setTranslationZ(0);
- callLogItem.setTranslationZ(0); // WAR
- }
- }
-
- public static void expandVoicemailTranscriptionView(CallLogListItemViews views,
- boolean isExpanded) {
- if (views.callType != Calls.VOICEMAIL_TYPE) {
- return;
- }
-
- final TextView view = views.phoneCallDetailsViews.voicemailTranscriptionView;
- if (TextUtils.isEmpty(view.getText())) {
- return;
- }
- view.setMaxLines(isExpanded ? VOICEMAIL_TRANSCRIPTION_MAX_LINES : 1);
- view.setSingleLine(!isExpanded);
- }
-
- /**
- * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not
- * inflated during initial binding, so click handlers, tags and accessibility text must be set
- * here, if necessary.
- *
- * @param callLogItem The call log list item view.
- */
- private void inflateActionViewStub(final View callLogItem) {
- final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
-
- ViewStub stub = (ViewStub)callLogItem.findViewById(R.id.call_log_entry_actions_stub);
- if (stub != null) {
- views.actionsView = (ViewGroup) stub.inflate();
- }
-
- if (views.callBackButtonView == null) {
- views.callBackButtonView = (TextView)views.actionsView.findViewById(
- R.id.call_back_action);
- }
-
- if (views.videoCallButtonView == null) {
- views.videoCallButtonView = (TextView)views.actionsView.findViewById(
- R.id.video_call_action);
- }
-
- if (views.voicemailButtonView == null) {
- views.voicemailButtonView = (TextView)views.actionsView.findViewById(
- R.id.voicemail_action);
- }
-
- if (views.detailsButtonView == null) {
- views.detailsButtonView = (TextView)views.actionsView.findViewById(R.id.details_action);
- }
-
- if (views.reportButtonView == null) {
- views.reportButtonView = (TextView)views.actionsView.findViewById(R.id.report_action);
- views.reportButtonView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOnReportButtonClickListener != null) {
- mOnReportButtonClickListener.onReportButtonClick(views.number);
- }
- }
- });
- }
-
- bindActionButtons(views);
- }
-
- /***
- * Binds text titles, click handlers and intents to the voicemail, details and callback action
- * buttons.
- *
- * @param views The call log item views.
- */
- private void bindActionButtons(CallLogListItemViews views) {
- boolean canPlaceCallToNumber =
- PhoneNumberUtilsWrapper.canPlaceCallsTo(views.number, views.numberPresentation);
- // Set return call intent, otherwise null.
- if (canPlaceCallToNumber) {
- boolean isVoicemailNumber =
- mPhoneNumberUtilsWrapper.isVoicemailNumber(views.accountHandle, views.number);
- if (isVoicemailNumber) {
- // Make a general call to voicemail to ensure that if there are multiple accounts
- // it does not call the voicemail number of a specific phone account.
- views.callBackButtonView.setTag(
- IntentProvider.getReturnVoicemailCallIntentProvider());
- } else {
- // Sets the primary action to call the number.
- views.callBackButtonView.setTag(
- IntentProvider.getReturnCallIntentProvider(views.number));
- }
- views.callBackButtonView.setVisibility(View.VISIBLE);
- views.callBackButtonView.setOnClickListener(mActionListener);
-
- final int titleId;
- if (views.callType == Calls.VOICEMAIL_TYPE || views.callType == Calls.OUTGOING_TYPE) {
- titleId = R.string.call_log_action_redial;
- } else {
- titleId = R.string.call_log_action_call_back;
- }
- views.callBackButtonView.setText(mContext.getString(titleId));
- } else {
- // Number is not callable, so hide button.
- views.callBackButtonView.setTag(null);
- views.callBackButtonView.setVisibility(View.GONE);
- }
-
- // If one of the calls had video capabilities, show the video call button.
- if (CallUtil.isVideoEnabled(mContext) && canPlaceCallToNumber &&
- views.phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
- views.videoCallButtonView.setTag(
- IntentProvider.getReturnVideoCallIntentProvider(views.number));
- views.videoCallButtonView.setVisibility(View.VISIBLE);
- views.videoCallButtonView.setOnClickListener(mActionListener);
- } else {
- views.videoCallButtonView.setTag(null);
- views.videoCallButtonView.setVisibility(View.GONE);
- }
-
- // For voicemail calls, show the "VOICEMAIL" action button; hide otherwise.
- if (views.callType == Calls.VOICEMAIL_TYPE) {
- views.voicemailButtonView.setOnClickListener(mActionListener);
- views.voicemailButtonView.setTag(
- IntentProvider.getPlayVoicemailIntentProvider(
- views.rowId, views.voicemailUri));
- views.voicemailButtonView.setVisibility(View.VISIBLE);
-
- views.detailsButtonView.setVisibility(View.GONE);
- } else {
- views.voicemailButtonView.setTag(null);
- views.voicemailButtonView.setVisibility(View.GONE);
-
- views.detailsButtonView.setOnClickListener(mActionListener);
- views.detailsButtonView.setTag(
- IntentProvider.getCallDetailIntentProvider(
- views.rowId, views.callIds, null)
- );
-
- if (views.canBeReportedAsInvalid && !views.reported) {
- views.reportButtonView.setVisibility(View.VISIBLE);
- } else {
- views.reportButtonView.setVisibility(View.GONE);
- }
- }
-
- mCallLogViewsHelper.setActionContentDescriptions(views);
- }
-
- protected void bindBadge(
- 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);
- if (UriUtils.isEncodedContactUri(info.lookupUri)) {
- if (stub != null) {
- mBadgeContainer = stub.inflate();
- } else {
- mBadgeContainer = view.findViewById(R.id.badge_container);
- }
-
- 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 {
- // Hide badge if it was previously shown.
- mBadgeContainer = view.findViewById(R.id.badge_container);
- if (mBadgeContainer != null) {
- mBadgeContainer.setVisibility(View.GONE);
- }
- }
- }
- }
-
- /** Checks whether the contact info from the call log matches the one from the contacts db. */
- private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
- // The call log only contains a subset of the fields in the contacts db.
- // Only check those.
- return TextUtils.equals(callLogInfo.name, info.name)
- && callLogInfo.type == info.type
- && TextUtils.equals(callLogInfo.label, info.label);
- }
-
- /** Stores the updated contact info in the call log if it is different from the current one. */
- private void updateCallLogContactInfoCache(String number, String countryIso,
- ContactInfo updatedInfo, ContactInfo callLogInfo) {
- final ContentValues values = new ContentValues();
- boolean needsUpdate = false;
-
- if (callLogInfo != null) {
- if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
- values.put(Calls.CACHED_NAME, updatedInfo.name);
- needsUpdate = true;
- }
-
- if (updatedInfo.type != callLogInfo.type) {
- values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
- needsUpdate = true;
- }
-
- if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
- values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
- needsUpdate = true;
- }
- if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) {
- values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
- needsUpdate = true;
- }
- // Only replace the normalized number if the new updated normalized number isn't empty.
- if (!TextUtils.isEmpty(updatedInfo.normalizedNumber) &&
- !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) {
- values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
- needsUpdate = true;
- }
- if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) {
- values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
- needsUpdate = true;
- }
- if (updatedInfo.photoId != callLogInfo.photoId) {
- values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
- needsUpdate = true;
- }
- if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) {
- values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
- needsUpdate = true;
- }
- } else {
- // No previous values, store all of them.
- values.put(Calls.CACHED_NAME, updatedInfo.name);
- values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
- values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
- values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
- values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
- values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
- values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
- values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
- needsUpdate = true;
- }
-
- if (!needsUpdate) return;
-
- 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);
- }
- }
-
- /** Returns the contact information as stored in the call log. */
- private ContactInfo getContactInfoFromCallLog(Cursor c) {
- ContactInfo info = new ContactInfo();
- info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI));
- info.name = c.getString(CallLogQuery.CACHED_NAME);
- info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
- info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
- String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER);
- info.number = matchedNumber == null ? c.getString(CallLogQuery.NUMBER) : matchedNumber;
- info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
- info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
- info.photoUri = null; // We do not cache the photo URI.
- info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
- return info;
- }
-
- /**
* Returns the call types for the given number of items in the cursor.
* <p>
* It uses the next {@code count} rows in the cursor to extract the types.
@@ -1256,55 +539,22 @@ public class CallLogAdapter extends GroupingListAdapter
return features;
}
- private void setPhoto(CallLogListItemViews views, long photoId, Uri contactUri,
- String displayName, String identifier, int contactType) {
- views.quickContactView.assignContactUri(contactUri);
- views.quickContactView.setOverlay(null);
- DefaultImageRequest request = new DefaultImageRequest(displayName, identifier,
- contactType, true /* isCircular */);
- mContactPhotoManager.loadThumbnail(views.quickContactView, photoId, false /* darkTheme */,
- true /* isCircular */, request);
- }
-
- private void setPhoto(CallLogListItemViews views, Uri photoUri, Uri contactUri,
- String displayName, String identifier, int contactType) {
- views.quickContactView.assignContactUri(contactUri);
- views.quickContactView.setOverlay(null);
- DefaultImageRequest request = new DefaultImageRequest(displayName, identifier,
- contactType, true /* isCircular */);
- mContactPhotoManager.loadPhoto(views.quickContactView, photoUri, mPhotoSize,
- false /* darkTheme */, true /* isCircular */, request);
- }
-
- /**
- * Bind a call log entry view for testing purposes. Also inflates the action view stub so
- * unit tests can access the buttons contained within.
- *
- * @param view The current call log row.
- * @param context The current context.
- * @param cursor The cursor to bind from.
- */
- @VisibleForTesting
- void bindViewForTest(View view, Context context, Cursor cursor) {
- bindStandAloneView(view, context, cursor);
- inflateActionViewStub(view);
- }
-
/**
* Sets whether processing of requests for contact details should be enabled.
- * <p>
+ *
* This method should be called in tests to disable such processing of requests when not
* needed.
*/
@VisibleForTesting
void disableRequestProcessingForTest() {
- mRequestProcessingDisabled = true;
+ // TODO: Remove this and test the cache directly.
+ mContactInfoCache.disableRequestProcessingForTest();
}
@VisibleForTesting
void injectContactInfoForTest(String number, String countryIso, ContactInfo contactInfo) {
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- mContactInfoCache.put(numberCountryIso, contactInfo);
+ // TODO: Remove this and test the cache directly.
+ mContactInfoCache.injectContactInfoForTest(number, countryIso, contactInfo);
}
@Override
@@ -1333,48 +583,6 @@ public class CallLogAdapter extends GroupingListAdapter
mDayGroups.clear();
}
- /*
- * Get the number from the Contacts, if available, since sometimes
- * the number provided by caller id may not be formatted properly
- * depending on the carrier (roaming) in use at the time of the
- * incoming call.
- * Logic : If the caller-id number starts with a "+", use it
- * Else if the number in the contacts starts with a "+", use that one
- * Else if the number in the contacts is longer, use that one
- */
- public String getBetterNumberFromContacts(String number, String countryIso) {
- String matchingNumber = null;
- // Look in the cache first. If it's not found then query the Phones db
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ContactInfo ci = mContactInfoCache.getPossiblyExpired(numberCountryIso);
- if (ci != null && ci != ContactInfo.EMPTY) {
- matchingNumber = ci.number;
- } else {
- try {
- Cursor phonesCursor = mContext.getContentResolver().query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
- PhoneQuery._PROJECTION, null, null, null);
- if (phonesCursor != null) {
- try {
- if (phonesCursor.moveToFirst()) {
- matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
- }
- } finally {
- phonesCursor.close();
- }
- }
- } catch (Exception e) {
- // Use the number from the call log
- }
- }
- if (!TextUtils.isEmpty(matchingNumber) &&
- (matchingNumber.startsWith("+")
- || matchingNumber.length() > number.length())) {
- number = matchingNumber;
- }
- return number;
- }
-
/**
* Retrieves the call Ids represented by the current call log row.
*
@@ -1411,20 +619,14 @@ public class CallLogAdapter extends GroupingListAdapter
}
}
- public void onBadDataReported(String number) {
- mContactInfoCache.expireAll();
- mReportedToast.show();
- }
-
/**
* Manages the state changes for the UI interaction where a call log row is expanded.
*
* @param view The view that was tapped
- * @param animate Whether or not to animate the expansion/collapse
* @param forceExpand Whether or not to force the call log row into an expanded state regardless
* of its previous state
*/
- private void handleRowExpanded(View view, boolean animate, boolean forceExpand) {
+ private void handleRowExpanded(View view, boolean forceExpand) {
final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
if (forceExpand && isExpanded(views.rowId)) {
@@ -1433,78 +635,20 @@ public class CallLogAdapter extends GroupingListAdapter
// Hide or show the actions view.
boolean expanded = toggleExpansion(views.rowId);
-
- // Trigger loading of the viewstub and visual expand or collapse.
- expandOrCollapseActions(view, expanded);
-
- // Animate the expansion or collapse.
- if (mCallItemExpandedListener != null) {
- if (animate) {
- mCallItemExpandedListener.onItemExpanded(view);
- }
-
- // Animate the collapse of the previous item if it is still visible on screen.
- if (mPreviouslyExpanded != NONE_EXPANDED) {
- View previousItem = mCallItemExpandedListener.getViewForCallId(
- mPreviouslyExpanded);
-
- if (previousItem != null) {
- expandOrCollapseActions(previousItem, false);
- if (animate) {
- mCallItemExpandedListener.onItemExpanded(previousItem);
- }
- }
- mPreviouslyExpanded = NONE_EXPANDED;
- }
- }
+ expandItem(views, expanded);
}
/**
- * 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.
+ * @param views The view holder for the item to expand or collapse.
+ * @param expand {@code true} to expand the item, {@code false} otherwise.
*/
- 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);
+ public void expandItem(CallLogListItemViews views, boolean expand) {
+ // Trigger loading of the viewstub and visual expand or collapse.
+ views.expandOrCollapseActions(
+ expand,
+ mOnReportButtonClickListener,
+ mActionListener,
+ mPhoneNumberUtilsWrapper,
+ mCallLogViewsHelper);
}
}
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 9c95d3ec8..d69c2ed7e 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -62,13 +62,10 @@ import java.util.List;
*/
public class CallLogFragment extends ListFragment
implements CallLogQueryHandler.Listener, CallLogAdapter.OnReportButtonClickListener,
- CallLogAdapter.CallFetcher,
- CallLogAdapter.CallItemExpandedListener {
+ CallLogAdapter.CallFetcher {
private static final String TAG = "CallLogFragment";
private static final String REPORT_DIALOG_TAG = "report_dialog";
- private String mReportDialogNumber;
- private boolean mIsReportDialogShowing;
/**
* ID of the empty loader to defer other fragments.
@@ -78,9 +75,6 @@ public class CallLogFragment extends ListFragment
private static final String KEY_FILTER_TYPE = "filter_type";
private static final String KEY_LOG_LIMIT = "log_limit";
private static final String KEY_DATE_LIMIT = "date_limit";
- private static final String KEY_SHOW_FOOTER = "show_footer";
- private static final String KEY_IS_REPORT_DIALOG_SHOWING = "is_report_dialog_showing";
- private static final String KEY_REPORT_DIALOG_NUMBER = "report_dialog_number";
private CallLogAdapter mAdapter;
private CallLogQueryHandler mCallLogQueryHandler;
@@ -91,21 +85,15 @@ public class CallLogFragment extends ListFragment
private VoicemailStatusHelper mVoicemailStatusHelper;
private View mStatusMessageView;
+ private View mEmptyListView;
private TextView mStatusMessageText;
private TextView mStatusMessageAction;
private KeyguardManager mKeyguardManager;
- private View mFooterView;
private boolean mEmptyLoaderRunning;
private boolean mCallLogFetched;
private boolean mVoicemailStatusFetched;
- private float mExpandedItemTranslationZ;
- private int mFadeInDuration;
- private int mFadeInStartDelay;
- private int mFadeOutDuration;
- private int mExpandCollapseDuration;
-
private final Handler mHandler = new Handler();
private class CustomContentObserver extends ContentObserver {
@@ -138,9 +126,6 @@ public class CallLogFragment extends ListFragment
// the date filter are included. If zero, no date-based filtering occurs.
private long mDateLimit = 0;
- // Whether or not to show the Show call history footer view
- private boolean mHasFooterView = false;
-
public CallLogFragment() {
this(CallLogQueryHandler.CALL_TYPE_ALL, -1);
}
@@ -184,44 +169,24 @@ public class CallLogFragment extends ListFragment
mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter);
mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit);
mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit);
- mHasFooterView = state.getBoolean(KEY_SHOW_FOOTER, mHasFooterView);
- mIsReportDialogShowing = state.getBoolean(KEY_IS_REPORT_DIALOG_SHOWING,
- mIsReportDialogShowing);
- mReportDialogNumber = state.getString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber);
}
String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
mAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
- new ContactInfoHelper(getActivity(), currentCountryIso), this, this, true);
+ new ContactInfoHelper(getActivity(), currentCountryIso), this);
setListAdapter(mAdapter);
mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
this, mLogLimit);
mKeyguardManager =
(KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
- getActivity().getContentResolver().registerContentObserver(CallLog.CONTENT_URI, true,
- mCallLogObserver);
+ getActivity().getContentResolver().registerContentObserver(
+ CallLog.CONTENT_URI, true, mCallLogObserver);
getActivity().getContentResolver().registerContentObserver(
ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
getActivity().getContentResolver().registerContentObserver(
Status.CONTENT_URI, true, mVoicemailStatusObserver);
setHasOptionsMenu(true);
- updateCallList(mCallTypeFilter, mDateLimit);
-
- mExpandedItemTranslationZ =
- getResources().getDimension(R.dimen.call_log_expanded_translation_z);
- mFadeInDuration = getResources().getInteger(R.integer.call_log_actions_fade_in_duration);
- mFadeInStartDelay = getResources().getInteger(R.integer.call_log_actions_fade_start);
- mFadeOutDuration = getResources().getInteger(R.integer.call_log_actions_fade_out_duration);
- mExpandCollapseDuration = getResources().getInteger(
- R.integer.call_log_expand_collapse_duration);
-
- if (mIsReportDialogShowing) {
- DialogFragment df = ObjectFactory.getReportDialogFragment(mReportDialogNumber);
- if (df != null) {
- df.setTargetFragment(this, 0);
- df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG);
- }
- }
+ fetchCalls();
}
/** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
@@ -235,8 +200,13 @@ public class CallLogFragment extends ListFragment
mAdapter.changeCursor(cursor);
// This will update the state of the "Clear call log" menu item.
getActivity().invalidateOptionsMenu();
+
+ final ListView listView = getListView();
+ boolean showListView = cursor.getCount() > 0;
+ listView.setVisibility(showListView ? View.VISIBLE : View.GONE);
+ mEmptyListView.setVisibility(!showListView ? View.VISIBLE : View.GONE);
+
if (mScrollToTop) {
- final ListView listView = getListView();
// The smooth-scroll animation happens over a fixed time period.
// As a result, if it scrolls through a large portion of the list,
// each frame will jump so far from the previous one that the user
@@ -270,13 +240,21 @@ public class CallLogFragment extends ListFragment
*/
@Override
public void onVoicemailStatusFetched(Cursor statusCursor) {
- if (getActivity() == null || getActivity().isFinishing()) {
+ Activity activity = getActivity();
+ if (activity == null || activity.isFinishing()) {
return;
}
updateVoicemailStatusMessage(statusCursor);
- int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
- setVoicemailSourcesAvailable(activeSources != 0);
+ // If there are any changes to the presence of active voicemail services, invalidate the
+ // options menu so that it will be updated.
+ boolean hasActiveVoicemailSources =
+ mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) != 0;
+ if (mVoicemailSourcesAvailable != hasActiveVoicemailSources) {
+ mVoicemailSourcesAvailable = hasActiveVoicemailSources;
+ activity.invalidateOptionsMenu();
+ }
+
mVoicemailStatusFetched = true;
destroyEmptyLoaderIfAllDataFetched();
}
@@ -288,18 +266,6 @@ public class CallLogFragment extends ListFragment
}
}
- /** Sets whether there are any voicemail sources available in the platform. */
- private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) {
- if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return;
- mVoicemailSourcesAvailable = voicemailSourcesAvailable;
-
- Activity activity = getActivity();
- if (activity != null) {
- // This is so that the options menu content is updated.
- activity.invalidateOptionsMenu();
- }
- }
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
View view = inflater.inflate(R.layout.call_log_fragment, container, false);
@@ -313,9 +279,8 @@ public class CallLogFragment extends ListFragment
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- getListView().setEmptyView(view.findViewById(R.id.empty_list_view));
+ mEmptyListView = view.findViewById(R.id.empty_list_view);
getListView().setItemsCanFocus(true);
- maybeAddFooterView();
updateEmptyMessage(mCallTypeFilter);
}
@@ -382,20 +347,20 @@ public class CallLogFragment extends ListFragment
@Override
public void onPause() {
super.onPause();
- // Kill the requests thread
- mAdapter.stopRequestProcessing();
+ mAdapter.pauseCache();
}
@Override
public void onStop() {
super.onStop();
- updateOnExit();
+
+ updateOnTransition(false /* onEntry */);
}
@Override
public void onDestroy() {
super.onDestroy();
- mAdapter.stopRequestProcessing();
+ mAdapter.pauseCache();
mAdapter.changeCursor(null);
getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
@@ -408,9 +373,6 @@ public class CallLogFragment extends ListFragment
outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter);
outState.putInt(KEY_LOG_LIMIT, mLogLimit);
outState.putLong(KEY_DATE_LIMIT, mDateLimit);
- outState.putBoolean(KEY_SHOW_FOOTER, mHasFooterView);
- outState.putBoolean(KEY_IS_REPORT_DIALOG_SHOWING, mIsReportDialogShowing);
- outState.putString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber);
}
@Override
@@ -418,19 +380,6 @@ public class CallLogFragment extends ListFragment
mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
}
- public void startCallsQuery() {
- mAdapter.setLoading(true);
- mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
- }
-
- private void startVoicemailStatusQuery() {
- mCallLogQueryHandler.fetchVoicemailStatus();
- }
-
- private void updateCallList(int filterType, long dateLimit) {
- mCallLogQueryHandler.fetchCalls(filterType, dateLimit);
- }
-
private void updateEmptyMessage(int filterType) {
final int messageId;
switch (filterType) {
@@ -448,7 +397,7 @@ public class CallLogFragment extends ListFragment
+ filterType);
}
DialerUtils.configureEmptyListView(
- getListView().getEmptyView(), R.drawable.empty_call_log, messageId, getResources());
+ mEmptyListView, R.drawable.empty_call_log, messageId, getResources());
}
CallLogAdapter getAdapter() {
@@ -461,7 +410,7 @@ public class CallLogFragment extends ListFragment
if (mMenuVisible != menuVisible) {
mMenuVisible = menuVisible;
if (!menuVisible) {
- updateOnExit();
+ updateOnTransition(false /* onEntry */);
} else if (isResumed()) {
refreshData();
}
@@ -475,24 +424,26 @@ public class CallLogFragment extends ListFragment
// Mark all entries in the contact info cache as out of date, so they will be looked up
// again once being shown.
mAdapter.invalidateCache();
- startCallsQuery();
- startVoicemailStatusQuery();
- updateOnEntry();
- mRefreshDataRequired = false;
- }
- }
+ mAdapter.setLoading(true);
- /** Updates call data and notification state while leaving the call log tab. */
- private void updateOnExit() {
- updateOnTransition(false);
- }
+ fetchCalls();
+ mCallLogQueryHandler.fetchVoicemailStatus();
- /** Updates call data and notification state while entering the call log tab. */
- private void updateOnEntry() {
- updateOnTransition(true);
+ updateOnTransition(true /* onEntry */);
+ mRefreshDataRequired = false;
+ } else {
+ // Refresh the display of the existing data to update the timestamp text descriptions.
+ mAdapter.notifyDataSetChanged();
+ }
}
- // TODO: Move to CallLogActivity
+ /**
+ * Updates the call data and notification state on entering or leaving the call log tab.
+ *
+ * If we are leaving the call log tab, mark all the missed calls as read.
+ *
+ * TODO: Move to CallLogActivity
+ */
private void updateOnTransition(boolean onEntry) {
// We don't want to update any call data when keyguard is on because the user has likely not
// seen the new calls yet.
@@ -509,184 +460,11 @@ public class CallLogFragment extends ListFragment
}
}
- /**
- * Enables/disables the showing of the view full call history footer
- *
- * @param hasFooterView Whether or not to show the footer
- */
- public void setHasFooterView(boolean hasFooterView) {
- mHasFooterView = hasFooterView;
- maybeAddFooterView();
- }
-
- /**
- * Determine whether or not the footer view should be added to the listview. If getView()
- * is null, which means onCreateView hasn't been called yet, defer the addition of the footer
- * until onViewCreated has been called.
- */
- private void maybeAddFooterView() {
- if (!mHasFooterView || getView() == null) {
- return;
- }
-
- if (mFooterView == null) {
- mFooterView = getActivity().getLayoutInflater().inflate(
- R.layout.recents_list_footer, getListView(), false);
- mFooterView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- ((HostInterface) getActivity()).showCallHistory();
- }
- });
- }
-
- final ListView listView = getListView();
- listView.removeFooterView(mFooterView);
- listView.addFooterView(mFooterView);
-
- ViewUtil.addBottomPaddingToListViewForFab(listView, getResources());
- }
-
- @Override
- public void onItemExpanded(final View view) {
- final int startingHeight = view.getHeight();
- final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag();
- final ViewTreeObserver observer = getListView().getViewTreeObserver();
- observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // We don't want to continue getting called for every draw.
- if (observer.isAlive()) {
- observer.removeOnPreDrawListener(this);
- }
- // Calculate some values to help with the animation.
- final int endingHeight = view.getHeight();
- final int distance = Math.abs(endingHeight - startingHeight);
- final int baseHeight = Math.min(endingHeight, startingHeight);
- final boolean isExpand = endingHeight > startingHeight;
-
- // Set the views back to the start state of the animation
- view.getLayoutParams().height = startingHeight;
- if (!isExpand) {
- viewHolder.actionsView.setVisibility(View.VISIBLE);
- }
- CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, !isExpand);
-
- // Set up the fade effect for the action buttons.
- if (isExpand) {
- // Start the fade in after the expansion has partly completed, otherwise it
- // will be mostly over before the expansion completes.
- viewHolder.actionsView.setAlpha(0f);
- viewHolder.actionsView.animate()
- .alpha(1f)
- .setStartDelay(mFadeInStartDelay)
- .setDuration(mFadeInDuration)
- .start();
- } else {
- viewHolder.actionsView.setAlpha(1f);
- viewHolder.actionsView.animate()
- .alpha(0f)
- .setDuration(mFadeOutDuration)
- .start();
- }
- view.requestLayout();
-
- // Set up the animator to animate the expansion and shadow depth.
- ValueAnimator animator = isExpand ? ValueAnimator.ofFloat(0f, 1f)
- : ValueAnimator.ofFloat(1f, 0f);
-
- // Figure out how much scrolling is needed to make the view fully visible.
- final Rect localVisibleRect = new Rect();
- view.getLocalVisibleRect(localVisibleRect);
- final int scrollingNeeded = localVisibleRect.top > 0 ? -localVisibleRect.top
- : view.getMeasuredHeight() - localVisibleRect.height();
- final ListView listView = getListView();
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
- private int mCurrentScroll = 0;
-
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- Float value = (Float) animator.getAnimatedValue();
-
- // For each value from 0 to 1, animate the various parts of the layout.
- view.getLayoutParams().height = (int) (value * distance + baseHeight);
- float z = mExpandedItemTranslationZ * value;
- viewHolder.callLogEntryView.setTranslationZ(z);
- view.setTranslationZ(z); // WAR
- view.requestLayout();
-
- if (isExpand) {
- if (listView != null) {
- int scrollBy = (int) (value * scrollingNeeded) - mCurrentScroll;
- listView.smoothScrollBy(scrollBy, /* duration = */ 0);
- mCurrentScroll += scrollBy;
- }
- }
- }
- });
- // Set everything to their final values when the animation's done.
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
-
- if (!isExpand) {
- viewHolder.actionsView.setVisibility(View.GONE);
- } else {
- // This seems like it should be unnecessary, but without this, after
- // navigating out of the activity and then back, the action view alpha
- // is defaulting to the value (0) at the start of the expand animation.
- viewHolder.actionsView.setAlpha(1);
- }
- CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, isExpand);
- }
- });
-
- animator.setDuration(mExpandCollapseDuration);
- animator.start();
-
- // Return false so this draw does not occur to prevent the final frame from
- // being drawn for the single frame before the animations start.
- return false;
- }
- });
- }
-
- /**
- * Retrieves the call log view for the specified call Id. If the view is not currently
- * visible, returns null.
- *
- * @param callId The call Id.
- * @return The call log view.
- */
- @Override
- public View getViewForCallId(long callId) {
- ListView listView = getListView();
-
- int firstPosition = listView.getFirstVisiblePosition();
- int lastPosition = listView.getLastVisiblePosition();
-
- for (int position = 0; position <= lastPosition - firstPosition; position++) {
- View view = listView.getChildAt(position);
-
- if (view != null) {
- final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag();
- if (viewHolder != null && viewHolder.rowId == callId) {
- return view;
- }
- }
- }
-
- return null;
- }
-
public void onBadDataReported(String number) {
- mIsReportDialogShowing = false;
if (number == null) {
return;
}
- mAdapter.onBadDataReported(number);
+ mAdapter.invalidateCache();
mAdapter.notifyDataSetChanged();
}
@@ -695,8 +473,6 @@ public class CallLogFragment extends ListFragment
if (df != null) {
df.setTargetFragment(this, 0);
df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG);
- mReportDialogNumber = number;
- mIsReportDialogShowing = true;
}
}
}
diff --git a/src/com/android/dialer/calllog/CallLogGroupBuilder.java b/src/com/android/dialer/calllog/CallLogGroupBuilder.java
index 1f11e1e60..0826aeb4a 100644
--- a/src/com/android/dialer/calllog/CallLogGroupBuilder.java
+++ b/src/com/android/dialer/calllog/CallLogGroupBuilder.java
@@ -21,7 +21,6 @@ import android.provider.CallLog.Calls;
import android.telephony.PhoneNumberUtils;
import android.text.format.Time;
-import com.android.common.widget.GroupingListAdapter;
import com.android.contacts.common.util.DateUtils;
import com.android.contacts.common.util.PhoneNumberHelper;
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index 0ccdf00a1..9d11a3ab6 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -17,19 +17,36 @@
package com.android.dialer.calllog;
import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
import android.widget.QuickContactBadge;
import android.widget.TextView;
+import com.android.contacts.common.CallUtil;
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.testing.NeededForTesting;
import com.android.dialer.PhoneCallDetailsViews;
import com.android.dialer.R;
/**
- * Simple value object containing the various views within a call log entry.
+ * This is an object containing the various views within a call log entry. It contains values
+ * pointing to views contained by a call log list item view, so that we can improve performance
+ * by reducing the frequency with which we need to find views by IDs.
+ *
+ * This object also contains methods for inflating action views and binding action behaviors. This
+ * is a way of isolating view logic from the CallLogAdapter. We should consider moving that logic
+ * if the call log list item is eventually represented as a UI component.
*/
public final class CallLogListItemViews {
+ /** The root view of the call log list item */
+ public final View rootView;
/** The quick contact badge for the contact. */
public final QuickContactBadge quickContactView;
/** The primary action view of the entry. */
@@ -113,18 +130,43 @@ public final class CallLogListItemViews {
*/
public boolean canBeReportedAsInvalid;
- private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
- PhoneCallDetailsViews phoneCallDetailsViews, View callLogEntryView,
+ private static final int VOICEMAIL_TRANSCRIPTION_MAX_LINES = 10;
+
+ private Context mContext;
+ private int mPhotoSize;
+
+ private int mCallLogBackgroundColor;
+ private int mExpandedBackgroundColor;
+ private float mExpandedTranslationZ;
+
+ private CallLogListItemViews(
+ Context context,
+ View rootView,
+ QuickContactBadge quickContactView,
+ View primaryActionView,
+ PhoneCallDetailsViews phoneCallDetailsViews,
+ View callLogEntryView,
TextView dayGroupHeader) {
+ mContext = context;
+
+ this.rootView = rootView;
this.quickContactView = quickContactView;
this.primaryActionView = primaryActionView;
this.phoneCallDetailsViews = phoneCallDetailsViews;
this.callLogEntryView = callLogEntryView;
this.dayGroupHeader = dayGroupHeader;
+
+ Resources resources = mContext.getResources();
+ 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);
+ mPhotoSize = mContext.getResources().getDimensionPixelSize(R.dimen.contact_photo_size);
}
- public static CallLogListItemViews fromView(View view) {
+ public static CallLogListItemViews fromView(Context context, View view) {
return new CallLogListItemViews(
+ context,
+ view,
(QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
view.findViewById(R.id.primary_action_view),
PhoneCallDetailsViews.fromView(view),
@@ -132,9 +174,217 @@ public final class CallLogListItemViews {
(TextView) view.findViewById(R.id.call_log_day_group_label));
}
+ /**
+ * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not
+ * inflated during initial binding, so click handlers, tags and accessibility text must be set
+ * here, if necessary.
+ *
+ * @param callLogItem The call log list item view.
+ */
+ public void inflateActionViewStub(
+ final CallLogAdapter.OnReportButtonClickListener onReportButtonClickListener,
+ View.OnClickListener actionListener,
+ PhoneNumberUtilsWrapper phoneNumberUtilsWrapper,
+ CallLogListItemHelper callLogViewsHelper) {
+ ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub);
+ if (stub != null) {
+ actionsView = (ViewGroup) stub.inflate();
+ }
+
+ if (callBackButtonView == null) {
+ callBackButtonView = (TextView) actionsView.findViewById(R.id.call_back_action);
+ }
+
+ if (videoCallButtonView == null) {
+ videoCallButtonView = (TextView) actionsView.findViewById(R.id.video_call_action);
+ }
+
+ if (voicemailButtonView == null) {
+ voicemailButtonView = (TextView) actionsView.findViewById(R.id.voicemail_action);
+ }
+
+ if (detailsButtonView == null) {
+ detailsButtonView = (TextView) actionsView.findViewById(R.id.details_action);
+ }
+
+ if (reportButtonView == null) {
+ reportButtonView = (TextView) actionsView.findViewById(R.id.report_action);
+ reportButtonView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (onReportButtonClickListener != null) {
+ onReportButtonClickListener.onReportButtonClick(number);
+ }
+ }
+ });
+ }
+
+ bindActionButtons(actionListener, phoneNumberUtilsWrapper, callLogViewsHelper);
+ }
+
+ /**
+ * Binds text titles, click handlers and intents to the voicemail, details and callback action
+ * buttons.
+ */
+ private void bindActionButtons(
+ View.OnClickListener actionListener,
+ PhoneNumberUtilsWrapper phoneNumberUtilsWrapper,
+ CallLogListItemHelper callLogViewsHelper) {
+ boolean canPlaceCallToNumber =
+ PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation);
+
+ // Set return call intent, otherwise null.
+ if (canPlaceCallToNumber) {
+ boolean isVoicemailNumber =
+ phoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number);
+ if (isVoicemailNumber) {
+ // Make a general call to voicemail to ensure that if there are multiple accounts
+ // it does not call the voicemail number of a specific phone account.
+ callBackButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider());
+ } else {
+ // Sets the primary action to call the number.
+ callBackButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
+ }
+ callBackButtonView.setVisibility(View.VISIBLE);
+ callBackButtonView.setOnClickListener(actionListener);
+
+ final int titleId;
+ if (callType == Calls.VOICEMAIL_TYPE || callType == Calls.OUTGOING_TYPE) {
+ titleId = R.string.call_log_action_redial;
+ } else {
+ titleId = R.string.call_log_action_call_back;
+ }
+ callBackButtonView.setText(mContext.getString(titleId));
+ } else {
+ // Number is not callable, so hide button.
+ callBackButtonView.setTag(null);
+ callBackButtonView.setVisibility(View.GONE);
+ }
+
+ // If one of the calls had video capabilities, show the video call button.
+ if (CallUtil.isVideoEnabled(mContext) && canPlaceCallToNumber &&
+ phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
+ videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
+ videoCallButtonView.setVisibility(View.VISIBLE);
+ videoCallButtonView.setOnClickListener(actionListener);
+ } else {
+ videoCallButtonView.setTag(null);
+ videoCallButtonView.setVisibility(View.GONE);
+ }
+
+ // For voicemail calls, show the "VOICEMAIL" action button; hide otherwise.
+ if (callType == Calls.VOICEMAIL_TYPE) {
+ voicemailButtonView.setOnClickListener(actionListener);
+ voicemailButtonView.setTag(
+ IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
+ voicemailButtonView.setVisibility(View.VISIBLE);
+
+ detailsButtonView.setVisibility(View.GONE);
+ } else {
+ voicemailButtonView.setTag(null);
+ voicemailButtonView.setVisibility(View.GONE);
+
+ detailsButtonView.setOnClickListener(actionListener);
+ detailsButtonView.setTag(
+ IntentProvider.getCallDetailIntentProvider(rowId, callIds, null));
+
+ if (canBeReportedAsInvalid && !reported) {
+ reportButtonView.setVisibility(View.VISIBLE);
+ } else {
+ reportButtonView.setVisibility(View.GONE);
+ }
+ }
+
+ callLogViewsHelper.setActionContentDescriptions(this);
+ }
+
+ /**
+ * Expands or collapses the view containing the CALLBACK/REDIAL, VOICEMAIL and DETAILS action
+ * buttons.
+ *
+ * TODO: Reduce number of classes which need to be passed in to inflate the action view stub.
+ * 1) Instantiate them in this class, and store local references.
+ * 2) Set them on the CallLogListItemHelper and use it for inflation.
+ * 3) Implement a parent view for a call log list item, and store references in that class.
+ */
+ public void expandOrCollapseActions(
+ boolean isExpanded,
+ final CallLogAdapter.OnReportButtonClickListener onReportButtonClickListener,
+ View.OnClickListener actionListener,
+ PhoneNumberUtilsWrapper phoneNumberUtilsWrapper,
+ CallLogListItemHelper callLogViewsHelper) {
+ expandVoicemailTranscriptionView(isExpanded);
+
+ if (isExpanded) {
+ // Inflate the view stub if necessary, and wire up the event handlers.
+ inflateActionViewStub(onReportButtonClickListener, actionListener,
+ phoneNumberUtilsWrapper, callLogViewsHelper);
+
+ actionsView.setVisibility(View.VISIBLE);
+ actionsView.setAlpha(1.0f);
+ callLogEntryView.setBackgroundColor(mExpandedBackgroundColor);
+ callLogEntryView.setTranslationZ(mExpandedTranslationZ);
+ rootView.setTranslationZ(mExpandedTranslationZ); // WAR
+ } else {
+ // When recycling a view, it is possible the actionsView ViewStub was previously
+ // inflated so we should hide it in this case.
+ if (actionsView != null) {
+ actionsView.setVisibility(View.GONE);
+ }
+
+ callLogEntryView.setBackgroundColor(mCallLogBackgroundColor);
+ callLogEntryView.setTranslationZ(0);
+ rootView.setTranslationZ(0); // WAR
+ }
+ }
+
+ public void expandVoicemailTranscriptionView(boolean isExpanded) {
+ if (callType != Calls.VOICEMAIL_TYPE) {
+ return;
+ }
+
+ final TextView view = phoneCallDetailsViews.voicemailTranscriptionView;
+ if (TextUtils.isEmpty(view.getText())) {
+ return;
+ }
+ view.setMaxLines(isExpanded ? VOICEMAIL_TRANSCRIPTION_MAX_LINES : 1);
+ view.setSingleLine(!isExpanded);
+ }
+
+ public void setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName,
+ boolean isVoicemail, boolean isBusiness) {
+ quickContactView.assignContactUri(contactUri);
+ quickContactView.setOverlay(null);
+
+ int contactType = ContactPhotoManager.TYPE_DEFAULT;
+ if (isVoicemail) {
+ contactType = ContactPhotoManager.TYPE_VOICEMAIL;
+ } else if (isBusiness) {
+ contactType = ContactPhotoManager.TYPE_BUSINESS;
+ }
+
+ String lookupKey = null;
+ if (contactUri != null) {
+ lookupKey = ContactInfoHelper.getLookupKeyFromUri(contactUri);
+ }
+
+ DefaultImageRequest request = new DefaultImageRequest(
+ displayName, lookupKey, contactType, true /* isCircular */);
+
+ if (photoId == 0 && photoUri != null) {
+ ContactPhotoManager.getInstance(mContext).loadPhoto(quickContactView, photoUri,
+ mPhotoSize, false /* darkTheme */, true /* isCircular */, request);
+ } else {
+ ContactPhotoManager.getInstance(mContext).loadThumbnail(quickContactView, photoId,
+ false /* darkTheme */, true /* isCircular */, request);
+ }
+ }
+
@NeededForTesting
public static CallLogListItemViews createForTest(Context context) {
CallLogListItemViews views = new CallLogListItemViews(
+ context,
+ new View(context),
new QuickContactBadge(context),
new View(context),
PhoneCallDetailsViews.createForTest(context),
diff --git a/src/com/android/dialer/calllog/CallLogQuery.java b/src/com/android/dialer/calllog/CallLogQuery.java
index 0ae4cda33..2b43c2857 100644
--- a/src/com/android/dialer/calllog/CallLogQuery.java
+++ b/src/com/android/dialer/calllog/CallLogQuery.java
@@ -45,7 +45,8 @@ public final class CallLogQuery {
Calls.PHONE_ACCOUNT_ID, // 19
Calls.FEATURES, // 20
Calls.DATA_USAGE, // 21
- Calls.TRANSCRIPTION // 22
+ Calls.TRANSCRIPTION, // 22
+ Calls.CACHED_PHOTO_URI // 23
};
public static final int ID = 0;
@@ -71,4 +72,5 @@ public final class CallLogQuery {
public static final int FEATURES = 20;
public static final int DATA_USAGE = 21;
public static final int TRANSCRIPTION = 22;
+ public static final int CACHED_PHOTO_URI = 23;
}
diff --git a/src/com/android/dialer/calllog/ContactInfo.java b/src/com/android/dialer/calllog/ContactInfo.java
index 7b6014dd1..effe14270 100644
--- a/src/com/android/dialer/calllog/ContactInfo.java
+++ b/src/com/android/dialer/calllog/ContactInfo.java
@@ -27,6 +27,11 @@ import com.google.common.base.Objects;
*/
public class ContactInfo {
public Uri lookupUri;
+
+ /**
+ * Contact lookup key. Note this may be a lookup key for a corp contact, in which case
+ * "lookup by lookup key" doesn't work on the personal profile.
+ */
public String lookupKey;
public String name;
public int type;
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index 01749fc22..8e8aa3ce1 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -14,9 +14,12 @@
package com.android.dialer.calllog;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteFullException;
import android.net.Uri;
+import android.provider.CallLog.Calls;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
@@ -24,6 +27,7 @@ import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Log;
import com.android.contacts.common.util.Constants;
import com.android.contacts.common.util.PhoneNumberHelper;
@@ -41,6 +45,8 @@ import java.util.List;
* Utility class to look up the contact information for a given number.
*/
public class ContactInfoHelper {
+ private static final String TAG = ContactInfoHelper.class.getSimpleName();
+
private final Context mContext;
private final String mCurrentCountryIso;
@@ -64,6 +70,9 @@ public class ContactInfoHelper {
* @param countryIso the country associated with this number
*/
public ContactInfo lookupNumber(String number, String countryIso) {
+ if (TextUtils.isEmpty(number)) {
+ return null;
+ }
final ContactInfo info;
// Determine the contact info.
@@ -149,6 +158,9 @@ public class ContactInfoHelper {
* value.
*/
private ContactInfo lookupContactFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
final ContactInfo info;
Cursor phonesCursor =
mContext.getContentResolver().query(uri, PhoneQuery._PROJECTION, null, null, null);
@@ -193,10 +205,13 @@ public class ContactInfoHelper {
* If the lookup fails for some other reason, it returns null.
*/
private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
+ if (TextUtils.isEmpty(sipAddress)) {
+ return null;
+ }
final ContactInfo info;
// "contactNumber" is a SIP address, so use the PhoneLookup table with the SIP parameter.
- Uri.Builder uriBuilder = PhoneLookup.CONTENT_FILTER_URI.buildUpon();
+ Uri.Builder uriBuilder = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon();
uriBuilder.appendPath(Uri.encode(sipAddress));
uriBuilder.appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1");
return lookupContactFromUri(uriBuilder.build());
@@ -212,6 +227,9 @@ public class ContactInfoHelper {
* If the lookup fails for some other reason, it returns null.
*/
private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
+ if (TextUtils.isEmpty(number)) {
+ return null;
+ }
String contactNumber = number;
if (!TextUtils.isEmpty(countryIso)) {
// Normalize the number: this is needed because the PhoneLookup query below does not
@@ -224,7 +242,8 @@ public class ContactInfoHelper {
}
// The "contactNumber" is a regular phone number, so use the PhoneLookup table.
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(contactNumber));
+ Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+ Uri.encode(contactNumber));
ContactInfo info = lookupContactFromUri(uri);
if (info != null && info != ContactInfo.EMPTY) {
info.formattedNumber = formatPhoneNumber(number, null, countryIso);
@@ -265,6 +284,107 @@ public class ContactInfoHelper {
}
/**
+ * Stores differences between the updated contact info and the current call log contact info.
+ *
+ * @param number The number of the contact.
+ * @param countryIso The country associated with this number.
+ * @param updatedInfo The updated contact info.
+ * @param callLogInfo The call log entry's current contact info.
+ */
+ public void updateCallLogContactInfo(String number, String countryIso, ContactInfo updatedInfo,
+ ContactInfo callLogInfo) {
+ final ContentValues values = new ContentValues();
+ boolean needsUpdate = false;
+
+ if (callLogInfo != null) {
+ if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
+ values.put(Calls.CACHED_NAME, updatedInfo.name);
+ needsUpdate = true;
+ }
+
+ if (updatedInfo.type != callLogInfo.type) {
+ values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
+ values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
+ needsUpdate = true;
+ }
+
+ if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) {
+ values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
+ needsUpdate = true;
+ }
+
+ // Only replace the normalized number if the new updated normalized number isn't empty.
+ if (!TextUtils.isEmpty(updatedInfo.normalizedNumber) &&
+ !TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) {
+ values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) {
+ values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
+ needsUpdate = true;
+ }
+
+ if (updatedInfo.photoId != callLogInfo.photoId) {
+ values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
+ needsUpdate = true;
+ }
+
+ final Uri updatedPhotoUriContactsOnly =
+ UriUtils.nullForNonContactsUri(updatedInfo.photoUri);
+ if (!UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) {
+ values.put(Calls.CACHED_PHOTO_URI,
+ UriUtils.uriToString(updatedPhotoUriContactsOnly));
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) {
+ values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
+ needsUpdate = true;
+ }
+ } else {
+ // No previous values, store all of them.
+ values.put(Calls.CACHED_NAME, updatedInfo.name);
+ values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
+ values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
+ values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
+ values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
+ values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
+ values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
+ values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(
+ UriUtils.nullForNonContactsUri(updatedInfo.photoUri)));
+ values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
+ needsUpdate = true;
+ }
+
+ if (!needsUpdate) {
+ return;
+ }
+
+ try {
+ if (countryIso == null) {
+ mContext.getContentResolver().update(
+ 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);
+ }
+ }
+
+ /**
* Parses the given URI to determine the original lookup key of the contact.
*/
public static String getLookupKeyFromUri(Uri lookupUri) {
@@ -283,6 +403,29 @@ public class ContactInfoHelper {
}
/**
+ * Returns the contact information stored in an entry of the call log.
+ *
+ * @param c A cursor pointing to an entry in the call log.
+ */
+ public static ContactInfo getContactInfo(Cursor c) {
+ ContactInfo info = new ContactInfo();
+
+ info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI));
+ info.name = c.getString(CallLogQuery.CACHED_NAME);
+ info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
+ info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
+ String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER);
+ info.number = matchedNumber == null ? c.getString(CallLogQuery.NUMBER) : matchedNumber;
+ info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
+ info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
+ info.photoUri = UriUtils.nullForNonContactsUri(
+ UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_PHOTO_URI)));
+ info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
+
+ return info;
+ }
+
+ /**
* Given a contact's sourceType, return true if the contact is a business
*
* @param sourceType sourceType of the contact. This is usually populated by
diff --git a/src/com/android/dialer/calllog/GroupingListAdapter.java b/src/com/android/dialer/calllog/GroupingListAdapter.java
new file mode 100644
index 000000000..78955492e
--- /dev/null
+++ b/src/com/android/dialer/calllog/GroupingListAdapter.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.calllog;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.android.contacts.common.testing.NeededForTesting;
+
+/**
+ * Maintains a list that groups adjacent items sharing the same value of a "group-by" field.
+ *
+ * The list has three types of elements: stand-alone, group header and group child. Groups are
+ * collapsible and collapsed by default. This is used by the call log to group related entries.
+ */
+abstract class GroupingListAdapter extends BaseAdapter {
+
+ private static final int GROUP_METADATA_ARRAY_INITIAL_SIZE = 16;
+ private static final int GROUP_METADATA_ARRAY_INCREMENT = 128;
+ private static final long GROUP_OFFSET_MASK = 0x00000000FFFFFFFFL;
+ private static final long GROUP_SIZE_MASK = 0x7FFFFFFF00000000L;
+ private static final long EXPANDED_GROUP_MASK = 0x8000000000000000L;
+
+ public static final int ITEM_TYPE_STANDALONE = 0;
+ public static final int ITEM_TYPE_GROUP_HEADER = 1;
+ public static final int ITEM_TYPE_IN_GROUP = 2;
+
+ /**
+ * Information about a specific list item: is it a group, if so is it expanded.
+ * Otherwise, is it a stand-alone item or a group member.
+ */
+ protected static class PositionMetadata {
+ int itemType;
+ boolean isExpanded;
+ int cursorPosition;
+ int childCount;
+ private int groupPosition;
+ private int listPosition = -1;
+ }
+
+ private Context mContext;
+ private Cursor mCursor;
+
+ /**
+ * Count of list items.
+ */
+ private int mCount;
+
+ private int mRowIdColumnIndex;
+
+ /**
+ * Count of groups in the list.
+ */
+ private int mGroupCount;
+
+ /**
+ * Information about where these groups are located in the list, how large they are
+ * and whether they are expanded.
+ */
+ private long[] mGroupMetadata;
+
+ private SparseIntArray mPositionCache = new SparseIntArray();
+ private int mLastCachedListPosition;
+ private int mLastCachedCursorPosition;
+ private int mLastCachedGroup;
+
+ /**
+ * A reusable temporary instance of PositionMetadata
+ */
+ private PositionMetadata mPositionMetadata = new PositionMetadata();
+
+ protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) {
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onContentChanged();
+ }
+ };
+
+ protected DataSetObserver mDataSetObserver = new DataSetObserver() {
+
+ @Override
+ public void onChanged() {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ notifyDataSetInvalidated();
+ }
+ };
+
+ public GroupingListAdapter(Context context) {
+ mContext = context;
+ resetCache();
+ }
+
+ /**
+ * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for
+ * each of them.
+ */
+ protected abstract void addGroups(Cursor cursor);
+
+ protected abstract View newStandAloneView(Context context, ViewGroup parent);
+ protected abstract void bindStandAloneView(View view, Context context, Cursor cursor);
+
+ protected abstract View newGroupView(Context context, ViewGroup parent);
+ protected abstract void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+ boolean expanded);
+
+ protected abstract View newChildView(Context context, ViewGroup parent);
+ protected abstract void bindChildView(View view, Context context, Cursor cursor);
+
+ /**
+ * Cache should be reset whenever the cursor changes or groups are expanded or collapsed.
+ */
+ private void resetCache() {
+ mCount = -1;
+ mLastCachedListPosition = -1;
+ mLastCachedCursorPosition = -1;
+ mLastCachedGroup = -1;
+ mPositionMetadata.listPosition = -1;
+ mPositionCache.clear();
+ }
+
+ protected void onContentChanged() {
+ }
+
+ public void changeCursor(Cursor cursor) {
+ if (cursor == mCursor) {
+ return;
+ }
+
+ if (mCursor != null) {
+ mCursor.unregisterContentObserver(mChangeObserver);
+ mCursor.unregisterDataSetObserver(mDataSetObserver);
+ mCursor.close();
+ }
+ mCursor = cursor;
+ resetCache();
+ findGroups();
+
+ if (cursor != null) {
+ cursor.registerContentObserver(mChangeObserver);
+ cursor.registerDataSetObserver(mDataSetObserver);
+ mRowIdColumnIndex = cursor.getColumnIndexOrThrow("_id");
+ notifyDataSetChanged();
+ } else {
+ // notify the observers about the lack of a data set
+ notifyDataSetInvalidated();
+ }
+
+ }
+
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ /**
+ * Scans over the entire cursor looking for duplicate phone numbers that need
+ * to be collapsed.
+ */
+ private void findGroups() {
+ mGroupCount = 0;
+ mGroupMetadata = new long[GROUP_METADATA_ARRAY_INITIAL_SIZE];
+
+ if (mCursor == null) {
+ return;
+ }
+
+ addGroups(mCursor);
+ }
+
+ /**
+ * Records information about grouping in the list. Should be called by the overridden
+ * {@link #addGroups} method.
+ */
+ protected void addGroup(int cursorPosition, int size, boolean expanded) {
+ if (mGroupCount >= mGroupMetadata.length) {
+ int newSize = idealLongArraySize(
+ mGroupMetadata.length + GROUP_METADATA_ARRAY_INCREMENT);
+ long[] array = new long[newSize];
+ System.arraycopy(mGroupMetadata, 0, array, 0, mGroupCount);
+ mGroupMetadata = array;
+ }
+
+ long metadata = ((long)size << 32) | cursorPosition;
+ if (expanded) {
+ metadata |= EXPANDED_GROUP_MASK;
+ }
+ mGroupMetadata[mGroupCount++] = metadata;
+ }
+
+ // Copy/paste from ArrayUtils
+ private int idealLongArraySize(int need) {
+ return idealByteArraySize(need * 8) / 8;
+ }
+
+ // Copy/paste from ArrayUtils
+ private int idealByteArraySize(int need) {
+ for (int i = 4; i < 32; i++)
+ if (need <= (1 << i) - 12)
+ return (1 << i) - 12;
+
+ return need;
+ }
+
+ public int getCount() {
+ if (mCursor == null) {
+ return 0;
+ }
+
+ if (mCount != -1) {
+ return mCount;
+ }
+
+ int cursorPosition = 0;
+ int count = 0;
+ for (int i = 0; i < mGroupCount; i++) {
+ long metadata = mGroupMetadata[i];
+ int offset = (int)(metadata & GROUP_OFFSET_MASK);
+ boolean expanded = (metadata & EXPANDED_GROUP_MASK) != 0;
+ int size = (int)((metadata & GROUP_SIZE_MASK) >> 32);
+
+ count += (offset - cursorPosition);
+
+ if (expanded) {
+ count += size + 1;
+ } else {
+ count++;
+ }
+
+ cursorPosition = offset + size;
+ }
+
+ mCount = count + mCursor.getCount() - cursorPosition;
+ return mCount;
+ }
+
+ /**
+ * Figures out whether the item at the specified position represents a
+ * stand-alone element, a group or a group child. Also computes the
+ * corresponding cursor position.
+ */
+ public void obtainPositionMetadata(PositionMetadata metadata, int position) {
+
+ // If the description object already contains requested information, just return
+ if (metadata.listPosition == position) {
+ return;
+ }
+
+ int listPosition = 0;
+ int cursorPosition = 0;
+ int firstGroupToCheck = 0;
+
+ // Check cache for the supplied position. What we are looking for is
+ // the group descriptor immediately preceding the supplied position.
+ // Once we have that, we will be able to tell whether the position
+ // is the header of the group, a member of the group or a standalone item.
+ if (mLastCachedListPosition != -1) {
+ if (position <= mLastCachedListPosition) {
+
+ // Have SparceIntArray do a binary search for us.
+ int index = mPositionCache.indexOfKey(position);
+
+ // If we get back a positive number, the position corresponds to
+ // a group header.
+ if (index < 0) {
+
+ // We had a cache miss, but we did obtain valuable information anyway.
+ // The negative number will allow us to compute the location of
+ // the group header immediately preceding the supplied position.
+ index = ~index - 1;
+
+ if (index >= mPositionCache.size()) {
+ index--;
+ }
+ }
+
+ // A non-negative index gives us the position of the group header
+ // corresponding or preceding the position, so we can
+ // search for the group information at the supplied position
+ // starting with the cached group we just found
+ if (index >= 0) {
+ listPosition = mPositionCache.keyAt(index);
+ firstGroupToCheck = mPositionCache.valueAt(index);
+ long descriptor = mGroupMetadata[firstGroupToCheck];
+ cursorPosition = (int)(descriptor & GROUP_OFFSET_MASK);
+ }
+ } else {
+
+ // If we haven't examined groups beyond the supplied position,
+ // we will start where we left off previously
+ firstGroupToCheck = mLastCachedGroup;
+ listPosition = mLastCachedListPosition;
+ cursorPosition = mLastCachedCursorPosition;
+ }
+ }
+
+ for (int i = firstGroupToCheck; i < mGroupCount; i++) {
+ long group = mGroupMetadata[i];
+ int offset = (int)(group & GROUP_OFFSET_MASK);
+
+ // Move pointers to the beginning of the group
+ listPosition += (offset - cursorPosition);
+ cursorPosition = offset;
+
+ if (i > mLastCachedGroup) {
+ mPositionCache.append(listPosition, i);
+ mLastCachedListPosition = listPosition;
+ mLastCachedCursorPosition = cursorPosition;
+ mLastCachedGroup = i;
+ }
+
+ // Now we have several possibilities:
+ // A) The requested position precedes the group
+ if (position < listPosition) {
+ metadata.itemType = ITEM_TYPE_STANDALONE;
+ metadata.cursorPosition = cursorPosition - (listPosition - position);
+ return;
+ }
+
+ boolean expanded = (group & EXPANDED_GROUP_MASK) != 0;
+ int size = (int) ((group & GROUP_SIZE_MASK) >> 32);
+
+ // B) The requested position is a group header
+ if (position == listPosition) {
+ metadata.itemType = ITEM_TYPE_GROUP_HEADER;
+ metadata.groupPosition = i;
+ metadata.isExpanded = expanded;
+ metadata.childCount = size;
+ metadata.cursorPosition = offset;
+ return;
+ }
+
+ if (expanded) {
+ // C) The requested position is an element in the expanded group
+ if (position < listPosition + size + 1) {
+ metadata.itemType = ITEM_TYPE_IN_GROUP;
+ metadata.cursorPosition = cursorPosition + (position - listPosition) - 1;
+ return;
+ }
+
+ // D) The element is past the expanded group
+ listPosition += size + 1;
+ } else {
+
+ // E) The element is past the collapsed group
+ listPosition++;
+ }
+
+ // Move cursor past the group
+ cursorPosition += size;
+ }
+
+ // The required item is past the last group
+ metadata.itemType = ITEM_TYPE_STANDALONE;
+ metadata.cursorPosition = cursorPosition + (position - listPosition);
+ }
+
+ /**
+ * Returns true if the specified position in the list corresponds to a
+ * group header.
+ */
+ public boolean isGroupHeader(int position) {
+ obtainPositionMetadata(mPositionMetadata, position);
+ return mPositionMetadata.itemType == ITEM_TYPE_GROUP_HEADER;
+ }
+
+ /**
+ * Given a position of a groups header in the list, returns the size of
+ * the corresponding group.
+ */
+ public int getGroupSize(int position) {
+ obtainPositionMetadata(mPositionMetadata, position);
+ return mPositionMetadata.childCount;
+ }
+
+ /**
+ * Mark group as expanded if it is collapsed and vice versa.
+ */
+ @NeededForTesting
+ public void toggleGroup(int position) {
+ obtainPositionMetadata(mPositionMetadata, position);
+ if (mPositionMetadata.itemType != ITEM_TYPE_GROUP_HEADER) {
+ throw new IllegalArgumentException("Not a group at position " + position);
+ }
+
+ if (mPositionMetadata.isExpanded) {
+ mGroupMetadata[mPositionMetadata.groupPosition] &= ~EXPANDED_GROUP_MASK;
+ } else {
+ mGroupMetadata[mPositionMetadata.groupPosition] |= EXPANDED_GROUP_MASK;
+ }
+ resetCache();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 3;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ obtainPositionMetadata(mPositionMetadata, position);
+ return mPositionMetadata.itemType;
+ }
+
+ public Object getItem(int position) {
+ if (mCursor == null) {
+ return null;
+ }
+
+ obtainPositionMetadata(mPositionMetadata, position);
+ if (mCursor.moveToPosition(mPositionMetadata.cursorPosition)) {
+ return mCursor;
+ } else {
+ return null;
+ }
+ }
+
+ public long getItemId(int position) {
+ Object item = getItem(position);
+ if (item != null) {
+ return mCursor.getLong(mRowIdColumnIndex);
+ } else {
+ return -1;
+ }
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ obtainPositionMetadata(mPositionMetadata, position);
+ View view = convertView;
+ if (view == null) {
+ switch (mPositionMetadata.itemType) {
+ case ITEM_TYPE_STANDALONE:
+ view = newStandAloneView(mContext, parent);
+ break;
+ case ITEM_TYPE_GROUP_HEADER:
+ view = newGroupView(mContext, parent);
+ break;
+ case ITEM_TYPE_IN_GROUP:
+ view = newChildView(mContext, parent);
+ break;
+ }
+ }
+
+ mCursor.moveToPosition(mPositionMetadata.cursorPosition);
+ switch (mPositionMetadata.itemType) {
+ case ITEM_TYPE_STANDALONE:
+ bindStandAloneView(view, mContext, mCursor);
+ break;
+ case ITEM_TYPE_GROUP_HEADER:
+ bindGroupView(view, mContext, mCursor, mPositionMetadata.childCount,
+ mPositionMetadata.isExpanded);
+ break;
+ case ITEM_TYPE_IN_GROUP:
+ bindChildView(view, mContext, mCursor);
+ break;
+
+ }
+ return view;
+ }
+}
diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java
index 3084e2424..2bd3f2eef 100644
--- a/src/com/android/dialer/calllog/IntentProvider.java
+++ b/src/com/android/dialer/calllog/IntentProvider.java
@@ -16,15 +16,23 @@
package com.android.dialer.calllog;
+import android.content.ContentValues;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
import android.telecom.PhoneAccountHandle;
-import com.android.contacts.common.CallUtil;
+import com.android.contacts.common.model.Contact;
+import com.android.contacts.common.model.ContactLoader;
import com.android.dialer.CallDetailActivity;
+import com.android.dialer.DialtactsActivity;
+import com.android.dialer.PhoneCallDetails;
+import com.android.dialer.util.PrivilegedCallUtil;
+
+import java.util.ArrayList;
/**
* Used to create an intent to attach to an action in the call log.
@@ -46,7 +54,7 @@ public abstract class IntentProvider {
return new IntentProvider() {
@Override
public Intent getIntent(Context context) {
- return CallUtil.getCallIntent(number, accountHandle);
+ return PrivilegedCallUtil.getCallIntent(number, accountHandle);
}
};
}
@@ -60,7 +68,7 @@ public abstract class IntentProvider {
return new IntentProvider() {
@Override
public Intent getIntent(Context context) {
- return CallUtil.getVideoCallIntent(number, accountHandle);
+ return PrivilegedCallUtil.getVideoCallIntent(number, accountHandle);
}
};
}
@@ -69,7 +77,7 @@ public abstract class IntentProvider {
return new IntentProvider() {
@Override
public Intent getIntent(Context context) {
- return CallUtil.getVoicemailIntent();
+ return PrivilegedCallUtil.getVoicemailIntent();
}
};
}
@@ -124,4 +132,68 @@ public abstract class IntentProvider {
}
};
}
+
+ /**
+ * Retrieves an add contact intent for the given contact and phone call details.
+ *
+ * @param info The contact info.
+ * @param details The phone call details.
+ */
+ public static IntentProvider getAddContactIntentProvider(
+ final ContactInfo info, final PhoneCallDetails details) {
+ return new IntentProvider() {
+ @Override
+ public Intent getIntent(Context context) {
+ Contact contactToSave = null;
+
+ if (info.lookupUri != null) {
+ contactToSave = ContactLoader.parseEncodedContactEntity(info.lookupUri);
+ }
+
+ if (contactToSave != null) {
+ // Populate the intent with contact information stored in the lookup URI.
+ // Note: This code mirrors code in Contacts/QuickContactsActivity.
+ final Intent intent = 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);
+
+ return intent;
+ } else {
+ // If no lookup uri is provided, rely on the available phone number and name.
+ return DialtactsActivity.getAddToContactIntent(details.name,
+ details.number,
+ details.numberType);
+ }
+ }
+ };
+ }
}
diff --git a/src/com/android/dialer/calllog/PhoneNumberDisplayHelper.java b/src/com/android/dialer/calllog/PhoneNumberDisplayHelper.java
index 0dffd868a..c1a5abfe1 100644
--- a/src/com/android/dialer/calllog/PhoneNumberDisplayHelper.java
+++ b/src/com/android/dialer/calllog/PhoneNumberDisplayHelper.java
@@ -75,7 +75,6 @@ public class PhoneNumberDisplayHelper {
*/
public CharSequence getDisplayNumber(PhoneAccountHandle accountHandle, CharSequence number,
int presentation, CharSequence formattedNumber) {
-
final CharSequence displayName = getDisplayName(accountHandle, number, presentation);
if (!TextUtils.isEmpty(displayName)) {
return displayName;
diff --git a/src/com/android/dialer/contactinfo/ContactInfoCache.java b/src/com/android/dialer/contactinfo/ContactInfoCache.java
new file mode 100644
index 000000000..2bb0f1e95
--- /dev/null
+++ b/src/com/android/dialer/contactinfo/ContactInfoCache.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.contactinfo;
+
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.calllog.ContactInfoHelper;
+import com.android.dialer.util.ExpirableCache;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.LinkedList;
+
+/**
+ * This is a cache of contact details for the phone numbers in the c all log. The key is the
+ * phone number with the country in which teh call was placed or received. The content of the
+ * cache is expired (but not purged) whenever the application comes to the foreground.
+ *
+ * This cache queues request for information and queries for information on a background thread,
+ * so {@code start()} and {@code stop()} must be called to initiate or halt that thread's exeuction
+ * as needed.
+ *
+ * TODO: Explore whether there is a pattern to remove external dependencies for starting and
+ * stopping the query thread.
+ */
+public class ContactInfoCache {
+ public interface OnContactInfoChangedListener {
+ public void onContactInfoChanged();
+ }
+
+ /*
+ * Handles requests for contact name and number type.
+ */
+ private class QueryThread extends Thread {
+ private volatile boolean mDone = false;
+
+ public QueryThread() {
+ super("CallLogAdapter.QueryThread");
+ }
+
+ public void stopProcessing() {
+ mDone = true;
+ }
+
+ @Override
+ public void run() {
+ boolean needRedraw = false;
+ while (true) {
+ // Check if thread is finished, and if so return immediately.
+ if (mDone) return;
+
+ // Obtain next request, if any is available.
+ // Keep synchronized section small.
+ ContactInfoRequest req = null;
+ synchronized (mRequests) {
+ if (!mRequests.isEmpty()) {
+ req = mRequests.removeFirst();
+ }
+ }
+
+ if (req != null) {
+ // Process the request. If the lookup succeeds, schedule a redraw.
+ needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo);
+ } else {
+ // Throttle redraw rate by only sending them when there are
+ // more requests.
+ if (needRedraw) {
+ needRedraw = false;
+ mHandler.sendEmptyMessage(REDRAW);
+ }
+
+ // Wait until another request is available, or until this
+ // thread is no longer needed (as indicated by being
+ // interrupted).
+ try {
+ synchronized (mRequests) {
+ mRequests.wait(1000);
+ }
+ } catch (InterruptedException ie) {
+ // Ignore, and attempt to continue processing requests.
+ }
+ }
+ }
+ }
+ }
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case REDRAW:
+ mOnContactInfoChangedListener.onContactInfoChanged();
+ break;
+ case START_THREAD:
+ startRequestProcessing();
+ break;
+ }
+ }
+ };
+
+ private static final int REDRAW = 1;
+ private static final int START_THREAD = 2;
+
+ private static final int CONTACT_INFO_CACHE_SIZE = 100;
+ private static final int START_PROCESSING_REQUESTS_DELAY_MS = 1000;
+
+
+ /**
+ * List of requests to update contact details. Each request contains a phone number to look up,
+ * and the contact info currently stored in the call log for this number.
+ *
+ * The requests are added when displaying contacts and are processed by a background thread.
+ */
+ private final LinkedList<ContactInfoRequest> mRequests;
+
+ private ExpirableCache<NumberWithCountryIso, ContactInfo> mCache;
+
+ private ContactInfoHelper mContactInfoHelper;
+ private QueryThread mContactInfoQueryThread;
+ private OnContactInfoChangedListener mOnContactInfoChangedListener;
+
+ public ContactInfoCache(ContactInfoHelper contactInfoHelper,
+ OnContactInfoChangedListener onContactInfoChangedListener) {
+ mContactInfoHelper = contactInfoHelper;
+ mOnContactInfoChangedListener = onContactInfoChangedListener;
+
+ mRequests = new LinkedList<ContactInfoRequest>();
+ mCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
+ }
+
+ public ContactInfo getValue(String number, String countryIso, ContactInfo cachedContactInfo) {
+ NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
+ ExpirableCache.CachedValue<ContactInfo> cachedInfo =
+ mCache.getCachedValue(numberCountryIso);
+ ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
+ if (cachedInfo == null) {
+ mCache.put(numberCountryIso, ContactInfo.EMPTY);
+ // Use the cached contact info from the call log.
+ info = cachedContactInfo;
+ // The db request should happen on a non-UI thread.
+ // Request the contact details immediately since they are currently missing.
+ enqueueRequest(number, countryIso, cachedContactInfo, true);
+ // We will format the phone number when we make the background request.
+ } else {
+ if (cachedInfo.isExpired()) {
+ // The contact info is no longer up to date, we should request it. However, we
+ // do not need to request them immediately.
+ enqueueRequest(number, countryIso, cachedContactInfo, false);
+ } else if (!callLogInfoMatches(cachedContactInfo, info)) {
+ // The call log information does not match the one we have, look it up again.
+ // We could simply update the call log directly, but that needs to be done in a
+ // background thread, so it is easier to simply request a new lookup, which will, as
+ // a side-effect, update the call log.
+ enqueueRequest(number, countryIso, cachedContactInfo, false);
+ }
+
+ if (info == ContactInfo.EMPTY) {
+ // Use the cached contact info from the call log.
+ info = cachedContactInfo;
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Queries the appropriate content provider for the contact associated with the number.
+ *
+ * Upon completion it also updates the cache in the call log, if it is different from
+ * {@code callLogInfo}.
+ *
+ * The number might be either a SIP address or a phone number.
+ *
+ * It returns true if it updated the content of the cache and we should therefore tell the
+ * view to update its content.
+ */
+ private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) {
+ final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
+
+ if (info == null) {
+ // The lookup failed, just return without requesting to update the view.
+ return false;
+ }
+
+ // Check the existing entry in the cache: only if it has changed we should update the
+ // view.
+ NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
+ ContactInfo existingInfo = mCache.getPossiblyExpired(numberCountryIso);
+
+ final boolean isRemoteSource = info.sourceType != 0;
+
+ // Don't force redraw if existing info in the cache is equal to {@link ContactInfo#EMPTY}
+ // to avoid updating the data set for every new row that is scrolled into view.
+ // see (https://googleplex-android-review.git.corp.google.com/#/c/166680/)
+
+ // Exception: Photo uris for contacts from remote sources are not cached in the call log
+ // cache, so we have to force a redraw for these contacts regardless.
+ boolean updated = (existingInfo != ContactInfo.EMPTY || isRemoteSource) &&
+ !info.equals(existingInfo);
+
+ // Store the data in the cache so that the UI thread can use to display it. Store it
+ // even if it has not changed so that it is marked as not expired.
+ mCache.put(numberCountryIso, info);
+
+ // Update the call log even if the cache it is up-to-date: it is possible that the cache
+ // contains the value from a different call log entry.
+ mContactInfoHelper.updateCallLogContactInfo(number, countryIso, info, callLogInfo);
+ return updated;
+ }
+
+ /**
+ * After a delay, start the thread to begin processing requests. We perform lookups on a
+ * background thread, but this must be called to indicate the thread should be running.
+ */
+ public void start() {
+ // Schedule a thread-creation message if the thread hasn't been created yet, as an
+ // optimization to queue fewer messages.
+ if (mContactInfoQueryThread == null) {
+ // TODO: Check whether this delay before starting to process is necessary.
+ mHandler.sendEmptyMessageDelayed(START_THREAD, START_PROCESSING_REQUESTS_DELAY_MS);
+ }
+ }
+
+ /**
+ * Stops the thread and clears the queue of messages to process. This cleans up the thread
+ * for lookups so that it is not perpetually running.
+ */
+ public void stop() {
+ stopRequestProcessing();
+ }
+
+ /**
+ * Starts a background thread to process contact-lookup requests, unless one
+ * has already been started.
+ */
+ private synchronized void startRequestProcessing() {
+ // For unit-testing.
+ if (mRequestProcessingDisabled) return;
+
+ // If a thread is already started, don't start another.
+ if (mContactInfoQueryThread != null) {
+ return;
+ }
+
+ mContactInfoQueryThread = new QueryThread();
+ mContactInfoQueryThread.setPriority(Thread.MIN_PRIORITY);
+ mContactInfoQueryThread.start();
+ }
+
+ public void invalidate() {
+ mCache.expireAll();
+ stopRequestProcessing();
+ }
+
+ /**
+ * Stops the background thread that processes updates and cancels any
+ * pending requests to start it.
+ */
+ private synchronized void stopRequestProcessing() {
+ // Remove any pending requests to start the processing thread.
+ mHandler.removeMessages(START_THREAD);
+ if (mContactInfoQueryThread != null) {
+ // Stop the thread; we are finished with it.
+ mContactInfoQueryThread.stopProcessing();
+ mContactInfoQueryThread.interrupt();
+ mContactInfoQueryThread = null;
+ }
+ }
+
+ /**
+ * Enqueues a request to look up the contact details for the given phone number.
+ * <p>
+ * It also provides the current contact info stored in the call log for this number.
+ * <p>
+ * If the {@code immediate} parameter is true, it will start immediately the thread that looks
+ * up the contact information (if it has not been already started). Otherwise, it will be
+ * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}.
+ */
+ protected void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
+ boolean immediate) {
+ ContactInfoRequest request = new ContactInfoRequest(number, countryIso, callLogInfo);
+ synchronized (mRequests) {
+ if (!mRequests.contains(request)) {
+ mRequests.add(request);
+ mRequests.notifyAll();
+ }
+ }
+ if (immediate) {
+ startRequestProcessing();
+ }
+ }
+
+ /**
+ * Checks whether the contact info from the call log matches the one from the contacts db.
+ */
+ private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
+ // The call log only contains a subset of the fields in the contacts db.
+ // Only check those.
+ return TextUtils.equals(callLogInfo.name, info.name)
+ && callLogInfo.type == info.type
+ && TextUtils.equals(callLogInfo.label, info.label);
+ }
+
+ /**
+ * Can be set to true by tests to disable processing of requests.
+ */
+ @VisibleForTesting
+ private volatile boolean mRequestProcessingDisabled = false;
+
+ /**
+ * Sets whether processing of requests for contact details should be enabled.
+ *
+ * This method should be called in tests to disable such processing of requests when not
+ * needed.
+ */
+ @VisibleForTesting
+ public void disableRequestProcessingForTest() {
+ mRequestProcessingDisabled = true;
+ }
+
+ @VisibleForTesting
+ public void injectContactInfoForTest(
+ String number, String countryIso, ContactInfo contactInfo) {
+ NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
+ mCache.put(numberCountryIso, contactInfo);
+ }
+}
diff --git a/src/com/android/dialer/contactinfo/ContactInfoRequest.java b/src/com/android/dialer/contactinfo/ContactInfoRequest.java
new file mode 100644
index 000000000..ec5c1198e
--- /dev/null
+++ b/src/com/android/dialer/contactinfo/ContactInfoRequest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.contactinfo;
+
+import android.text.TextUtils;
+
+import com.android.dialer.calllog.ContactInfo;
+import com.google.common.base.Objects;
+
+/**
+ * A request for contact details for the given number, used by the ContactInfoCache.
+ */
+public final class ContactInfoRequest {
+ /** The number to look-up. */
+ public final String number;
+ /** The country in which a call to or from this number was placed or received. */
+ public final String countryIso;
+ /** The cached contact information stored in the call log. */
+ public final ContactInfo callLogInfo;
+
+ public ContactInfoRequest(String number, String countryIso, ContactInfo callLogInfo) {
+ this.number = number;
+ this.countryIso = countryIso;
+ this.callLogInfo = callLogInfo;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (!(obj instanceof ContactInfoRequest)) return false;
+
+ ContactInfoRequest other = (ContactInfoRequest) obj;
+
+ if (!TextUtils.equals(number, other.number)) return false;
+ if (!TextUtils.equals(countryIso, other.countryIso)) return false;
+ if (!Objects.equal(callLogInfo, other.callLogInfo)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((callLogInfo == null) ? 0 : callLogInfo.hashCode());
+ result = prime * result + ((countryIso == null) ? 0 : countryIso.hashCode());
+ result = prime * result + ((number == null) ? 0 : number.hashCode());
+ return result;
+ }
+}
diff --git a/src/com/android/dialer/contactinfo/NumberWithCountryIso.java b/src/com/android/dialer/contactinfo/NumberWithCountryIso.java
new file mode 100644
index 000000000..1383fb7e9
--- /dev/null
+++ b/src/com/android/dialer/contactinfo/NumberWithCountryIso.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.contactinfo;
+
+import android.text.TextUtils;
+
+/**
+ * Stores a phone number of a call with the country code where it originally occurred. This object
+ * is used as a key in the {@code ContactInfoCache}.
+ *
+ * The country does not necessarily specify the country of the phone number itself, but rather
+ * it is the country in which the user was in when the call was placed or received.
+ */
+public final class NumberWithCountryIso {
+ public final String number;
+ public final String countryIso;
+
+ public NumberWithCountryIso(String number, String countryIso) {
+ this.number = number;
+ this.countryIso = countryIso;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) return false;
+ if (!(o instanceof NumberWithCountryIso)) return false;
+ NumberWithCountryIso other = (NumberWithCountryIso) o;
+ return TextUtils.equals(number, other.number)
+ && TextUtils.equals(countryIso, other.countryIso);
+ }
+
+ @Override
+ public int hashCode() {
+ int numberHashCode = number == null ? 0 : number.hashCode();
+ int countryHashCode = countryIso == null ? 0 : countryIso.hashCode();
+
+ return numberHashCode ^ countryHashCode;
+ }
+}
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index 511c2a7bc..2177878c6 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -93,6 +93,9 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper {
static final String PROPERTIES = "properties";
}
+ public static final Uri SMART_DIAL_UPDATED_URI =
+ Uri.parse("content://com.android.dialer/smart_dial_updated");
+
public interface SmartDialDbColumns {
static final String _ID = "id";
static final String DATA_ID = "data_id";
@@ -893,6 +896,9 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper {
final SharedPreferences.Editor editor = databaseLastUpdateSharedPref.edit();
editor.putLong(LAST_UPDATED_MILLIS, currentMillis);
editor.commit();
+
+ // Notify content observers that smart dial database has been updated.
+ mContext.getContentResolver().notifyChange(SMART_DIAL_UPDATED_URI, null, false);
}
}
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index f1112f175..a910f7a1e 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -36,6 +36,7 @@ import android.media.AudioManager;
import android.media.ToneGenerator;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Trace;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.provider.Contacts.PhonesColumns;
@@ -44,13 +45,10 @@ import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
-import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.Editable;
-import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -70,7 +68,6 @@ import android.widget.PopupMenu;
import android.widget.RelativeLayout;
import android.widget.TextView;
-import com.android.contacts.common.CallUtil;
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.GeoUtil;
import com.android.contacts.common.util.PhoneNumberFormatter;
@@ -81,6 +78,7 @@ import com.android.dialer.NeededForReflection;
import com.android.dialer.R;
import com.android.dialer.SpecialCharSequenceMgr;
import com.android.dialer.calllog.PhoneAccountUtils;
+import com.android.dialer.util.PrivilegedCallUtil;
import com.android.dialer.util.DialerUtils;
import com.android.phone.common.CallLogAsync;
import com.android.phone.common.HapticFeedback;
@@ -102,7 +100,7 @@ public class DialpadFragment extends Fragment
AdapterView.OnItemClickListener, TextWatcher,
PopupMenu.OnMenuItemClickListener,
DialpadKeyButton.OnPressedListener {
- private static final String TAG = DialpadFragment.class.getSimpleName();
+ private static final String TAG = "DialpadFragment";
/**
* LinearLayout with getter and setter methods for the translationY property using floats,
@@ -260,8 +258,6 @@ public class DialpadFragment extends Fragment
private boolean mFirstLaunch = false;
private boolean mAnimate = false;
- private ComponentName mSmsPackageComponentName;
-
private static final String PREF_DIGITS_FILLED_BY_INTENT = "pref_digits_filled_by_intent";
private TelephonyManager getTelephonyManager() {
@@ -315,8 +311,11 @@ public class DialpadFragment extends Fragment
@Override
public void onCreate(Bundle state) {
+ Trace.beginSection(TAG + " onCreate");
super.onCreate(state);
- mFirstLaunch = true;
+
+ mFirstLaunch = state == null;
+
mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
try {
@@ -341,15 +340,21 @@ public class DialpadFragment extends Fragment
mCallStateReceiver = new CallStateReceiver();
((Context) getActivity()).registerReceiver(mCallStateReceiver, callStateIntentFilter);
}
+ Trace.endSection();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ Trace.beginSection(TAG + " onCreateView");
+ Trace.beginSection(TAG + " inflate view");
final View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container,
false);
+ Trace.endSection();
+ Trace.beginSection(TAG + " buildLayer");
fragmentView.buildLayer();
+ Trace.endSection();
- Resources r = getResources();
+ Trace.beginSection(TAG + " setup views");
mDialpadView = (DialpadView) fragmentView.findViewById(R.id.dialpad_view);
mDialpadView.setCanDigitsBeEdited(true);
@@ -399,7 +404,8 @@ public class DialpadFragment extends Fragment
floatingActionButton.setOnClickListener(this);
mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
floatingActionButtonContainer, floatingActionButton);
-
+ Trace.endSection();
+ Trace.endSection();
return fragmentView;
}
@@ -582,6 +588,7 @@ public class DialpadFragment extends Fragment
@Override
public void onStart() {
+ Trace.beginSection(TAG + " onStart");
super.onStart();
// if the mToneGenerator creation fails, just continue without it. It is
// a local audio signal, and is not as important as the dtmf tone itself.
@@ -600,10 +607,12 @@ public class DialpadFragment extends Fragment
if (total > 50) {
Log.i(TAG, "Time for ToneGenerator creation: " + total);
}
+ Trace.endSection();
};
@Override
public void onResume() {
+ Trace.beginSection(TAG + " onResume");
super.onResume();
final DialtactsActivity activity = (DialtactsActivity) getActivity();
@@ -641,8 +650,6 @@ public class DialpadFragment extends Fragment
showDialpadChooser(false);
}
- mFirstLaunch = false;
-
stopWatch.lap("hnt");
updateDeleteButtonEnabledState();
@@ -651,8 +658,6 @@ public class DialpadFragment extends Fragment
stopWatch.stopAndLog(TAG, 50);
- 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.
@@ -661,6 +666,15 @@ public class DialpadFragment extends Fragment
mOverflowMenuButton.setOnTouchListener(mOverflowPopupMenu.getDragToOpenListener());
mOverflowMenuButton.setOnClickListener(this);
mOverflowMenuButton.setVisibility(isDigitsEmpty() ? View.INVISIBLE : View.VISIBLE);
+
+ if (mFirstLaunch) {
+ // The onHiddenChanged callback does not get called the first time the fragment is
+ // attached, so call it ourselves here.
+ onHiddenChanged(false);
+ }
+
+ mFirstLaunch = false;
+ Trace.endSection();
}
@Override
@@ -860,8 +874,6 @@ public class DialpadFragment extends Fragment
@Override
public void show() {
final Menu menu = getMenu();
- final MenuItem sendMessage = menu.findItem(R.id.menu_send_message);
- sendMessage.setVisible(mSmsPackageComponentName != null);
boolean enable = !isDigitsEmpty();
for (int i = 0; i < menu.size(); i++) {
@@ -988,7 +1000,8 @@ public class DialpadFragment extends Fragment
}
public void callVoicemail() {
- DialerUtils.startActivityWithErrorToast(getActivity(), CallUtil.getVoicemailIntent());
+ DialerUtils.startActivityWithErrorToast(getActivity(), PrivilegedCallUtil
+ .getVoicemailIntent());
hideAndClearDialpad(false);
}
@@ -1084,7 +1097,7 @@ public class DialpadFragment extends Fragment
// Clear the digits just in case.
clearDialpad();
} else {
- final Intent intent = CallUtil.getCallIntent(number,
+ final Intent intent = PrivilegedCallUtil.getCallIntent(number,
(getActivity() instanceof DialtactsActivity ?
((DialtactsActivity) getActivity()).getCallOrigin() : null));
DialerUtils.startActivityWithErrorToast(getActivity(), intent);
@@ -1430,26 +1443,12 @@ public class DialpadFragment extends Fragment
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
- case R.id.menu_add_contact: {
- final CharSequence digits = mDigits.getText();
- DialerUtils.startActivityWithErrorToast(getActivity(),
- DialtactsActivity.getAddNumberToContactIntent(digits));
- return true;
- }
case R.id.menu_2s_pause:
updateDialString(PAUSE);
return true;
case R.id.menu_add_wait:
updateDialString(WAIT);
return true;
- case R.id.menu_send_message: {
- final CharSequence digits = mDigits.getText();
- final Intent smsIntent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts(ContactsUtils.SCHEME_SMSTO, digits.toString(), null));
- smsIntent.setComponent(mSmsPackageComponentName);
- DialerUtils.startActivityWithErrorToast(getActivity(), smsIntent);
- return true;
- }
default:
return false;
}
@@ -1614,7 +1613,7 @@ public class DialpadFragment extends Fragment
}
private Intent newFlashIntent() {
- final Intent intent = CallUtil.getCallIntent(EMPTY_NUMBER);
+ final Intent intent = PrivilegedCallUtil.getCallIntent(EMPTY_NUMBER);
intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
return intent;
}
diff --git a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
index d01776c61..372692eae 100644
--- a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
+++ b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
@@ -18,8 +18,10 @@ package com.android.dialer.dialpad;
import android.content.AsyncTaskLoader;
import android.content.Context;
+import android.content.Loader.ForceLoadContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.net.Uri;
import android.util.Log;
import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
@@ -44,6 +46,8 @@ public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> {
private String mQuery;
private SmartDialNameMatcher mNameMatcher;
+ private ForceLoadContentObserver mObserver;
+
public SmartDialCursorLoader(Context context) {
super(context);
mContext = context;
@@ -110,6 +114,12 @@ public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> {
Cursor oldCursor = mCursor;
mCursor = cursor;
+ if (mObserver == null) {
+ mObserver = new ForceLoadContentObserver();
+ mContext.getContentResolver().registerContentObserver(
+ DialerDatabaseHelper.SMART_DIAL_UPDATED_URI, true, mObserver);
+ }
+
if (isStarted()) {
/** If the Loader is in a started state, deliver the results to the client. */
super.deliverResult(cursor);
@@ -144,6 +154,11 @@ public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> {
/** Ensure the loader has been stopped. */
onStopLoading();
+ if (mObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+
/** Release all previously saved query results. */
if (mCursor != null) {
releaseResources(mCursor);
@@ -155,6 +170,11 @@ public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> {
public void onCanceled(Cursor cursor) {
super.onCanceled(cursor);
+ if (mObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+
/** The load has been canceled, so we should release the resources associated with 'data'.*/
releaseResources(cursor);
}
diff --git a/src/com/android/dialer/interactions/PhoneNumberInteraction.java b/src/com/android/dialer/interactions/PhoneNumberInteraction.java
index de217ce7b..78c468bbc 100644
--- a/src/com/android/dialer/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/dialer/interactions/PhoneNumberInteraction.java
@@ -45,7 +45,6 @@ import android.widget.CheckBox;
import android.widget.ListAdapter;
import android.widget.TextView;
-import com.android.contacts.common.CallUtil;
import com.android.contacts.common.Collapser;
import com.android.contacts.common.Collapser.Collapsible;
import com.android.contacts.common.MoreContactUtils;
@@ -53,6 +52,7 @@ import com.android.contacts.common.activity.TransactionSafeActivity;
import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.dialer.R;
import com.android.dialer.contact.ContactUpdateService;
+import com.android.dialer.util.PrivilegedCallUtil;
import com.android.dialer.util.DialerUtils;
import com.google.common.annotations.VisibleForTesting;
@@ -322,7 +322,7 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener<Cursor> {
Intent.ACTION_SENDTO, Uri.fromParts("sms", phoneNumber, null));
break;
default:
- intent = CallUtil.getCallIntent(phoneNumber, callOrigin);
+ intent = PrivilegedCallUtil.getCallIntent(phoneNumber, callOrigin);
break;
}
DialerUtils.startActivityWithErrorToast(context, intent);
diff --git a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
index 6d74cd0f7..960a31bc3 100644
--- a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
+++ b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
@@ -30,6 +30,8 @@ import android.text.TextUtils;
/**
* This broadcast receiver is used to listen to outgoing calls and undemote formerly demoted
* contacts if a phone call is made to a phone number belonging to that contact.
+ *
+ * NOTE This doesn't work for corp contacts.
*/
public class UndemoteOutgoingCallReceiver extends BroadcastReceiver {
diff --git a/src/com/android/dialer/list/AllContactsFragment.java b/src/com/android/dialer/list/AllContactsFragment.java
index deabb80b9..94efc4869 100644
--- a/src/com/android/dialer/list/AllContactsFragment.java
+++ b/src/com/android/dialer/list/AllContactsFragment.java
@@ -18,6 +18,7 @@ package com.android.dialer.list;
import android.database.Cursor;
import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.QuickContact;
import android.view.LayoutInflater;
import android.view.View;
@@ -83,7 +84,8 @@ public class AllContactsFragment extends ContactEntryListFragment<ContactEntryLi
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Uri uri = (Uri) view.getTag();
if (uri != null) {
- QuickContact.showQuickContact(getActivity(), view, uri, QuickContact.MODE_LARGE, null);
+ QuickContact.showQuickContact(getContext(), view, uri, null,
+ Phone.CONTENT_ITEM_TYPE);
}
}
diff --git a/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java b/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java
index da4f4a4c9..82daab5aa 100644
--- a/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java
+++ b/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java
@@ -27,10 +27,12 @@ public class DialerPhoneNumberListAdapter extends PhoneNumberListAdapter {
public final static int SHORTCUT_INVALID = -1;
public final static int SHORTCUT_DIRECT_CALL = 0;
- public final static int SHORTCUT_ADD_NUMBER_TO_CONTACTS = 1;
- public final static int SHORTCUT_MAKE_VIDEO_CALL = 2;
+ public final static int SHORTCUT_CREATE_NEW_CONTACT = 1;
+ public final static int SHORTCUT_ADD_TO_EXISTING_CONTACT = 2;
+ public final static int SHORTCUT_SEND_SMS_MESSAGE = 3;
+ public final static int SHORTCUT_MAKE_VIDEO_CALL = 4;
- public final static int SHORTCUT_COUNT = 3;
+ public final static int SHORTCUT_COUNT = 5;
private final boolean[] mShortcutEnabled = new boolean[SHORTCUT_COUNT];
@@ -142,10 +144,18 @@ public class DialerPhoneNumberListAdapter extends PhoneNumberListAdapter {
text = resources.getString(R.string.search_shortcut_call_number, number);
drawableId = R.drawable.ic_search_phone;
break;
- case SHORTCUT_ADD_NUMBER_TO_CONTACTS:
- text = resources.getString(R.string.search_shortcut_add_to_contacts);
+ case SHORTCUT_CREATE_NEW_CONTACT:
+ text = resources.getString(R.string.search_shortcut_create_new_contact);
drawableId = R.drawable.ic_search_add_contact;
break;
+ case SHORTCUT_ADD_TO_EXISTING_CONTACT:
+ text = resources.getString(R.string.search_shortcut_add_to_existing_contact);
+ drawableId = R.drawable.ic_person_24dp;
+ break;
+ case SHORTCUT_SEND_SMS_MESSAGE:
+ text = resources.getString(R.string.search_shortcut_send_sms_message);
+ drawableId = R.drawable.ic_textsms_24dp;
+ break;
case SHORTCUT_MAKE_VIDEO_CALL:
text = resources.getString(R.string.search_shortcut_make_video_call);
drawableId = R.drawable.ic_videocam;
@@ -153,7 +163,7 @@ public class DialerPhoneNumberListAdapter extends PhoneNumberListAdapter {
default:
throw new IllegalArgumentException("Invalid shortcut type");
}
- v.setDrawableResource(R.drawable.search_shortcut_background, drawableId);
+ v.setDrawableResource(drawableId);
v.setDisplayName(text);
v.setPhotoPosition(super.getPhotoPosition());
v.setAdjustSelectionBoundsEnabled(false);
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index af82e4075..f22a5d19c 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -8,6 +8,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
+import android.os.Trace;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
@@ -23,16 +24,11 @@ import com.android.contacts.common.list.ViewPagerTabs;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.DialtactsActivity;
import com.android.dialer.R;
-import com.android.dialer.calllog.CallLogAdapter;
import com.android.dialer.calllog.CallLogFragment;
-import com.android.dialer.calllog.CallLogQuery;
import com.android.dialer.calllog.CallLogQueryHandler;
import com.android.dialer.calllog.ContactInfoHelper;
-import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.widget.ActionBarController;
-import com.android.dialer.widget.OverlappingPaneLayout;
-import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks;
import com.android.dialerbind.ObjectFactory;
import java.util.ArrayList;
@@ -42,11 +38,10 @@ import java.util.ArrayList;
*
* Contains a ViewPager that contains various contact lists like the Speed Dial list and the
* All Contacts list. This will also eventually contain the logic that allows sliding the
- * ViewPager containing the lists up above the shortcut cards and pin it against the top of the
+ * ViewPager containing the lists up above the search bar and pin it against the top of the
* screen.
*/
-public class ListsFragment extends Fragment implements CallLogQueryHandler.Listener,
- CallLogAdapter.CallFetcher, ViewPager.OnPageChangeListener {
+public class ListsFragment extends Fragment implements ViewPager.OnPageChangeListener {
private static final boolean DEBUG = DialtactsActivity.DEBUG;
private static final String TAG = "ListsFragment";
@@ -64,9 +59,6 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE =
"key_last_dismissed_call_shortcut_date";
- public static final float REMOVE_VIEW_SHOWN_ALPHA = 0.5f;
- public static final float REMOVE_VIEW_HIDDEN_ALPHA = 1;
-
public interface HostInterface {
public void showCallHistory();
public ActionBarController getActionBarController();
@@ -76,7 +68,6 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
private ViewPager mViewPager;
private ViewPagerTabs mViewPagerTabs;
private ViewPagerAdapter mViewPagerAdapter;
- private ListView mShortcutCardsListView;
private RemoveView mRemoveView;
private View mRemoveViewContent;
private SpeedDialFragment mSpeedDialFragment;
@@ -86,13 +77,7 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
new ArrayList<OnPageChangeListener>();
private String[] mTabTitles;
-
- private ShortcutCardsAdapter mMergedAdapter;
- private CallLogAdapter mCallLogAdapter;
- private CallLogQueryHandler mCallLogQueryHandler;
- private OverlappingPaneLayout mOverlappingPaneLayout;
-
- private boolean mIsPanelOpen = true;
+ private int[] mTabIcons;
/**
* Call shortcuts older than this date (persisted in shared preferences) will not show up in
@@ -105,80 +90,6 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
*/
private long mCurrentCallShortcutDate = 0;
- private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() {
- @Override
- public void onPanelSlide(View panel, float slideOffset) {
- // For every 1 percent that the panel is slid upwards, clip 1 percent off the top
- // edge of the shortcut card, to achieve the animated effect of the shortcut card
- // being pushed out of view when the panel is slid upwards. slideOffset is 1 when
- // the shortcut card is fully exposed, and 0 when completely hidden.
- float ratioCardHidden = (1 - slideOffset);
- if (mShortcutCardsListView.getChildCount() > 0) {
- final SwipeableShortcutCard v =
- (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0);
- v.clipCard(ratioCardHidden);
- }
-
- if (mActionBar != null) {
- // Amount of available space that is not being hidden by the bottom pane
- final int topPaneHeight = (int) (slideOffset * mShortcutCardsListView.getHeight());
-
- final int availableActionBarHeight =
- Math.min(mActionBar.getHeight(), topPaneHeight);
- final ActionBarController controller =
- ((HostInterface) getActivity()).getActionBarController();
- controller.setHideOffset(mActionBar.getHeight() - availableActionBarHeight);
-
- if (!mActionBar.isShowing()) {
- mActionBar.show();
- }
- }
- }
-
- @Override
- public void onPanelOpened(View panel) {
- if (DEBUG) {
- Log.d(TAG, "onPanelOpened");
- }
- mIsPanelOpen = true;
- }
-
- @Override
- public void onPanelClosed(View panel) {
- if (DEBUG) {
- Log.d(TAG, "onPanelClosed");
- }
- mIsPanelOpen = false;
- }
-
- @Override
- public void onPanelFlingReachesEdge(int velocityY) {
- if (getCurrentListView() != null) {
- getCurrentListView().fling(velocityY);
- }
- }
-
- @Override
- public boolean isScrollableChildUnscrolled() {
- final AbsListView listView = getCurrentListView();
- return listView != null && (listView.getChildCount() == 0
- || listView.getChildAt(0).getTop() == listView.getPaddingTop());
- }
- };
-
- private AbsListView getCurrentListView() {
- final int position = mViewPager.getCurrentItem();
- switch (getRtlPosition(position)) {
- case TAB_INDEX_SPEED_DIAL:
- return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView();
- case TAB_INDEX_RECENTS:
- return mRecentsFragment == null ? null : mRecentsFragment.getListView();
- case TAB_INDEX_ALL_CONTACTS:
- return mAllContactsFragment == null ? null : mAllContactsFragment.getListView();
- }
- throw new IllegalStateException("No fragment at position " + position);
- }
-
public class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
@@ -198,7 +109,6 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
case TAB_INDEX_RECENTS:
mRecentsFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL,
MAX_RECENTS_ENTRIES, System.currentTimeMillis() - OLDEST_RECENTS_DATE);
- mRecentsFragment.setHasFooterView(true);
return mRecentsFragment;
case TAB_INDEX_ALL_CONTACTS:
mAllContactsFragment = new AllContactsFragment();
@@ -237,49 +147,38 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
@Override
public void onCreate(Bundle savedInstanceState) {
+ Trace.beginSection(TAG + " onCreate");
super.onCreate(savedInstanceState);
- mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
- this, 1);
+ Trace.beginSection(TAG + " getCurrentCountryIso");
final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
- mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
- new ContactInfoHelper(getActivity(), currentCountryIso), null, null, false);
+ Trace.endSection();
- mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter);
+ Trace.endSection();
}
@Override
public void onResume() {
+ Trace.beginSection(TAG + " onResume");
super.onResume();
final SharedPreferences prefs = getActivity().getSharedPreferences(
DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0);
mActionBar = getActivity().getActionBar();
- fetchCalls();
- mCallLogAdapter.setLoading(true);
if (getUserVisibleHint()) {
sendScreenViewForPosition(mViewPager.getCurrentItem());
}
- }
-
- @Override
- public void onPause() {
- // Wipe the cache to refresh the call shortcut item. This is not that expensive because
- // it only contains one item.
- mCallLogAdapter.invalidateCache();
- super.onPause();
- }
-
- @Override
- public void onDestroy() {
- mCallLogAdapter.stopRequestProcessing();
- super.onDestroy();
+ Trace.endSection();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ Trace.beginSection(TAG + " onCreateView");
+ Trace.beginSection(TAG + " inflate view");
final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
+ Trace.endSection();
+ Trace.beginSection(TAG + " setup views");
mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
mViewPager.setAdapter(mViewPagerAdapter);
@@ -292,61 +191,24 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
mTabTitles[TAB_INDEX_RECENTS] = getResources().getString(R.string.tab_recents);
mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts);
+ mTabIcons = new int[TAB_INDEX_COUNT];
+ mTabIcons[TAB_INDEX_SPEED_DIAL] = R.drawable.tab_speed_dial;
+ mTabIcons[TAB_INDEX_RECENTS] = R.drawable.tab_recents;
+ mTabIcons[TAB_INDEX_ALL_CONTACTS] = R.drawable.tab_contacts;
+
mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
+ mViewPagerTabs.setTabIcons(mTabIcons);
mViewPagerTabs.setViewPager(mViewPager);
addOnPageChangeListener(mViewPagerTabs);
- mShortcutCardsListView = (ListView) parentView.findViewById(R.id.shortcut_card_list);
- mShortcutCardsListView.setAdapter(mMergedAdapter);
-
mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view);
mRemoveViewContent = parentView.findViewById(R.id.remove_view_content);
- setupPaneLayout((OverlappingPaneLayout) parentView);
- mOverlappingPaneLayout = (OverlappingPaneLayout) parentView;
-
+ Trace.endSection();
+ Trace.endSection();
return parentView;
}
- @Override
- public void onVoicemailStatusFetched(Cursor statusCursor) {
- // no-op
- }
-
- @Override
- public boolean onCallsFetched(Cursor cursor) {
- mCallLogAdapter.setLoading(false);
-
- // Save the date of the most recent call log item
- if (cursor != null && cursor.moveToFirst()) {
- mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE);
- }
-
- 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;
- }
-
- @Override
- public void fetchCalls() {
- mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate);
- }
-
- public void dismissShortcut(View view) {
- mLastCallShortcutDate = mCurrentCallShortcutDate;
- final SharedPreferences prefs = view.getContext().getSharedPreferences(
- DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
- prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate)
- .apply();
- fetchCalls();
- }
-
public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
mOnPageChangeListeners.add(onPageChangeListener);
@@ -383,36 +245,11 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste
mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
mRemoveView.setAlpha(show ? 0 : 1);
mRemoveView.animate().alpha(show ? 1 : 0).start();
-
- if (mShortcutCardsListView.getChildCount() > 0) {
- View v = mShortcutCardsListView.getChildAt(0);
- v.animate().withLayer()
- .alpha(show ? REMOVE_VIEW_SHOWN_ALPHA : REMOVE_VIEW_HIDDEN_ALPHA)
- .start();
- }
}
public boolean shouldShowActionBar() {
- return mIsPanelOpen && mActionBar != null;
- }
-
- public boolean isPaneOpen() {
- return mIsPanelOpen;
- }
-
- private void setupPaneLayout(OverlappingPaneLayout paneLayout) {
- // TODO: Remove the notion of a capturable view. The entire view be slideable, once
- // the framework better supports nested scrolling.
- paneLayout.setCapturableView(mViewPagerTabs);
- paneLayout.openPane();
- paneLayout.setPanelSlideCallbacks(mPanelSlideCallbacks);
- paneLayout.setIntermediatePinnedOffset(
- ((HostInterface) getActivity()).getActionBarController().getActionBarHeight());
-
- LayoutTransition transition = paneLayout.getLayoutTransition();
- // Turns on animations for all types of layout changes so that they occur for
- // height changes.
- transition.enableTransitionType(LayoutTransition.CHANGING);
+ // TODO: Update this based on scroll state.
+ return mActionBar != null;
}
public SpeedDialFragment getSpeedDialFragment() {
diff --git a/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java b/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java
index 5f8877616..05780c66a 100644
--- a/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java
@@ -17,6 +17,7 @@
package com.android.dialer.list;
import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.QuickContact;
import android.util.AttributeSet;
import android.view.View;
@@ -63,7 +64,7 @@ public class PhoneFavoriteSquareTileView extends PhoneFavoriteTileView {
private void launchQuickContact() {
QuickContact.showQuickContact(getContext(), PhoneFavoriteSquareTileView.this,
- getLookupUri(), QuickContact.MODE_LARGE, null);
+ getLookupUri(), null, Phone.CONTENT_ITEM_TYPE);
}
@Override
diff --git a/src/com/android/dialer/list/RegularSearchListAdapter.java b/src/com/android/dialer/list/RegularSearchListAdapter.java
index f1f2ae039..3c55bc012 100644
--- a/src/com/android/dialer/list/RegularSearchListAdapter.java
+++ b/src/com/android/dialer/list/RegularSearchListAdapter.java
@@ -18,12 +18,10 @@ package com.android.dialer.list;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.provider.ContactsContract;
import android.text.TextUtils;
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.list.DirectoryPartition;
-import com.android.contacts.common.list.PhoneNumberListAdapter;
import com.android.dialer.calllog.ContactInfo;
import com.android.dialer.service.CachedNumberLookupService;
import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
@@ -35,6 +33,8 @@ public class RegularSearchListAdapter extends DialerPhoneNumberListAdapter {
public RegularSearchListAdapter(Context context) {
super(context);
+ setShortcutEnabled(SHORTCUT_CREATE_NEW_CONTACT, false);
+ setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, false);
}
public CachedContactInfo getContactInfo(
@@ -71,10 +71,7 @@ public class RegularSearchListAdapter extends DialerPhoneNumberListAdapter {
final boolean showNumberShortcuts = !TextUtils.isEmpty(getFormattedQueryString());
boolean changed = false;
changed |= setShortcutEnabled(SHORTCUT_DIRECT_CALL, showNumberShortcuts);
- // Either one of the add contacts options should be enabled. If the user entered
- // a dialable number, then clicking add to contact should add it as a number.
- // Otherwise, it should add it to a new contact as a name.
- changed |= setShortcutEnabled(SHORTCUT_ADD_NUMBER_TO_CONTACTS, showNumberShortcuts);
+ changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts);
changed |= setShortcutEnabled(SHORTCUT_MAKE_VIDEO_CALL,
showNumberShortcuts && CallUtil.isVideoEnabled(getContext()));
if (changed) {
diff --git a/src/com/android/dialer/list/SearchFragment.java b/src/com/android/dialer/list/SearchFragment.java
index 1c88c4e81..73127a294 100644
--- a/src/com/android/dialer/list/SearchFragment.java
+++ b/src/com/android/dialer/list/SearchFragment.java
@@ -15,11 +15,16 @@
*/
package com.android.dialer.list;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.Intent;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -41,7 +46,8 @@ import com.android.dialer.util.DialerUtils;
import com.android.phone.common.animation.AnimUtils;
public class SearchFragment extends PhoneNumberPickerFragment {
- private static final String TAG = "SearchFragment";
+ private static final String TAG = SearchFragment.class.getSimpleName();
+ private static final String SMS_URI_PREFIX = "sms:";
private OnListFragmentScrolledListener mActivityScrollListener;
@@ -128,6 +134,26 @@ public class SearchFragment extends PhoneNumberPickerFragment {
}
@Override
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ Animator animator = null;
+ if (nextAnim != 0) {
+ animator = AnimatorInflater.loadAnimator(getActivity(), nextAnim);
+ }
+ if (animator != null) {
+ final View view = getView();
+ final int oldLayerType = view.getLayerType();
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setLayerType(oldLayerType, null);
+ }
+ });
+ }
+ return animator;
+ }
+
+ @Override
protected void setSearchMode(boolean flag) {
super.setSearchMode(flag);
// This hides the "All contacts with phone numbers" header in the search fragment
@@ -180,31 +206,48 @@ public class SearchFragment extends PhoneNumberPickerFragment {
final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter();
final int shortcutType = adapter.getShortcutTypeFromPosition(position);
final OnPhoneNumberPickerActionListener listener;
- final String phoneNumber = TextUtils.isEmpty(mAddToContactNumber) ?
- adapter.getQueryString() : mAddToContactNumber;
+ final Intent intent;
+ final String number;
+
+ Log.i(TAG, "onItemClick: shortcutType=" + shortcutType);
- boolean ret = checkForProhibitedPhoneNumber(phoneNumber);
switch (shortcutType) {
case DialerPhoneNumberListAdapter.SHORTCUT_INVALID:
super.onItemClick(position, id);
break;
case DialerPhoneNumberListAdapter.SHORTCUT_DIRECT_CALL:
+ number = adapter.getQueryString();
listener = getOnPhoneNumberPickerListener();
- if (listener != null && !ret) {
- listener.onCallNumberDirectly(phoneNumber);
+ if (listener != null && !checkForProhibitedPhoneNumber(number)) {
+ listener.onCallNumberDirectly(number);
}
break;
- case DialerPhoneNumberListAdapter.SHORTCUT_ADD_NUMBER_TO_CONTACTS:
- final String number = TextUtils.isEmpty(mAddToContactNumber) ?
+ case DialerPhoneNumberListAdapter.SHORTCUT_CREATE_NEW_CONTACT:
+ number = TextUtils.isEmpty(mAddToContactNumber) ?
adapter.getFormattedQueryString() : mAddToContactNumber;
- final Intent intent = DialtactsActivity.getAddNumberToContactIntent(number);
+ intent = new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
+ intent.putExtra(ContactsContract.Intents.Insert.PHONE, number);
+ intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE,
+ ContactsContract.CommonDataKinds.Phone.TYPE_MAIN);
+ DialerUtils.startActivityWithErrorToast(getActivity(), intent);
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_ADD_TO_EXISTING_CONTACT:
+ number = TextUtils.isEmpty(mAddToContactNumber) ?
+ adapter.getFormattedQueryString() : mAddToContactNumber;
+ intent = DialtactsActivity.getAddNumberToContactIntent(number);
DialerUtils.startActivityWithErrorToast(getActivity(), intent,
R.string.add_contact_not_available);
break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_SEND_SMS_MESSAGE:
+ intent = new Intent(
+ Intent.ACTION_VIEW, Uri.parse(SMS_URI_PREFIX + getQueryString()));
+ DialerUtils.startActivityWithErrorToast(getActivity(), intent);
+ break;
case DialerPhoneNumberListAdapter.SHORTCUT_MAKE_VIDEO_CALL:
+ number = adapter.getQueryString();
listener = getOnPhoneNumberPickerListener();
- if (listener != null && !ret) {
- listener.onCallNumberDirectly(phoneNumber, true /* isVideoCall */);
+ if (listener != null && !checkForProhibitedPhoneNumber(number)) {
+ listener.onCallNumberDirectly(number, true /* isVideoCall */);
}
break;
}
diff --git a/src/com/android/dialer/list/ShortcutCardsAdapter.java b/src/com/android/dialer/list/ShortcutCardsAdapter.java
deleted file mode 100644
index 6410eabf6..000000000
--- a/src/com/android/dialer/list/ShortcutCardsAdapter.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2011 Google Inc.
- * Licensed to The Android Open Source Project.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.dialer.list;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.text.TextUtils;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.dialer.R;
-import com.android.dialer.calllog.CallLogAdapter;
-import com.android.dialer.calllog.CallLogNotificationsHelper;
-import com.android.dialer.calllog.CallLogQueryHandler;
-import com.android.dialer.list.SwipeHelper.OnItemGestureListener;
-import com.android.dialer.list.SwipeHelper.SwipeHelperCallback;
-
-/**
- * An adapter that displays call shortcuts from {@link com.android.dialer.calllog.CallLogAdapter}
- * in the form of cards.
- */
-public class ShortcutCardsAdapter extends BaseAdapter {
-
- private class CustomDataSetObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- notifyDataSetChanged();
- }
- }
-
- private static final String TAG = ShortcutCardsAdapter.class.getSimpleName();
- private static final float CLIP_CARD_BARELY_HIDDEN_RATIO = 0.001f;
- private static final float CLIP_CARD_MOSTLY_HIDDEN_RATIO = 0.9f;
- // Fade out 5x faster than the hidden ratio.
- private static final float CLIP_CARD_OPACITY_RATIO = 5f;
-
- private final CallLogAdapter mCallLogAdapter;
-
- private final ListsFragment mFragment;
-
- private final int mCallLogMarginHorizontal;
- private final int mCallLogMarginTop;
- private final int mCallLogMarginBottom;
- private final int mCallLogPaddingStart;
- private final int mCallLogPaddingTop;
- private final int mCallLogPaddingBottom;
- private final int mCardMaxHorizontalClip;
- private final int mShortCardBackgroundColor;
-
- private final Context mContext;
-
- private final DataSetObserver mObserver;
-
- private final CallLogQueryHandler mCallLogQueryHandler;
-
- private final OnItemGestureListener mCallLogOnItemSwipeListener =
- new OnItemGestureListener() {
- @Override
- public void onSwipe(View view) {
- mCallLogQueryHandler.markNewCallsAsOld();
- mCallLogQueryHandler.markNewVoicemailsAsOld();
- CallLogNotificationsHelper.removeMissedCallNotifications(mContext);
- CallLogNotificationsHelper.updateVoicemailNotifications(mContext);
- mFragment.dismissShortcut(view);
- }
-
- @Override
- public void onTouch() {}
-
- @Override
- public boolean isSwipeEnabled() {
- return true;
- }
- };
-
- private final CallLogQueryHandler.Listener mCallLogQueryHandlerListener =
- new CallLogQueryHandler.Listener() {
- @Override
- public void onVoicemailStatusFetched(Cursor statusCursor) {}
-
- @Override
- public boolean onCallsFetched(Cursor combinedCursor) {
- mCallLogAdapter.invalidateCache();
- mCallLogAdapter.changeCursor(combinedCursor);
- mCallLogAdapter.notifyDataSetChanged();
- // Return true; took ownership of cursor
- return true;
- }
- };
-
- public ShortcutCardsAdapter(Context context,
- ListsFragment fragment,
- CallLogAdapter callLogAdapter) {
- final Resources resources = context.getResources();
- mContext = context;
- mFragment = fragment;
- mCardMaxHorizontalClip =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_horizontal_clip_limit);
- mCallLogMarginHorizontal =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_margin_horizontal);
- mCallLogMarginTop =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_margin_top);
- mCallLogMarginBottom =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_margin_bottom);
- mCallLogPaddingStart =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_padding_start);
- mCallLogPaddingTop =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_padding_top);
- mCallLogPaddingBottom =
- resources.getDimensionPixelSize(R.dimen.recent_call_log_item_padding_bottom);
- mShortCardBackgroundColor = resources.getColor(R.color.call_log_expanded_background_color);
-
-
- mCallLogAdapter = callLogAdapter;
- mObserver = new CustomDataSetObserver();
- mCallLogAdapter.registerDataSetObserver(mObserver);
- mCallLogQueryHandler = new CallLogQueryHandler(mContext.getContentResolver(),
- mCallLogQueryHandlerListener);
- }
-
- /**
- * Determines the number of items in the adapter.
- * mCallLogAdapter contains the item for the most recent caller.
- * mContactTileAdapter contains the starred contacts.
- * The +1 is to account for the presence of the favorites menu.
- *
- * @return Number of items in the adapter.
- */
- @Override
- public int getCount() {
- return mCallLogAdapter.getCount();
- }
-
- @Override
- public Object getItem(int position) {
- return mCallLogAdapter.getItem(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- /**
- * Determine the number of view types present.
- */
- @Override
- public int getViewTypeCount() {
- return mCallLogAdapter.getViewTypeCount();
- }
-
- @Override
- public int getItemViewType(int position) {
- return mCallLogAdapter.getItemViewType(position);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final SwipeableShortcutCard wrapper;
- if (convertView == null) {
- wrapper = new SwipeableShortcutCard(mContext);
- wrapper.setOnItemSwipeListener(mCallLogOnItemSwipeListener);
- } else {
- wrapper = (SwipeableShortcutCard) convertView;
- }
-
- // Special case wrapper view for the most recent call log item. This allows
- // us to create a card-like effect for the more recent call log item in
- // the PhoneFavoriteMergedAdapter, but keep the original look of the item in
- // the CallLogAdapter.
- final View view = mCallLogAdapter.getView(position, convertView == null ?
- null : wrapper.getChildAt(0), parent
- );
- wrapper.removeAllViews();
- wrapper.prepareChildView(view);
- wrapper.addView(view);
- wrapper.setVisibility(View.VISIBLE);
- return wrapper;
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return mCallLogAdapter.areAllItemsEnabled();
- }
-
- @Override
- public boolean isEnabled(int position) {
- return mCallLogAdapter.isEnabled(position);
- }
-
- /**
- * The swipeable call log row.
- */
- class SwipeableShortcutCard extends FrameLayout implements SwipeHelperCallback {
- private SwipeHelper mSwipeHelper;
- private OnItemGestureListener mOnItemSwipeListener;
-
- private float mPreviousTranslationZ = 0;
- private Rect mClipRect = new Rect();
-
- public SwipeableShortcutCard(Context context) {
- super(context);
- final float densityScale = getResources().getDisplayMetrics().density;
- final float pagingTouchSlop = ViewConfiguration.get(context)
- .getScaledPagingTouchSlop();
- mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this,
- densityScale, pagingTouchSlop);
- }
-
- private void prepareChildView(View view) {
- // Override CallLogAdapter's accessibility behavior; don't expand the shortcut card.
- view.setAccessibilityDelegate(null);
- view.setBackgroundResource(R.drawable.rounded_corner_bg);
-
- final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT);
- params.setMargins(
- mCallLogMarginHorizontal,
- mCallLogMarginTop,
- mCallLogMarginHorizontal,
- mCallLogMarginBottom);
- view.setLayoutParams(params);
-
- LinearLayout actionView =
- (LinearLayout) view.findViewById(R.id.primary_action_view);
- actionView.setPaddingRelative(
- mCallLogPaddingStart,
- mCallLogPaddingTop,
- actionView.getPaddingEnd(),
- mCallLogPaddingBottom);
-
- // TODO: Set content description including type/location and time information.
- TextView nameView = (TextView) actionView.findViewById(R.id.name);
-
- actionView.setContentDescription(
- TextUtils.expandTemplate(
- getResources().getString(R.string.description_call_back_action),
- nameView.getText()));
-
- mPreviousTranslationZ = getResources().getDimensionPixelSize(
- R.dimen.recent_call_log_item_translation_z);
- view.setTranslationZ(mPreviousTranslationZ);
-
- final ViewGroup callLogItem = (ViewGroup) view.findViewById(R.id.call_log_list_item);
- // Reset the internal call log item view if it is being recycled
- callLogItem.setTranslationX(0);
- callLogItem.setTranslationY(0);
- callLogItem.setAlpha(1);
- callLogItem.setClipBounds(null);
- setChildrenOpacity(callLogItem, 1.0f);
-
- callLogItem.findViewById(R.id.call_log_row)
- .setBackgroundColor(mShortCardBackgroundColor);
-
- callLogItem.findViewById(R.id.call_indicator_icon).setVisibility(View.VISIBLE);
- }
-
- @Override
- public View getChildAtPosition(MotionEvent ev) {
- return getChildCount() > 0 ? getChildAt(0) : null;
- }
-
- @Override
- public View getChildContentView(View v) {
- return v.findViewById(R.id.call_log_list_item);
- }
-
- @Override
- public void onScroll() {}
-
- @Override
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public void onBeginDrag(View v) {
- // We do this so the underlying ScrollView knows that it won't get
- // the chance to intercept events anymore
- requestDisallowInterceptTouchEvent(true);
- }
-
- @Override
- public void onChildDismissed(View v) {
- if (v != null && mOnItemSwipeListener != null) {
- mOnItemSwipeListener.onSwipe(v);
- }
- }
-
- @Override
- public void onDragCancelled(View v) {}
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mSwipeHelper != null) {
- return mSwipeHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
- } else {
- return super.onInterceptTouchEvent(ev);
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mSwipeHelper != null) {
- return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
- } else {
- return super.onTouchEvent(ev);
- }
- }
-
- public void setOnItemSwipeListener(OnItemGestureListener listener) {
- mOnItemSwipeListener = listener;
- }
-
- /**
- * Clips the card by a specified amount.
- *
- * @param ratioHidden A float indicating how much of each edge of the card should be
- * clipped. If 0, the entire card is displayed. If 0.5f, each edge is hidden
- * entirely, thus obscuring the entire card.
- */
- public void clipCard(float ratioHidden) {
- final View viewToClip = getChildAt(0);
- if (viewToClip == null) {
- return;
- }
- int width = viewToClip.getWidth();
- int height = viewToClip.getHeight();
-
- if (ratioHidden <= CLIP_CARD_BARELY_HIDDEN_RATIO) {
- viewToClip.setTranslationZ(mPreviousTranslationZ);
- } else if (viewToClip.getTranslationZ() != 0){
- mPreviousTranslationZ = viewToClip.getTranslationZ();
- viewToClip.setTranslationZ(0);
- }
-
- if (ratioHidden > CLIP_CARD_MOSTLY_HIDDEN_RATIO) {
- mClipRect.set(0, 0 , 0, 0);
- setVisibility(View.INVISIBLE);
- } else {
- setVisibility(View.VISIBLE);
- int newTop = (int) (ratioHidden * height);
- mClipRect.set(0, newTop, width, height);
-
- // Since the pane will be overlapping with the action bar, apply a vertical offset
- // to top align the clipped card in the viewable area;
- viewToClip.setTranslationY(-newTop);
- }
- viewToClip.setClipBounds(mClipRect);
-
- // If the view has any children, fade them out of view.
- final ViewGroup viewGroup = (ViewGroup) viewToClip;
- setChildrenOpacity(
- viewGroup, Math.max(0, 1 - (CLIP_CARD_OPACITY_RATIO * ratioHidden)));
- }
-
- private void setChildrenOpacity(ViewGroup viewGroup, float alpha) {
- final int count = viewGroup.getChildCount();
- for (int i = 0; i < count; i++) {
- viewGroup.getChildAt(i).setAlpha(alpha);
- }
- }
- }
-}
diff --git a/src/com/android/dialer/list/SmartDialNumberListAdapter.java b/src/com/android/dialer/list/SmartDialNumberListAdapter.java
index 04c0d620d..fe27a25ab 100644
--- a/src/com/android/dialer/list/SmartDialNumberListAdapter.java
+++ b/src/com/android/dialer/list/SmartDialNumberListAdapter.java
@@ -20,15 +20,12 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Callable;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.list.ContactListItemView;
-import com.android.contacts.common.list.PhoneNumberListAdapter;
-import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
import com.android.dialer.dialpad.SmartDialCursorLoader;
import com.android.dialer.dialpad.SmartDialNameMatcher;
import com.android.dialer.dialpad.SmartDialPrefix;
@@ -49,6 +46,7 @@ public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter {
public SmartDialNumberListAdapter(Context context) {
super(context);
mNameMatcher = new SmartDialNameMatcher("", SmartDialPrefix.getMap());
+ setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_DIRECT_CALL, false);
if (DEBUG) {
Log.v(TAG, "Constructing List Adapter");
@@ -119,7 +117,9 @@ public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter {
public void setQueryString(String queryString) {
final boolean showNumberShortcuts = !TextUtils.isEmpty(getFormattedQueryString());
boolean changed = false;
- changed |= setShortcutEnabled(SHORTCUT_ADD_NUMBER_TO_CONTACTS, showNumberShortcuts);
+ changed |= setShortcutEnabled(SHORTCUT_CREATE_NEW_CONTACT, showNumberShortcuts);
+ changed |= setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, showNumberShortcuts);
+ changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts);
changed |= setShortcutEnabled(SHORTCUT_MAKE_VIDEO_CALL,
showNumberShortcuts && CallUtil.isVideoEnabled(getContext()));
if (changed) {
diff --git a/src/com/android/dialer/list/SmartDialSearchFragment.java b/src/com/android/dialer/list/SmartDialSearchFragment.java
index 4f0ce7216..082bc4360 100644
--- a/src/com/android/dialer/list/SmartDialSearchFragment.java
+++ b/src/com/android/dialer/list/SmartDialSearchFragment.java
@@ -38,11 +38,8 @@ public class SmartDialSearchFragment extends SearchFragment {
SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());
adapter.setUseCallableUri(super.usesCallableUri());
adapter.setQuickContactEnabled(true);
- // Disable the direct call shortcut for the smart dial fragment, since the call button
- // will already be showing anyway.
- adapter.setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_DIRECT_CALL, false);
- adapter.setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_ADD_NUMBER_TO_CONTACTS,
- false);
+ // Set adapter's query string to restore previous instance state.
+ adapter.setQueryString(getQueryString());
return adapter;
}
diff --git a/src/com/android/dialer/list/SpeedDialFragment.java b/src/com/android/dialer/list/SpeedDialFragment.java
index 63f1f34c6..e72b25059 100644
--- a/src/com/android/dialer/list/SpeedDialFragment.java
+++ b/src/com/android/dialer/list/SpeedDialFragment.java
@@ -28,6 +28,7 @@ import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Trace;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -38,10 +39,11 @@ import android.view.animation.LayoutAnimationController;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.ContactTileLoaderFactory;
@@ -68,7 +70,7 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener,
*/
private static final long KEY_REMOVED_ITEM_HEIGHT = Long.MAX_VALUE;
- private static final String TAG = SpeedDialFragment.class.getSimpleName();
+ private static final String TAG = "SpeedDialFragment";
private static final boolean DEBUG = false;
private int mAnimationDuration;
@@ -180,21 +182,26 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener,
@Override
public void onCreate(Bundle savedState) {
if (DEBUG) Log.d(TAG, "onCreate()");
+ Trace.beginSection(TAG + " onCreate");
super.onCreate(savedState);
mAnimationDuration = getResources().getInteger(R.integer.fade_duration);
+ Trace.endSection();
}
@Override
public void onResume() {
+ Trace.beginSection(TAG + " onResume");
super.onResume();
getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad();
+ Trace.endSection();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ Trace.beginSection(TAG + " onCreateView");
mParentView = inflater.inflate(R.layout.speed_dial_fragment, container, false);
mListView = (PhoneFavoriteListView) mParentView.findViewById(R.id.contact_tile_list);
@@ -208,7 +215,6 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener,
(ImageView) getActivity().findViewById(R.id.contact_tile_drag_shadow_overlay);
mListView.setDragShadowOverlay(dragShadowOverlay);
- final Resources resources = getResources();
mEmptyView = mParentView.findViewById(R.id.empty_list_view);
DialerUtils.configureEmptyListView(
mEmptyView, R.drawable.empty_speed_dial, R.string.speed_dial_empty, getResources());
@@ -224,7 +230,7 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener,
mListView.setOnScrollListener(mScrollListener);
mListView.setFastScrollEnabled(false);
mListView.setFastScrollAlwaysVisible(false);
-
+ Trace.endSection();
return mParentView;
}
@@ -239,7 +245,7 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener,
final int listViewVisibility = visible ? View.GONE : View.VISIBLE;
if (previousVisibility != emptyViewVisibility) {
- final RelativeLayout.LayoutParams params = (LayoutParams) mContactTileFrame
+ final FrameLayout.LayoutParams params = (LayoutParams) mContactTileFrame
.getLayoutParams();
params.height = visible ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
mContactTileFrame.setLayoutParams(params);
diff --git a/src/com/android/dialer/list/SwipeHelper.java b/src/com/android/dialer/list/SwipeHelper.java
deleted file mode 100644
index 03300df42..000000000
--- a/src/com/android/dialer/list/SwipeHelper.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2012 Google Inc.
- * Licensed to The Android Open Source Project.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.list;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.RectF;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.animation.LinearInterpolator;
-
-import com.android.dialer.R;
-
-/**
- * Copy of packages/apps/UnifiedEmail - com.android.mail.ui.SwipeHelper with changes.
- */
-public class SwipeHelper {
- static final String TAG = SwipeHelper.class.getSimpleName();
- private static final boolean DEBUG_INVALIDATE = false;
- private static final boolean CONSTRAIN_SWIPE = true;
- private static final boolean FADE_OUT_DURING_SWIPE = true;
- private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
- private static final boolean LOG_SWIPE_DISMISS_VELOCITY = false; // STOPSHIP - DEBUG ONLY
-
- public static final int IS_SWIPEABLE_TAG = R.id.is_swipeable_tag;
- public static final Object IS_SWIPEABLE = new Object();
-
- public static final int X = 0;
- public static final int Y = 1;
-
- private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
-
- private static int SWIPE_ESCAPE_VELOCITY = -1;
- private static int DEFAULT_ESCAPE_ANIMATION_DURATION;
- private static int MAX_ESCAPE_ANIMATION_DURATION;
- private static int MAX_DISMISS_VELOCITY;
- private static int SNAP_ANIM_LEN;
- private static int SWIPE_SCROLL_SLOP;
- private static float MIN_SWIPE;
- private static float MIN_VERT;
- private static float MIN_LOCK;
-
- public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
- // where fade starts
- static final float ALPHA_FADE_END = 0.7f; // fraction of thumbnail width
- // beyond which alpha->0
- private static final float FACTOR = 1.2f;
-
- private static final int PROTECTION_PADDING = 50;
-
- private float mMinAlpha = 0.3f;
-
- private float mPagingTouchSlop;
- private final SwipeHelperCallback mCallback;
- private final int mSwipeDirection;
- private final VelocityTracker mVelocityTracker;
-
- private float mInitialTouchPosX;
- private boolean mDragging;
- private View mCurrView;
- private View mCurrAnimView;
- private boolean mCanCurrViewBeDimissed;
- private float mDensityScale;
- private float mLastY;
- private float mInitialTouchPosY;
-
- private float mStartAlpha;
- private boolean mProtected = false;
-
- private float mChildSwipedFarEnoughFactor = 0.4f;
- private float mChildSwipedFastEnoughFactor = 0.05f;
-
- public SwipeHelper(Context context, int swipeDirection, SwipeHelperCallback callback, float densityScale,
- float pagingTouchSlop) {
- mCallback = callback;
- mSwipeDirection = swipeDirection;
- mVelocityTracker = VelocityTracker.obtain();
- mDensityScale = densityScale;
- mPagingTouchSlop = pagingTouchSlop;
- if (SWIPE_ESCAPE_VELOCITY == -1) {
- Resources res = context.getResources();
- SWIPE_ESCAPE_VELOCITY = res.getInteger(R.integer.swipe_escape_velocity);
- DEFAULT_ESCAPE_ANIMATION_DURATION = res.getInteger(R.integer.escape_animation_duration);
- MAX_ESCAPE_ANIMATION_DURATION = res.getInteger(R.integer.max_escape_animation_duration);
- MAX_DISMISS_VELOCITY = res.getInteger(R.integer.max_dismiss_velocity);
- SNAP_ANIM_LEN = res.getInteger(R.integer.snap_animation_duration);
- SWIPE_SCROLL_SLOP = res.getInteger(R.integer.swipe_scroll_slop);
- MIN_SWIPE = res.getDimension(R.dimen.min_swipe);
- MIN_VERT = res.getDimension(R.dimen.min_vert);
- MIN_LOCK = res.getDimension(R.dimen.min_lock);
- }
- }
-
- public void setDensityScale(float densityScale) {
- mDensityScale = densityScale;
- }
-
- public void setPagingTouchSlop(float pagingTouchSlop) {
- mPagingTouchSlop = pagingTouchSlop;
- }
-
- public void setChildSwipedFarEnoughFactor(float factor) {
- mChildSwipedFarEnoughFactor = factor;
- }
-
- public void setChildSwipedFastEnoughFactor(float factor) {
- mChildSwipedFastEnoughFactor = factor;
- }
-
- private float getVelocity(VelocityTracker vt) {
- return mSwipeDirection == X ? vt.getXVelocity() :
- vt.getYVelocity();
- }
-
- private ObjectAnimator createTranslationAnimation(View v, float newPos) {
- ObjectAnimator anim = ObjectAnimator.ofFloat(v,
- mSwipeDirection == X ? "translationX" : "translationY", newPos);
- return anim;
- }
-
- private ObjectAnimator createDismissAnimation(View v, float newPos, int duration) {
- ObjectAnimator anim = createTranslationAnimation(v, newPos);
- anim.setInterpolator(sLinearInterpolator);
- anim.setDuration(duration);
- return anim;
- }
-
- private float getPerpendicularVelocity(VelocityTracker vt) {
- return mSwipeDirection == X ? vt.getYVelocity() :
- vt.getXVelocity();
- }
-
- private void setTranslation(View v, float translate) {
- if (mSwipeDirection == X) {
- v.setTranslationX(translate);
- } else {
- v.setTranslationY(translate);
- }
- }
-
- private float getSize(View v) {
- return mSwipeDirection == X ? v.getMeasuredWidth() :
- v.getMeasuredHeight();
- }
-
- public void setMinAlpha(float minAlpha) {
- mMinAlpha = minAlpha;
- }
-
- private float getAlphaForOffset(View view) {
- float viewSize = getSize(view);
- final float fadeSize = ALPHA_FADE_END * viewSize;
- float result = mStartAlpha;
- float pos = view.getTranslationX();
- if (pos >= viewSize * ALPHA_FADE_START) {
- result = mStartAlpha - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
- } else if (pos < viewSize * (mStartAlpha - ALPHA_FADE_START)) {
- result = mStartAlpha + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
- }
- return Math.max(mMinAlpha, result);
- }
-
- // invalidate the view's own bounds all the way up the view hierarchy
- public static void invalidateGlobalRegion(View view) {
- invalidateGlobalRegion(
- view,
- new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
- }
-
- // invalidate a rectangle relative to the view's coordinate system all the way up the view
- // hierarchy
- public static void invalidateGlobalRegion(View view, RectF childBounds) {
- // childBounds.offset(view.getTranslationX(), view.getTranslationY());
- if (DEBUG_INVALIDATE)
- Log.v(TAG, "-------------");
- while (view.getParent() != null && view.getParent() instanceof View) {
- view = (View) view.getParent();
- view.getMatrix().mapRect(childBounds);
- view.invalidate((int) Math.floor(childBounds.left),
- (int) Math.floor(childBounds.top),
- (int) Math.ceil(childBounds.right),
- (int) Math.ceil(childBounds.bottom));
- if (DEBUG_INVALIDATE) {
- Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
- + "," + (int) Math.floor(childBounds.top)
- + "," + (int) Math.ceil(childBounds.right)
- + "," + (int) Math.ceil(childBounds.bottom));
- }
- }
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- final int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mLastY = ev.getY();
- mDragging = false;
- mCurrView = mCallback.getChildAtPosition(ev);
- mVelocityTracker.clear();
- if (mCurrView != null) {
- mCurrAnimView = mCallback.getChildContentView(mCurrView);
- mStartAlpha = mCurrAnimView.getAlpha();
- mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
- mVelocityTracker.addMovement(ev);
- mInitialTouchPosX = ev.getX();
- mInitialTouchPosY = ev.getY();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (mCurrView != null) {
- // Check the movement direction.
- if (mLastY >= 0 && !mDragging) {
- float currY = ev.getY();
- float currX = ev.getX();
- float deltaY = Math.abs(currY - mInitialTouchPosY);
- float deltaX = Math.abs(currX - mInitialTouchPosX);
- if (deltaY > SWIPE_SCROLL_SLOP && deltaY > (FACTOR * deltaX)) {
- mLastY = ev.getY();
- mCallback.onScroll();
- return false;
- }
- }
- mVelocityTracker.addMovement(ev);
- float pos = ev.getX();
- float delta = pos - mInitialTouchPosX;
- if (Math.abs(delta) > mPagingTouchSlop) {
- mCallback.onBeginDrag(mCallback.getChildContentView(mCurrView));
- mDragging = true;
- mInitialTouchPosX = ev.getX() - mCurrAnimView.getTranslationX();
- mInitialTouchPosY = ev.getY();
- }
- }
- mLastY = ev.getY();
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mDragging = false;
- mCurrView = null;
- mCurrAnimView = null;
- mLastY = -1;
- break;
- }
- return mDragging;
- }
-
- /**
- * @param view The view to be dismissed
- * @param velocity The desired pixels/second speed at which the view should
- * move
- */
- private void dismissChild(final View view, float velocity) {
- final View animView = mCallback.getChildContentView(view);
- final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
- float newPos = determinePos(animView, velocity);
- int duration = determineDuration(animView, newPos, velocity);
-
- animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- ObjectAnimator anim = createDismissAnimation(animView, newPos, duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mCallback.onChildDismissed(view);
- animView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
- animView.setAlpha(getAlphaForOffset(animView));
- }
- invalidateGlobalRegion(animView);
- }
- });
- anim.start();
- }
-
- private int determineDuration(View animView, float newPos, float velocity) {
- int duration = MAX_ESCAPE_ANIMATION_DURATION;
- if (velocity != 0) {
- duration = Math
- .min(duration,
- (int) (Math.abs(newPos - animView.getTranslationX()) * 1000f / Math
- .abs(velocity)));
- } else {
- duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
- }
- return duration;
- }
-
- private float determinePos(View animView, float velocity) {
- float newPos = 0;
- if (velocity < 0 || (velocity == 0 && animView.getTranslationX() < 0)
- // if we use the Menu to dismiss an item in landscape, animate up
- || (velocity == 0 && animView.getTranslationX() == 0 && mSwipeDirection == Y)) {
- newPos = -getSize(animView);
- } else {
- newPos = getSize(animView);
- }
- return newPos;
- }
-
- public void snapChild(final View view, float velocity) {
- final View animView = mCallback.getChildContentView(view);
- final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
- ObjectAnimator anim = createTranslationAnimation(animView, 0);
- int duration = SNAP_ANIM_LEN;
- anim.setDuration(duration);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
- animView.setAlpha(getAlphaForOffset(animView));
- }
- invalidateGlobalRegion(animView);
- }
- });
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- animView.setAlpha(mStartAlpha);
- mCallback.onDragCancelled(mCurrView);
- }
- });
- anim.start();
- }
-
- public boolean onTouchEvent(MotionEvent ev) {
- if (!mDragging || mProtected) {
- return false;
- }
- mVelocityTracker.addMovement(ev);
- final int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_OUTSIDE:
- case MotionEvent.ACTION_MOVE:
- if (mCurrView != null) {
- float deltaX = ev.getX() - mInitialTouchPosX;
- float deltaY = Math.abs(ev.getY() - mInitialTouchPosY);
- // If the user has gone vertical and not gone horizontalish AT
- // LEAST minBeforeLock, switch to scroll. Otherwise, cancel
- // the swipe.
- if (!mDragging && deltaY > MIN_VERT && (Math.abs(deltaX)) < MIN_LOCK
- && deltaY > (FACTOR * Math.abs(deltaX))) {
- mCallback.onScroll();
- return false;
- }
- float minDistance = MIN_SWIPE;
- if (Math.abs(deltaX) < minDistance) {
- // Don't start the drag until at least X distance has
- // occurred.
- return true;
- }
- // don't let items that can't be dismissed be dragged more
- // than maxScrollDistance
- if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
- float size = getSize(mCurrAnimView);
- float maxScrollDistance = 0.15f * size;
- if (Math.abs(deltaX) >= size) {
- deltaX = deltaX > 0 ? maxScrollDistance : -maxScrollDistance;
- } else {
- deltaX = maxScrollDistance
- * (float) Math.sin((deltaX / size) * (Math.PI / 2));
- }
- }
- setTranslation(mCurrAnimView, deltaX);
- if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
- mCurrAnimView.setAlpha(getAlphaForOffset(mCurrAnimView));
- }
- invalidateGlobalRegion(mCallback.getChildContentView(mCurrView));
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (mCurrView != null) {
- float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
- mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
- float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
- float velocity = getVelocity(mVelocityTracker);
- float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
-
- // Decide whether to dismiss the current view
- // Tweak constants below as required to prevent erroneous
- // swipe/dismiss
- float translation = Math.abs(mCurrAnimView.getTranslationX());
- float currAnimViewSize = getSize(mCurrAnimView);
- // Long swipe = translation of {@link #mChildSwipedFarEnoughFactor} * width
- boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH
- && translation > mChildSwipedFarEnoughFactor * currAnimViewSize;
- // Fast swipe = > escapeVelocity and translation of
- // {@link #mChildSwipedFastEnoughFactor} * width
- boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity)
- && (Math.abs(velocity) > Math.abs(perpendicularVelocity))
- && (velocity > 0) == (mCurrAnimView.getTranslationX() > 0)
- && translation > mChildSwipedFastEnoughFactor * currAnimViewSize;
- if (LOG_SWIPE_DISMISS_VELOCITY) {
- Log.v(TAG, "Swipe/Dismiss: " + velocity + "/" + escapeVelocity + "/"
- + perpendicularVelocity + ", x: " + translation + "/"
- + currAnimViewSize);
- }
-
- boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
- && (childSwipedFastEnough || childSwipedFarEnough);
-
- if (dismissChild) {
- dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
- } else {
- snapChild(mCurrView, velocity);
- }
- }
- break;
- }
- return true;
- }
-
- public static void setSwipeable(View view, boolean swipeable) {
- view.setTag(IS_SWIPEABLE_TAG, swipeable ? IS_SWIPEABLE : null);
- }
-
- public static boolean isSwipeable(View view) {
- return IS_SWIPEABLE == view.getTag(IS_SWIPEABLE_TAG);
- }
-
- public interface SwipeHelperCallback {
- View getChildAtPosition(MotionEvent ev);
-
- View getChildContentView(View v);
-
- void onScroll();
-
- boolean canChildBeDismissed(View v);
-
- void onBeginDrag(View v);
-
- void onChildDismissed(View v);
-
- void onDragCancelled(View v);
-
- }
-
- public interface OnItemGestureListener {
- public void onSwipe(View view);
-
- public void onTouch();
-
- public boolean isSwipeEnabled();
- }
-}
diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index de07128ca..8653dc9a7 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -2,6 +2,7 @@ package com.android.dialer.settings;
import com.google.common.collect.Lists;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -14,13 +15,7 @@ import android.preference.PreferenceActivity.Header;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.view.LayoutInflater;
import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListAdapter;
-import android.widget.TextView;
import com.android.dialer.DialtactsActivity;
import com.android.dialer.R;
@@ -30,7 +25,6 @@ import java.util.List;
public class DialerSettingsActivity extends PreferenceActivity {
protected SharedPreferences mPreferences;
- private HeaderAdapter mHeaderAdapter;
private static final int OWNER_HANDLE_ID = 0;
@@ -42,18 +36,31 @@ public class DialerSettingsActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
- final Header generalSettingsHeader = new Header();
- generalSettingsHeader.titleRes = R.string.general_settings_label;
- generalSettingsHeader.fragment = GeneralSettingsFragment.class.getName();
- target.add(generalSettingsHeader);
+ Header displayOptionsHeader = new Header();
+ displayOptionsHeader.titleRes = R.string.display_options_title;
+ displayOptionsHeader.fragment = DisplayOptionsSettingsFragment.class.getName();
+ target.add(displayOptionsHeader);
+
+ Header soundSettingsHeader = new Header();
+ soundSettingsHeader.titleRes = R.string.sounds_and_vibration_title;
+ soundSettingsHeader.fragment = SoundSettingsFragment.class.getName();
+ target.add(soundSettingsHeader);
+
+ Header quickResponseSettingsHeader = new Header();
+ Intent quickResponseSettingsIntent =
+ new Intent(TelecomManager.ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS);
+ quickResponseSettingsHeader.titleRes = R.string.respond_via_sms_setting_title;
+ quickResponseSettingsHeader.intent = quickResponseSettingsIntent;
+ target.add(quickResponseSettingsHeader);
+
+ TelephonyManager telephonyManager =
+ (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// Only show call setting menus if the current user is the primary/owner user.
if (isPrimaryUser()) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// Show "Call Settings" if there is one SIM and "Phone Accounts" if there are more.
if (telephonyManager.getPhoneCount() <= 1) {
- final Header callSettingsHeader = new Header();
+ Header callSettingsHeader = new Header();
Intent callSettingsIntent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
callSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -61,7 +68,7 @@ public class DialerSettingsActivity extends PreferenceActivity {
callSettingsHeader.intent = callSettingsIntent;
target.add(callSettingsHeader);
} else {
- final Header phoneAccountSettingsHeader = new Header();
+ Header phoneAccountSettingsHeader = new Header();
Intent phoneAccountSettingsIntent =
new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
phoneAccountSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -71,6 +78,16 @@ public class DialerSettingsActivity extends PreferenceActivity {
target.add(phoneAccountSettingsHeader);
}
}
+
+ if (telephonyManager.isTtyModeSupported()
+ || telephonyManager.isHearingAidCompatibilitySupported()) {
+ Header accessibilitySettingsHeader = new Header();
+ Intent accessibilitySettingsIntent =
+ new Intent(TelecomManager.ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS);
+ accessibilitySettingsHeader.titleRes = R.string.accessibility_settings_title;
+ accessibilitySettingsHeader.intent = accessibilitySettingsIntent;
+ target.add(accessibilitySettingsHeader);
+ }
}
@Override
@@ -87,23 +104,6 @@ public class DialerSettingsActivity extends PreferenceActivity {
return true;
}
- @Override
- public void setListAdapter(ListAdapter adapter) {
- if (adapter == null) {
- super.setListAdapter(null);
- } else {
- // We don't have access to the hidden getHeaders() method, so grab the headers from
- // the intended adapter and then replace it with our own.
- int headerCount = adapter.getCount();
- List<Header> headers = Lists.newArrayList();
- for (int i = 0; i < headerCount; i++) {
- headers.add((Header) adapter.getItem(i));
- }
- mHeaderAdapter = new HeaderAdapter(this, headers);
- super.setListAdapter(mHeaderAdapter);
- }
- }
-
/**
* Whether a user handle associated with the current user is that of the primary owner. That is,
* whether there is a user handle which has an id which matches the owner's handle.
@@ -120,51 +120,4 @@ public class DialerSettingsActivity extends PreferenceActivity {
return false;
}
-
- /**
- * This custom {@code ArrayAdapter} is mostly identical to the equivalent one in
- * {@code PreferenceActivity}, except with a local layout resource.
- */
- private static class HeaderAdapter extends ArrayAdapter<Header> {
- static class HeaderViewHolder {
- TextView title;
- TextView summary;
- }
-
- private LayoutInflater mInflater;
-
- public HeaderAdapter(Context context, List<Header> objects) {
- super(context, 0, objects);
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- HeaderViewHolder holder;
- View view;
-
- if (convertView == null) {
- view = mInflater.inflate(R.layout.dialer_preferences, parent, false);
- holder = new HeaderViewHolder();
- holder.title = (TextView) view.findViewById(R.id.title);
- holder.summary = (TextView) view.findViewById(R.id.summary);
- view.setTag(holder);
- } else {
- view = convertView;
- holder = (HeaderViewHolder) view.getTag();
- }
-
- // All view fields must be updated every time, because the view may be recycled
- Header header = getItem(position);
- holder.title.setText(header.getTitle(getContext().getResources()));
- CharSequence summary = header.getSummary(getContext().getResources());
- if (!TextUtils.isEmpty(summary)) {
- holder.summary.setVisibility(View.VISIBLE);
- holder.summary.setText(summary);
- } else {
- holder.summary.setVisibility(View.GONE);
- }
- return view;
- }
- }
}
diff --git a/src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java b/src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java
new file mode 100644
index 000000000..4b2c8f6db
--- /dev/null
+++ b/src/com/android/dialer/settings/DisplayOptionsSettingsFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.settings;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+import com.android.dialer.R;
+
+public class DisplayOptionsSettingsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.display_options_settings);
+ }
+}
diff --git a/src/com/android/dialer/settings/GeneralSettingsFragment.java b/src/com/android/dialer/settings/GeneralSettingsFragment.java
deleted file mode 100644
index 578ff3389..000000000
--- a/src/com/android/dialer/settings/GeneralSettingsFragment.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.dialer.settings;
-
-import android.content.Context;
-import android.media.RingtoneManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Vibrator;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-
-import com.android.dialer.R;
-import com.android.phone.common.util.SettingsUtil;
-
-import java.lang.Boolean;
-import java.lang.CharSequence;
-import java.lang.Object;
-import java.lang.Override;
-import java.lang.Runnable;
-import java.lang.String;
-import java.lang.Thread;
-
-public class GeneralSettingsFragment extends PreferenceFragment
- implements Preference.OnPreferenceChangeListener {
- private static final String CATEGORY_SOUNDS_KEY = "dialer_general_sounds_category_key";
- private static final String BUTTON_RINGTONE_KEY = "button_ringtone_key";
- private static final String BUTTON_VIBRATE_ON_RING = "button_vibrate_on_ring";
- private static final String BUTTON_PLAY_DTMF_TONE = "button_play_dtmf_tone";
- private static final String BUTTON_RESPOND_VIA_SMS_KEY = "button_respond_via_sms_key";
-
- private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
-
- private Context mContext;
-
- private Preference mRingtonePreference;
- private CheckBoxPreference mVibrateWhenRinging;
- private CheckBoxPreference mPlayDtmfTone;
- private Preference mRespondViaSms;
-
- private Runnable mRingtoneLookupRunnable;
- private final Handler mRingtoneLookupComplete = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_RINGTONE_SUMMARY:
- mRingtonePreference.setSummary((CharSequence) msg.obj);
- break;
- }
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mContext = getActivity().getApplicationContext();
-
- addPreferencesFromResource(R.xml.general_settings);
-
- mRingtonePreference = findPreference(BUTTON_RINGTONE_KEY);
- mVibrateWhenRinging = (CheckBoxPreference) findPreference(BUTTON_VIBRATE_ON_RING);
- mPlayDtmfTone = (CheckBoxPreference) findPreference(BUTTON_PLAY_DTMF_TONE);
- mRespondViaSms = findPreference(BUTTON_RESPOND_VIA_SMS_KEY);
-
- PreferenceCategory soundCategory = (PreferenceCategory) findPreference(CATEGORY_SOUNDS_KEY);
- if (mVibrateWhenRinging != null) {
- Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
- if (vibrator != null && vibrator.hasVibrator()) {
- mVibrateWhenRinging.setOnPreferenceChangeListener(this);
- } else {
- soundCategory.removePreference(mVibrateWhenRinging);
- mVibrateWhenRinging = null;
- }
- }
-
- if (mPlayDtmfTone != null) {
- mPlayDtmfTone.setOnPreferenceChangeListener(this);
- mPlayDtmfTone.setChecked(Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.DTMF_TONE_WHEN_DIALING, 1) != 0);
- }
-
- mRingtoneLookupRunnable = new Runnable() {
- @Override
- public void run() {
- if (mRingtonePreference != null) {
- SettingsUtil.updateRingtoneName(
- mContext,
- mRingtoneLookupComplete,
- RingtoneManager.TYPE_RINGTONE,
- mRingtonePreference.getKey(),
- MSG_UPDATE_RINGTONE_SUMMARY);
- }
- }
- };
- }
-
- /**
- * Supports onPreferenceChangeListener to look for preference changes.
- *
- * @param preference The preference to be changed
- * @param objValue The value of the selection, NOT its localized display value.
- */
- @Override
- public boolean onPreferenceChange(Preference preference, Object objValue) {
- if (preference == mVibrateWhenRinging) {
- boolean doVibrate = (Boolean) objValue;
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, doVibrate ? 1 : 0);
- }
- return true;
- }
-
- /**
- * Click listener for toggle events.
- */
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mPlayDtmfTone) {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.DTMF_TONE_WHEN_DIALING, mPlayDtmfTone.isChecked() ? 1 : 0);
- } else if (preference == mRespondViaSms) {
- // Needs to return false for the intent to launch.
- return false;
- }
- return true;
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (mVibrateWhenRinging != null) {
- mVibrateWhenRinging.setChecked(SettingsUtil.getVibrateWhenRingingSetting(mContext));
- }
-
- // Lookup the ringtone name asynchronously.
- new Thread(mRingtoneLookupRunnable).start();
- }
-}
diff --git a/src/com/android/dialer/settings/SoundSettingsFragment.java b/src/com/android/dialer/settings/SoundSettingsFragment.java
new file mode 100644
index 000000000..43297b5e5
--- /dev/null
+++ b/src/com/android/dialer/settings/SoundSettingsFragment.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.settings;
+
+import android.content.Context;
+import android.media.RingtoneManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Vibrator;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import com.android.dialer.R;
+import com.android.phone.common.util.SettingsUtil;
+
+import java.lang.Boolean;
+import java.lang.CharSequence;
+import java.lang.Object;
+import java.lang.Override;
+import java.lang.Runnable;
+import java.lang.String;
+import java.lang.Thread;
+
+public class SoundSettingsFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final int NO_DTMF_TONE = 0;
+ private static final int PLAY_DTMF_TONE = 1;
+
+ private static final int NO_VIBRATION_FOR_CALLS = 0;
+ private static final int DO_VIBRATION_FOR_CALLS = 1;
+
+ private static final int SHOW_CARRIER_SETTINGS = 0;
+ private static final int HIDE_CARRIER_SETTINGS = 1;
+
+ private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
+
+ private Preference mRingtonePreference;
+ private CheckBoxPreference mVibrateWhenRinging;
+ private CheckBoxPreference mPlayDtmfTone;
+ private ListPreference mDtmfToneLength;
+
+ private final Runnable mRingtoneLookupRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateRingtonePreferenceSummary();
+ }
+ };
+
+ private final Handler mRingtoneLookupComplete = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_RINGTONE_SUMMARY:
+ mRingtonePreference.setSummary((CharSequence) msg.obj);
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.sound_settings);
+
+ Context context = getActivity();
+
+ mRingtonePreference = findPreference(context.getString(R.string.ringtone_preference_key));
+ mVibrateWhenRinging = (CheckBoxPreference) findPreference(
+ context.getString(R.string.vibrate_on_preference_key));
+ mPlayDtmfTone = (CheckBoxPreference) findPreference(
+ context.getString(R.string.play_dtmf_preference_key));
+ mDtmfToneLength = (ListPreference) findPreference(
+ context.getString(R.string.dtmf_tone_length_preference_key));
+
+ if (hasVibrator()) {
+ mVibrateWhenRinging.setOnPreferenceChangeListener(this);
+ } else {
+ getPreferenceScreen().removePreference(mVibrateWhenRinging);
+ mVibrateWhenRinging = null;
+ }
+
+ mPlayDtmfTone.setOnPreferenceChangeListener(this);
+ mPlayDtmfTone.setChecked(shouldPlayDtmfTone());
+
+ TelephonyManager telephonyManager =
+ (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+ if (telephonyManager.canChangeDtmfToneLength()
+ && (telephonyManager.isWorldPhone() || !shouldHideCarrierSettings())) {
+ mDtmfToneLength.setOnPreferenceChangeListener(this);
+ mDtmfToneLength.setValueIndex(
+ Settings.System.getInt(context.getContentResolver(),
+ Settings.System.DTMF_TONE_WHEN_DIALING, PLAY_DTMF_TONE));
+ } else {
+ getPreferenceScreen().removePreference(mDtmfToneLength);
+ mDtmfToneLength = null;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mVibrateWhenRinging != null) {
+ mVibrateWhenRinging.setChecked(shouldVibrateWhenRinging());
+ }
+
+ // Lookup the ringtone name asynchronously.
+ new Thread(mRingtoneLookupRunnable).start();
+ }
+
+ /**
+ * Supports onPreferenceChangeListener to look for preference changes.
+ *
+ * @param preference The preference to be changed
+ * @param objValue The value of the selection, NOT its localized display value.
+ */
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (preference == mVibrateWhenRinging) {
+ boolean doVibrate = (Boolean) objValue;
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING,
+ doVibrate ? DO_VIBRATION_FOR_CALLS : NO_VIBRATION_FOR_CALLS);
+ } else if (preference == mDtmfToneLength) {
+ int index = mDtmfToneLength.findIndexOfValue((String) objValue);
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, index);
+ }
+ return true;
+ }
+
+ /**
+ * Click listener for toggle events.
+ */
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ if (preference == mPlayDtmfTone) {
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.DTMF_TONE_WHEN_DIALING,
+ mPlayDtmfTone.isChecked() ? PLAY_DTMF_TONE : NO_DTMF_TONE);
+ }
+ return true;
+ }
+
+ /**
+ * Updates the summary text on the ringtone preference with the name of the ringtone.
+ */
+ private void updateRingtonePreferenceSummary() {
+ SettingsUtil.updateRingtoneName(
+ getActivity(),
+ mRingtoneLookupComplete,
+ RingtoneManager.TYPE_RINGTONE,
+ mRingtonePreference.getKey(),
+ MSG_UPDATE_RINGTONE_SUMMARY);
+ }
+
+ /**
+ * Obtain the value for "vibrate when ringing" setting. The default value is false.
+ *
+ * Watch out: if the setting is missing in the device, this will try obtaining the old
+ * "vibrate on ring" setting from AudioManager, and save the previous setting to the new one.
+ */
+ private boolean shouldVibrateWhenRinging() {
+ int vibrateWhenRingingSetting = Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING,
+ NO_VIBRATION_FOR_CALLS);
+ return hasVibrator() && (vibrateWhenRingingSetting == DO_VIBRATION_FOR_CALLS);
+ }
+
+ /**
+ * Obtains the value for dialpad/DTMF tones. The default value is true.
+ */
+ private boolean shouldPlayDtmfTone() {
+ int dtmfToneSetting = Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.DTMF_TONE_WHEN_DIALING,
+ PLAY_DTMF_TONE);
+ return dtmfToneSetting == PLAY_DTMF_TONE;
+ }
+
+ /**
+ * Whether the device hardware has a vibrator.
+ */
+ private boolean hasVibrator() {
+ Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
+ return vibrator != null && vibrator.hasVibrator();
+ }
+
+ private boolean shouldHideCarrierSettings() {
+ int hideCarrierNetworkSetting = Settings.Global.getInt(getActivity().getContentResolver(),
+ Settings.Global.HIDE_CARRIER_NETWORK_SETTINGS, SHOW_CARRIER_SETTINGS);
+ return hideCarrierNetworkSetting == HIDE_CARRIER_SETTINGS;
+ }
+}
diff --git a/src/com/android/dialer/util/PrivilegedCallUtil.java b/src/com/android/dialer/util/PrivilegedCallUtil.java
new file mode 100644
index 000000000..367f36566
--- /dev/null
+++ b/src/com/android/dialer/util/PrivilegedCallUtil.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.util;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+
+import com.android.contacts.common.CallUtil;
+import com.android.phone.common.PhoneConstants;
+
+/**
+ * Utilities related to calls that can only be used by system apps. These
+ * use {@link Intent#ACTION_CALL_PRIVILEGED}.
+ */
+public class PrivilegedCallUtil {
+
+ public static final String CALL_ACTION = Intent.ACTION_CALL_PRIVILEGED;
+
+ /**
+ * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined
+ * automatically.
+ */
+ public static Intent getCallIntent(String number) {
+ return getCallIntent(number, null, null);
+ }
+
+ /**
+ * Return an Intent for making a phone call. A given Uri will be used as is (without any
+ * sanity check).
+ */
+ public static Intent getCallIntent(Uri uri) {
+ return getCallIntent(uri, null, null);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(String)} but also accept a call origin.
+ * For more information about call origin, see comments in Phone package (PhoneApp).
+ */
+ public static Intent getCallIntent(String number, String callOrigin) {
+ return getCallIntent(CallUtil.getCallUri(number), callOrigin, null);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(String)} but also include {@code Account}.
+ */
+ public static Intent getCallIntent(String number, PhoneAccountHandle accountHandle) {
+ return getCallIntent(number, null, accountHandle);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(android.net.Uri)} but also include {@code Account}.
+ */
+ public static Intent getCallIntent(Uri uri, PhoneAccountHandle accountHandle) {
+ return getCallIntent(uri, null, accountHandle);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(String, String)} but also include {@code Account}.
+ */
+ public static Intent getCallIntent(
+ String number, String callOrigin, PhoneAccountHandle accountHandle) {
+ return getCallIntent(CallUtil.getCallUri(number), callOrigin, accountHandle);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(android.net.Uri)} but also accept a call
+ * origin and {@code Account}.
+ * For more information about call origin, see comments in Phone package (PhoneApp).
+ */
+ public static Intent getCallIntent(
+ Uri uri, String callOrigin, PhoneAccountHandle accountHandle) {
+ return getCallIntent(uri, callOrigin, accountHandle,
+ VideoProfile.VideoState.AUDIO_ONLY);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(String, String)} for starting a video call.
+ */
+ public static Intent getVideoCallIntent(String number, String callOrigin) {
+ return getCallIntent(CallUtil.getCallUri(number), callOrigin, null,
+ VideoProfile.VideoState.BIDIRECTIONAL);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(String, String, android.telecom.PhoneAccountHandle)} for
+ * starting a video call.
+ */
+ public static Intent getVideoCallIntent(
+ String number, String callOrigin, PhoneAccountHandle accountHandle) {
+ return getCallIntent(CallUtil.getCallUri(number), callOrigin, accountHandle,
+ VideoProfile.VideoState.BIDIRECTIONAL);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(String, String, android.telecom.PhoneAccountHandle)} for
+ * starting a video call.
+ */
+ public static Intent getVideoCallIntent(String number, PhoneAccountHandle accountHandle) {
+ return getVideoCallIntent(number, null, accountHandle);
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(android.net.Uri)} for calling Voicemail.
+ */
+ public static Intent getVoicemailIntent() {
+ return getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null));
+ }
+
+ /**
+ * A variant of {@link #getCallIntent(android.net.Uri)} but also accept a call
+ * origin and {@code Account} and {@code VideoCallProfile} state.
+ * For more information about call origin, see comments in Phone package (PhoneApp).
+ */
+ public static Intent getCallIntent(
+ Uri uri, String callOrigin, PhoneAccountHandle accountHandle, int videoState) {
+ final Intent intent = new Intent(CALL_ACTION, uri);
+ intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+ if (callOrigin != null) {
+ intent.putExtra(PhoneConstants.EXTRA_CALL_ORIGIN, callOrigin);
+ }
+ if (accountHandle != null) {
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
+ }
+
+ return intent;
+ }
+}
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
index 31db17720..0d5c3deed 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
@@ -120,6 +120,12 @@ public class VoicemailPlaybackFragment extends Fragment {
}
@Override
+ public void onViewStateRestored(Bundle savedInstanceState) {
+ mPresenter.onRestoreInstanceState(savedInstanceState);
+ super.onViewStateRestored(savedInstanceState);
+ }
+
+ @Override
public void onDestroy() {
shutdownMediaPlayer();
mPresenter.onDestroy();
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index cb246f4c8..30fea1aee 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -32,6 +32,7 @@ import com.android.dialer.R;
import com.android.dialer.util.AsyncTaskExecutor;
import com.android.ex.variablespeed.MediaPlayerProxy;
import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@@ -110,8 +111,8 @@ public class VoicemailPlaybackPresenter {
* If present in the saved instance bundle, we should not resume playback on
* create.
*/
- private static final String PAUSED_STATE_KEY = VoicemailPlaybackPresenter.class.getName()
- + ".PAUSED_STATE_KEY";
+ private static final String IS_PLAYING_STATE_KEY = VoicemailPlaybackPresenter.class.getName()
+ + ".IS_PLAYING_STATE_KEY";
/**
* If present in the saved instance bundle, indicates where to set the
* playback slider.
@@ -169,6 +170,8 @@ public class VoicemailPlaybackPresenter {
private FetchResultHandler mFetchResultHandler;
private PowerManager.WakeLock mWakeLock;
private AsyncTask<Void, ?, ?> mPrepareTask;
+ private int mPosition;
+ private boolean mPlaying;
public VoicemailPlaybackPresenter(PlaybackView view, MediaPlayerProxy player,
Uri voicemailUri, ScheduledExecutorService executorService,
@@ -346,22 +349,34 @@ public class VoicemailPlaybackPresenter {
mView.setSpeakerPhoneOn(mView.isSpeakerPhoneOn());
mView.setRateDecreaseButtonListener(createRateDecreaseListener());
mView.setRateIncreaseButtonListener(createRateIncreaseListener());
- mView.setClipPosition(0, mDuration.get());
- mView.playbackStopped();
- // Always disable on stop.
- mView.disableProximitySensor();
- if (mStartPlayingImmediately) {
- resetPrepareStartPlaying(0);
+ if (mPlaying) {
+ resetPrepareStartPlaying(mPosition);
+ } else {
+ stopPlaybackAtPosition(mPosition, mDuration.get());
+ if ((mPosition == 0) && (mStartPlayingImmediately)) {
+ resetPrepareStartPlaying(0);
+ }
}
- // TODO: Now I'm ignoring the bundle, when previously I was checking for contains against
- // the PAUSED_STATE_KEY, and CLIP_POSITION_KEY.
}
public void onSaveInstanceState(Bundle outState) {
outState.putInt(CLIP_POSITION_KEY, mView.getDesiredClipPosition());
- if (!mPlayer.isPlaying()) {
- outState.putBoolean(PAUSED_STATE_KEY, true);
+ outState.putBoolean(IS_PLAYING_STATE_KEY, mPlaying);
+ }
+
+ public void onRestoreInstanceState(Bundle inState) {
+ int position = 0;
+ boolean isPlaying = false;
+ if (inState != null) {
+ position = inState.getInt(CLIP_POSITION_KEY, 0);
+ isPlaying = inState.getBoolean(IS_PLAYING_STATE_KEY, false);
}
+ setPositionAndPlayingStatus(position, isPlaying) ;
+ }
+
+ private void setPositionAndPlayingStatus(int position, boolean isPlaying) {
+ mPosition = position;
+ mPlaying = isPlaying;
}
public void onDestroy() {
@@ -469,6 +484,7 @@ public class VoicemailPlaybackPresenter {
try {
// Can throw RejectedExecutionException
mPlayer.start();
+ setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), true);
mView.playbackStarted();
if (!mWakeLock.isHeld()) {
mWakeLock.acquire();
@@ -501,6 +517,7 @@ public class VoicemailPlaybackPresenter {
mView.playbackError(e);
mPositionUpdater.stopUpdating();
mPlayer.release();
+ setPositionAndPlayingStatus(0, false);
}
public void handleCompletion(MediaPlayer mediaPlayer) {
@@ -537,10 +554,15 @@ public class VoicemailPlaybackPresenter {
@Override
public void onStopTrackingTouch(SeekBar arg0) {
if (mPlayer.isPlaying()) {
+ setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), false);
stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
+ } else {
+ setPositionAndPlayingStatus(mView.getDesiredClipPosition(),
+ mShouldResumePlaybackAfterSeeking);
}
+
if (mShouldResumePlaybackAfterSeeking) {
- resetPrepareStartPlaying(mView.getDesiredClipPosition());
+ postSuccessfullyFetchedContent();
}
}
@@ -575,9 +597,11 @@ public class VoicemailPlaybackPresenter {
@Override
public void onClick(View arg0) {
if (mPlayer.isPlaying()) {
+ setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), false);
stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
} else {
- resetPrepareStartPlaying(mView.getDesiredClipPosition());
+ setPositionAndPlayingStatus(mPosition, true);
+ postSuccessfullyFetchedContent();
}
}
}
diff --git a/src/com/android/dialer/widget/OverlappingPaneLayout.java b/src/com/android/dialer/widget/OverlappingPaneLayout.java
deleted file mode 100644
index 167b849f2..000000000
--- a/src/com/android/dialer/widget/OverlappingPaneLayout.java
+++ /dev/null
@@ -1,1358 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.widget;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v4.view.AccessibilityDelegateCompat;
-import android.support.v4.view.MotionEventCompat;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-
-/**
- * A custom layout that aligns its child views vertically as two panes, and allows for the bottom
- * pane to be dragged upwards to overlap and hide the top pane. This layout is adapted from
- * {@link android.support.v4.widget.SlidingPaneLayout}.
- */
-public class OverlappingPaneLayout extends ViewGroup {
- private static final String TAG = "SlidingPaneLayout";
- private static final boolean DEBUG = false;
-
- /**
- * Default size of the overhang for a pane in the open state.
- * At least this much of a sliding pane will remain visible.
- * This indicates that there is more content available and provides
- * a "physical" edge to grab to pull it closed.
- */
- private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
-
- /**
- * If no fade color is given by default it will fade to 80% gray.
- */
- private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
-
- /**
- * Minimum velocity that will be detected as a fling
- */
- private static final int MIN_FLING_VELOCITY = 400; // dips per second
-
- /**
- * The size of the overhang in pixels.
- * This is the minimum section of the sliding panel that will
- * be visible in the open state to allow for a closing drag.
- */
- private final int mOverhangSize;
-
- /**
- * True if a panel can slide with the current measurements
- */
- private boolean mCanSlide;
-
- /**
- * The child view that can slide, if any.
- */
- private View mSlideableView;
-
- /**
- * The view that can be used to start the drag with.
- */
- private View mCapturableView;
-
- /**
- * How far the panel is offset from its closed position.
- * range [0, 1] where 0 = closed, 1 = open.
- */
- private float mSlideOffset;
-
- /**
- * How far the panel is offset from its closed position, in pixels.
- * range [0, {@link #mSlideRange}] where 0 is completely closed.
- */
- private int mSlideOffsetPx;
-
- /**
- * How far in pixels the slideable panel may move.
- */
- private int mSlideRange;
-
- /**
- * A panel view is locked into internal scrolling or another condition that
- * is preventing a drag.
- */
- private boolean mIsUnableToDrag;
-
- /**
- * Tracks whether or not a child view is in the process of a nested scroll.
- */
- private boolean mIsInNestedScroll;
-
- /**
- * Indicates that the layout is currently in the process of a nested pre-scroll operation where
- * the child scrolling view is being dragged downwards.
- */
- private boolean mInNestedPreScrollDownwards;
-
- /**
- * Indicates that the layout is currently in the process of a nested pre-scroll operation where
- * the child scrolling view is being dragged upwards.
- */
- private boolean mInNestedPreScrollUpwards;
-
- /**
- * Indicates that the layout is currently in the process of a fling initiated by a pre-fling
- * from the child scrolling view.
- */
- private boolean mIsInNestedFling;
-
- /**
- * Indicates the direction of the pre fling. We need to store this information since
- * OverScoller doesn't expose the direction of its velocity.
- */
- private boolean mInUpwardsPreFling;
-
- /**
- * Stores an offset used to represent a point somewhere in between the panel's fully closed
- * state and fully opened state where the panel can be temporarily pinned or opened up to
- * during scrolling.
- */
- private int mIntermediateOffset = 0;
-
- private float mInitialMotionX;
- private float mInitialMotionY;
-
- private PanelSlideCallbacks mPanelSlideCallbacks;
-
- private final ViewDragHelper mDragHelper;
-
- /**
- * Stores whether or not the pane was open the last time it was slideable.
- * If open/close operations are invoked this state is modified. Used by
- * instance state save/restore.
- */
- private boolean mPreservedOpenState;
- private boolean mFirstLayout = true;
-
- private final Rect mTmpRect = new Rect();
-
- /**
- * How many dips we need to scroll past a position before we can snap to the next position
- * on release. Using this prevents accidentally snapping to positions.
- *
- * This is needed since vertical nested scrolling can be passed to this class even if the
- * vertical scroll is less than the the nested list's touch slop.
- */
- private final int mReleaseScrollSlop;
-
- /**
- * Callbacks for interacting with sliding panes.
- */
- public interface PanelSlideCallbacks {
- /**
- * Called when a sliding pane's position changes.
- * @param panel The child view that was moved
- * @param slideOffset The new offset of this sliding pane within its range, from 0-1
- */
- public void onPanelSlide(View panel, float slideOffset);
- /**
- * Called when a sliding pane becomes slid completely open. The pane may or may not
- * be interactive at this point depending on how much of the pane is visible.
- * @param panel The child view that was slid to an open position, revealing other panes
- */
- public void onPanelOpened(View panel);
-
- /**
- * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
- * to be interactive. It may now obscure other views in the layout.
- * @param panel The child view that was slid to a closed position
- */
- public void onPanelClosed(View panel);
-
- /**
- * Called when a sliding pane is flung as far open/closed as it can be.
- * @param velocityY Velocity of the panel once its fling goes as far as it can.
- */
- public void onPanelFlingReachesEdge(int velocityY);
-
- /**
- * Returns true if the second panel's contents haven't been scrolled at all. This value is
- * used to determine whether or not we can fully expand the header on downwards scrolls.
- *
- * Instead of using this callback, it would be preferable to instead fully expand the header
- * on a View#onNestedFlingOver() callback. The behavior would be nicer. Unfortunately,
- * no such callback exists yet (b/17547693).
- */
- public boolean isScrollableChildUnscrolled();
- }
-
- public OverlappingPaneLayout(Context context) {
- this(context, null);
- }
-
- public OverlappingPaneLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public OverlappingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- final float density = context.getResources().getDisplayMetrics().density;
- mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
-
- setWillNotDraw(false);
-
- ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
-
- mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
- mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
-
- mReleaseScrollSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- /**
- * Set an offset, somewhere in between the panel's fully closed state and fully opened state,
- * where the panel can be temporarily pinned or opened up to.
- *
- * @param offset Offset in pixels
- */
- public void setIntermediatePinnedOffset(int offset) {
- mIntermediateOffset = offset;
- }
-
- /**
- * Set the view that can be used to start dragging the sliding pane.
- */
- public void setCapturableView(View capturableView) {
- mCapturableView = capturableView;
- }
-
- public void setPanelSlideCallbacks(PanelSlideCallbacks listener) {
- mPanelSlideCallbacks = listener;
- }
-
- void dispatchOnPanelSlide(View panel) {
- mPanelSlideCallbacks.onPanelSlide(panel, mSlideOffset);
- }
-
- void dispatchOnPanelOpened(View panel) {
- mPanelSlideCallbacks.onPanelOpened(panel);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
-
- void dispatchOnPanelClosed(View panel) {
- mPanelSlideCallbacks.onPanelClosed(panel);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
-
- void updateObscuredViewsVisibility(View panel) {
- final int startBound = getPaddingTop();
- final int endBound = getHeight() - getPaddingBottom();
-
- final int leftBound = getPaddingLeft();
- final int rightBound = getWidth() - getPaddingRight();
- final int left;
- final int right;
- final int top;
- final int bottom;
- if (panel != null && viewIsOpaque(panel)) {
- left = panel.getLeft();
- right = panel.getRight();
- top = panel.getTop();
- bottom = panel.getBottom();
- } else {
- left = right = top = bottom = 0;
- }
-
- for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
- final View child = getChildAt(i);
-
- if (child == panel) {
- // There are still more children above the panel but they won't be affected.
- break;
- }
-
- final int clampedChildLeft = Math.max(leftBound, child.getLeft());
- final int clampedChildRight = Math.min(rightBound, child.getRight());
- final int clampedChildTop = Math.max(startBound, child.getTop());
- final int clampedChildBottom = Math.min(endBound, child.getBottom());
-
- final int vis;
- if (clampedChildLeft >= left && clampedChildTop >= top &&
- clampedChildRight <= right && clampedChildBottom <= bottom) {
- vis = INVISIBLE;
- } else {
- vis = VISIBLE;
- }
- child.setVisibility(vis);
- }
- }
-
- void setAllChildrenVisible() {
- for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == INVISIBLE) {
- child.setVisibility(VISIBLE);
- }
- }
- }
-
- private static boolean viewIsOpaque(View v) {
- if (ViewCompat.isOpaque(v)) return true;
-
- final Drawable bg = v.getBackground();
- if (bg != null) {
- return bg.getOpacity() == PixelFormat.OPAQUE;
- }
- return false;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mFirstLayout = true;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mFirstLayout = true;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- if (widthMode != MeasureSpec.EXACTLY) {
- if (isInEditMode()) {
- // Don't crash the layout editor. Consume all of the space if specified
- // or pick a magic number from thin air otherwise.
- // TODO Better communication with tools of this bogus state.
- // It will crash on a real device.
- if (widthMode == MeasureSpec.AT_MOST) {
- widthMode = MeasureSpec.EXACTLY;
- } else if (widthMode == MeasureSpec.UNSPECIFIED) {
- widthMode = MeasureSpec.EXACTLY;
- widthSize = 300;
- }
- } else {
- throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
- }
- } else if (heightMode == MeasureSpec.UNSPECIFIED) {
- if (isInEditMode()) {
- // Don't crash the layout editor. Pick a magic number from thin air instead.
- // TODO Better communication with tools of this bogus state.
- // It will crash on a real device.
- if (heightMode == MeasureSpec.UNSPECIFIED) {
- heightMode = MeasureSpec.AT_MOST;
- heightSize = 300;
- }
- } else {
- throw new IllegalStateException("Height must not be UNSPECIFIED");
- }
- }
-
- int layoutWidth = 0;
- int maxLayoutWidth = -1;
- switch (widthMode) {
- case MeasureSpec.EXACTLY:
- layoutWidth = maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
- break;
- case MeasureSpec.AT_MOST:
- maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
- break;
- }
-
- float weightSum = 0;
- boolean canSlide = false;
- final int heightAvailable = heightSize - getPaddingTop() - getPaddingBottom();
- int heightRemaining = heightAvailable;
- final int childCount = getChildCount();
-
- if (childCount > 2) {
- Log.e(TAG, "onMeasure: More than two child views are not supported.");
- }
-
- // We'll find the current one below.
- mSlideableView = null;
-
- // First pass. Measure based on child LayoutParams width/height.
- // Weight will incur a second pass.
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- if (lp.weight > 0) {
- weightSum += lp.weight;
-
- // If we have no height, weight is the only contributor to the final size.
- // Measure this view on the weight pass only.
- if (lp.height == 0) continue;
- }
-
- int childHeightSpec;
- final int verticalMargin = lp.topMargin + lp.bottomMargin;
- if (lp.height == LayoutParams.WRAP_CONTENT) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin,
- MeasureSpec.AT_MOST);
- } else if (lp.height == LayoutParams.MATCH_PARENT) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin,
- MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
- }
-
- int childWidthSpec;
- if (lp.width == LayoutParams.WRAP_CONTENT) {
- childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
- } else if (lp.width == LayoutParams.MATCH_PARENT) {
- childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY);
- } else {
- childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
- }
-
- child.measure(childWidthSpec, childHeightSpec);
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
-
- if (widthMode == MeasureSpec.AT_MOST && childWidth > layoutWidth) {
- layoutWidth = Math.min(childWidth, maxLayoutWidth);
- }
-
- heightRemaining -= childHeight;
- canSlide |= lp.slideable = heightRemaining < 0;
- if (lp.slideable) {
- mSlideableView = child;
- }
- }
-
- // Resolve weight and make sure non-sliding panels are smaller than the full screen.
- if (canSlide || weightSum > 0) {
- final int fixedPanelHeightLimit = heightAvailable - mOverhangSize;
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- final boolean skippedFirstPass = lp.height == 0 && lp.weight > 0;
- final int measuredHeight = skippedFirstPass ? 0 : child.getMeasuredHeight();
- if (canSlide && child != mSlideableView) {
- if (lp.height < 0 && (measuredHeight > fixedPanelHeightLimit || lp.weight > 0)) {
- // Fixed panels in a sliding configuration should
- // be clamped to the fixed panel limit.
- final int childWidthSpec;
- if (skippedFirstPass) {
- // Do initial width measurement if we skipped measuring this view
- // the first time around.
- if (lp.width == LayoutParams.WRAP_CONTENT) {
- childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
- MeasureSpec.AT_MOST);
- } else if (lp.height == LayoutParams.MATCH_PARENT) {
- childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
- MeasureSpec.EXACTLY);
- } else {
- childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width,
- MeasureSpec.EXACTLY);
- }
- } else {
- childWidthSpec = MeasureSpec.makeMeasureSpec(
- child.getMeasuredWidth(), MeasureSpec.EXACTLY);
- }
- final int childHeightSpec = MeasureSpec.makeMeasureSpec(
- fixedPanelHeightLimit, MeasureSpec.EXACTLY);
- child.measure(childWidthSpec, childHeightSpec);
- }
- } else if (lp.weight > 0) {
- int childWidthSpec;
- if (lp.height == 0) {
- // This was skipped the first time; figure out a real width spec.
- if (lp.width == LayoutParams.WRAP_CONTENT) {
- childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
- MeasureSpec.AT_MOST);
- } else if (lp.width == LayoutParams.MATCH_PARENT) {
- childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
- MeasureSpec.EXACTLY);
- } else {
- childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width,
- MeasureSpec.EXACTLY);
- }
- } else {
- childWidthSpec = MeasureSpec.makeMeasureSpec(
- child.getMeasuredWidth(), MeasureSpec.EXACTLY);
- }
-
- if (canSlide) {
- // Consume available space
- final int verticalMargin = lp.topMargin + lp.bottomMargin;
- final int newHeight = heightAvailable - verticalMargin;
- final int childHeightSpec = MeasureSpec.makeMeasureSpec(
- newHeight, MeasureSpec.EXACTLY);
- if (measuredHeight != newHeight) {
- child.measure(childWidthSpec, childHeightSpec);
- }
- } else {
- // Distribute the extra width proportionally similar to LinearLayout
- final int heightToDistribute = Math.max(0, heightRemaining);
- final int addedHeight = (int) (lp.weight * heightToDistribute / weightSum);
- final int childHeightSpec = MeasureSpec.makeMeasureSpec(
- measuredHeight + addedHeight, MeasureSpec.EXACTLY);
- child.measure(childWidthSpec, childHeightSpec);
- }
- }
- }
- }
-
- final int measuredHeight = heightSize;
- final int measuredWidth = layoutWidth + getPaddingLeft() + getPaddingRight();
-
- setMeasuredDimension(measuredWidth, measuredHeight);
- mCanSlide = canSlide;
-
- if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
- // Cancel scrolling in progress, it's no longer relevant.
- mDragHelper.abort();
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP);
-
- final int height = b - t;
- final int paddingTop = getPaddingTop();
- final int paddingBottom = getPaddingBottom();
- final int paddingLeft = getPaddingLeft();
-
- final int childCount = getChildCount();
- int yStart = paddingTop;
- int nextYStart = yStart;
-
- if (mFirstLayout) {
- mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f;
- }
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- final int childHeight = child.getMeasuredHeight();
-
- if (lp.slideable) {
- final int margin = lp.topMargin + lp.bottomMargin;
- final int range = Math.min(nextYStart,
- height - paddingBottom - mOverhangSize) - yStart - margin;
- mSlideRange = range;
- final int lpMargin = lp.topMargin;
- final int pos = (int) (range * mSlideOffset);
- yStart += pos + lpMargin;
- updateSlideOffset(pos);
- } else {
- yStart = nextYStart;
- }
-
- final int childTop = yStart;
- final int childBottom = childTop + childHeight;
- final int childLeft = paddingLeft;
- final int childRight = childLeft + child.getMeasuredWidth();
-
- child.layout(childLeft, childTop, childRight, childBottom);
-
- nextYStart += child.getHeight();
- }
-
- if (mFirstLayout) {
- updateObscuredViewsVisibility(mSlideableView);
- }
-
- mFirstLayout = false;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // Recalculate sliding panes and their details
- if (h != oldh) {
- mFirstLayout = true;
- }
- }
-
- @Override
- public void requestChildFocus(View child, View focused) {
- super.requestChildFocus(child, focused);
- if (!isInTouchMode() && !mCanSlide) {
- mPreservedOpenState = child == mSlideableView;
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- final int action = MotionEventCompat.getActionMasked(ev);
-
- // Preserve the open state based on the last view that was touched.
- if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) {
- // After the first things will be slideable.
- final View secondChild = getChildAt(1);
- if (secondChild != null) {
- mPreservedOpenState = !mDragHelper.isViewUnder(secondChild,
- (int) ev.getX(), (int) ev.getY());
- }
- }
-
- if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
- if (!mIsInNestedScroll) {
- mDragHelper.cancel();
- }
- return super.onInterceptTouchEvent(ev);
- }
-
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- if (!mIsInNestedScroll) {
- mDragHelper.cancel();
- }
- return false;
- }
-
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- mIsUnableToDrag = false;
- final float x = ev.getX();
- final float y = ev.getY();
- mInitialMotionX = x;
- mInitialMotionY = y;
-
- break;
- }
-
- case MotionEvent.ACTION_MOVE: {
- final float x = ev.getX();
- final float y = ev.getY();
- final float adx = Math.abs(x - mInitialMotionX);
- final float ady = Math.abs(y - mInitialMotionY);
- final int slop = mDragHelper.getTouchSlop();
- if (ady > slop && adx > ady || !isCapturableViewUnder((int) x, (int) y)) {
- if (!mIsInNestedScroll) {
- mDragHelper.cancel();
- }
- mIsUnableToDrag = true;
- return false;
- }
- }
- }
-
- final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
-
- return interceptForDrag;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (!mCanSlide) {
- return super.onTouchEvent(ev);
- }
-
- mDragHelper.processTouchEvent(ev);
-
- final int action = ev.getAction();
- boolean wantTouchEvents = true;
-
- switch (action & MotionEventCompat.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- final float x = ev.getX();
- final float y = ev.getY();
- mInitialMotionX = x;
- mInitialMotionY = y;
- break;
- }
- }
-
- 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;
- return true;
- }
- return false;
- }
-
- private boolean openPane(View pane, int initialVelocity) {
- if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
- mPreservedOpenState = true;
- return true;
- }
- return false;
- }
-
- private void updateSlideOffset(int offsetPx) {
- mSlideOffsetPx = offsetPx;
- mSlideOffset = (float) mSlideOffsetPx / mSlideRange;
- }
-
- /**
- * Open the sliding pane if it is currently slideable. If first layout
- * has already completed this will animate.
- *
- * @return true if the pane was slideable and is now open/in the process of opening
- */
- public boolean openPane() {
- return openPane(mSlideableView, 0);
- }
-
- /**
- * Close the sliding pane if it is currently slideable. If first layout
- * has already completed this will animate.
- *
- * @return true if the pane was slideable and is now closed/in the process of closing
- */
- public boolean closePane() {
- return closePane(mSlideableView, 0);
- }
-
- /**
- * Check if the layout is open. It can be open either because the slider
- * itself is open revealing the left pane, or if all content fits without sliding.
- *
- * @return true if sliding panels are open
- */
- public boolean isOpen() {
- return !mCanSlide || mSlideOffset > 0;
- }
-
- /**
- * Check if the content in this layout cannot fully fit side by side and therefore
- * the content pane can be slid back and forth.
- *
- * @return true if content in this layout can be slid open and closed
- */
- public boolean isSlideable() {
- return mCanSlide;
- }
-
- private void onPanelDragged(int newTop) {
- if (mSlideableView == null) {
- // This can happen if we're aborting motion during layout because everything now fits.
- mSlideOffset = 0;
- return;
- }
- final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-
- final int lpMargin = lp.topMargin;
- final int topBound = getPaddingTop() + lpMargin;
-
- updateSlideOffset(newTop - topBound);
-
- dispatchOnPanelSlide(mSlideableView);
- }
-
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- boolean result;
- final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
-
- if (mCanSlide && !lp.slideable && mSlideableView != null) {
- // Clip against the slider; no sense drawing what will immediately be covered.
- canvas.getClipBounds(mTmpRect);
-
- mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
- canvas.clipRect(mTmpRect);
- }
-
- if (Build.VERSION.SDK_INT >= 11) { // HC
- result = super.drawChild(canvas, child, drawingTime);
- } else {
- if (child.isDrawingCacheEnabled()) {
- child.setDrawingCacheEnabled(false);
- }
- result = super.drawChild(canvas, child, drawingTime);
- }
-
- canvas.restoreToCount(save);
-
- return result;
- }
-
- /**
- * Smoothly animate mDraggingPane to the target X position within its range.
- *
- * @param slideOffset position to animate to
- * @param velocity initial velocity in case of fling, or 0.
- */
- boolean smoothSlideTo(float slideOffset, int velocity) {
- if (!mCanSlide) {
- // Nothing to do.
- return false;
- }
-
- final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-
- int y;
- int topBound = getPaddingTop() + lp.topMargin;
- y = (int) (topBound + slideOffset * mSlideRange);
-
- if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
- setAllChildrenVisible();
- ViewCompat.postInvalidateOnAnimation(this);
- return true;
- }
- return false;
- }
-
- @Override
- public void computeScroll() {
- if (mDragHelper.continueSettling(/* deferCallbacks = */ false)) {
- if (!mCanSlide) {
- mDragHelper.abort();
- return;
- }
-
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
-
- private boolean isCapturableViewUnder(int x, int y) {
- View capturableView = mCapturableView != null ? mCapturableView : mSlideableView;
- if (capturableView == null) {
- return false;
- }
- int[] viewLocation = new int[2];
- capturableView.getLocationOnScreen(viewLocation);
- int[] parentLocation = new int[2];
- this.getLocationOnScreen(parentLocation);
- int screenX = parentLocation[0] + x;
- int screenY = parentLocation[1] + y;
- return screenX >= viewLocation[0]
- && screenX < viewLocation[0] + capturableView.getWidth()
- && screenY >= viewLocation[1]
- && screenY < viewLocation[1] + capturableView.getHeight();
- }
-
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams();
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof MarginLayoutParams
- ? new LayoutParams((MarginLayoutParams) p)
- : new LayoutParams(p);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams && super.checkLayoutParams(p);
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
-
- SavedState ss = new SavedState(superState);
- ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
-
- return ss;
- }
-
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
-
- if (ss.isOpen) {
- openPane();
- } else {
- closePane();
- }
- mPreservedOpenState = ss.isOpen;
- }
-
- @Override
- public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
- final boolean startNestedScroll = (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
- if (startNestedScroll) {
- mIsInNestedScroll = true;
- mDragHelper.startNestedScroll(mSlideableView);
- }
- if (DEBUG) {
- Log.d(TAG, "onStartNestedScroll: " + startNestedScroll);
- }
- return startNestedScroll;
- }
-
- @Override
- public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
- if (dy == 0) {
- // Nothing to do
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onNestedPreScroll: " + dy);
- }
-
- mInNestedPreScrollDownwards = dy < 0;
- mInNestedPreScrollUpwards = dy > 0;
- mIsInNestedFling = false;
- mDragHelper.processNestedScroll(mSlideableView, 0, -dy, consumed);
- }
-
- @Override
- public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
- if (!(velocityY > 0 && mSlideOffsetPx != 0
- || velocityY < 0 && mSlideOffsetPx < mIntermediateOffset
- || velocityY < 0 && mSlideOffsetPx < mSlideRange
- && mPanelSlideCallbacks.isScrollableChildUnscrolled())) {
- // No need to consume the fling if the fling won't collapse or expand the header.
- // How far we are willing to expand the header depends on isScrollableChildUnscrolled().
- return false;
- }
-
- if (DEBUG) {
- Log.d(TAG, "onNestedPreFling: " + velocityY);
- }
- mInUpwardsPreFling = velocityY > 0;
- mIsInNestedFling = true;
- mIsInNestedScroll = false;
- mDragHelper.processNestedFling(mSlideableView, (int) -velocityY);
- return true;
- }
-
- @Override
- public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
- int dyUnconsumed) {
- if (DEBUG) {
- Log.d(TAG, "onNestedScroll: " + dyUnconsumed);
- }
- mIsInNestedFling = false;
- mDragHelper.processNestedScroll(mSlideableView, 0, -dyUnconsumed, null);
- }
-
- @Override
- public void onStopNestedScroll(View child) {
- if (DEBUG) {
- Log.d(TAG, "onStopNestedScroll");
- }
- if (mIsInNestedScroll && !mIsInNestedFling) {
- mDragHelper.stopNestedScroll(mSlideableView);
- mInNestedPreScrollDownwards = false;
- mInNestedPreScrollUpwards = false;
- mIsInNestedScroll = false;
- }
- }
-
- private class DragHelperCallback extends ViewDragHelper.Callback {
-
- @Override
- public boolean tryCaptureView(View child, int pointerId) {
- if (mIsUnableToDrag) {
- return false;
- }
-
- return ((LayoutParams) child.getLayoutParams()).slideable;
- }
-
- @Override
- public void onViewDragStateChanged(int state) {
- if (DEBUG) {
- Log.d(TAG, "onViewDragStateChanged: " + state);
- }
-
- if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
- if (mSlideOffset == 0) {
- updateObscuredViewsVisibility(mSlideableView);
- dispatchOnPanelClosed(mSlideableView);
- mPreservedOpenState = false;
- } else {
- dispatchOnPanelOpened(mSlideableView);
- mPreservedOpenState = true;
- }
- }
-
- if (state == ViewDragHelper.STATE_IDLE
- && mDragHelper.getVelocityMagnitude() > 0
- && mIsInNestedFling) {
- mIsInNestedFling = false;
- final int flingVelocity = !mInUpwardsPreFling ?
- -mDragHelper.getVelocityMagnitude() : mDragHelper.getVelocityMagnitude();
- mPanelSlideCallbacks.onPanelFlingReachesEdge(flingVelocity);
- }
- }
-
- @Override
- public void onViewCaptured(View capturedChild, int activePointerId) {
- // Make all child views visible in preparation for sliding things around
- setAllChildrenVisible();
- }
-
- @Override
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- onPanelDragged(top);
- invalidate();
- }
-
- @Override
- public void onViewFling(View releasedChild, float xVelocity, float yVelocity) {
- if (releasedChild == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onViewFling: " + yVelocity);
- }
-
- // Flings won't always fully expand or collapse the header. Instead of performing the
- // fling and then waiting for the fling to end before snapping into place, we
- // immediately snap into place if we predict the fling won't fully expand or collapse
- // the header.
- int yOffsetPx = mDragHelper.predictFlingYOffset((int) yVelocity);
- if (yVelocity < 0) {
- // Only perform a fling if we know the fling will fully compress the header.
- if (-yOffsetPx > mSlideOffsetPx) {
- mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0,
- mSlideRange, Integer.MAX_VALUE, (int) yVelocity);
- } else {
- mIsInNestedFling = false;
- onViewReleased(releasedChild, xVelocity, yVelocity);
- }
- } else {
- // Only perform a fling if we know the fling will expand the header as far
- // as it can possible be expanded, given the isScrollableChildUnscrolled() value.
- if (yOffsetPx + mSlideOffsetPx >= mSlideRange
- && mPanelSlideCallbacks.isScrollableChildUnscrolled()) {
- mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0,
- Integer.MAX_VALUE, mSlideRange, (int) yVelocity);
- } else if (yOffsetPx + mSlideOffsetPx >= mIntermediateOffset
- && mSlideOffsetPx <= mIntermediateOffset
- && !mPanelSlideCallbacks.isScrollableChildUnscrolled()) {
- mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0,
- Integer.MAX_VALUE, mIntermediateOffset, (int) yVelocity);
- } else {
- mIsInNestedFling = false;
- onViewReleased(releasedChild, xVelocity, yVelocity);
- }
- }
-
- mInNestedPreScrollDownwards = false;
- mInNestedPreScrollUpwards = false;
-
- // Without this invalidate, some calls to flingCapturedView can have no affect.
- invalidate();
- }
-
- @Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
- if (DEBUG) {
- Log.d(TAG, "onViewReleased: "
- + " mIsInNestedFling=" + mIsInNestedFling
- + " unscrolled=" + mPanelSlideCallbacks.isScrollableChildUnscrolled()
- + ", mInNestedPreScrollDownwards = " + mInNestedPreScrollDownwards
- + ", mInNestedPreScrollUpwards = " + mInNestedPreScrollUpwards
- + ", yvel=" + yvel);
- }
- if (releasedChild == null) {
- return;
- }
-
- final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
- int top = getPaddingTop() + lp.topMargin;
-
- // Decide where to snap to according to the current direction of motion and the current
- // position. The velocity's magnitude has no bearing on this.
- if (mInNestedPreScrollDownwards || yvel > 0) {
- // Scrolling downwards
- if (mSlideOffsetPx > mIntermediateOffset + mReleaseScrollSlop) {
- top += mSlideRange;
- } else if (mSlideOffsetPx > mReleaseScrollSlop) {
- top += mIntermediateOffset;
- } else {
- // Offset is very close to 0
- }
- } else if (mInNestedPreScrollUpwards || yvel < 0) {
- // Scrolling upwards
- if (mSlideOffsetPx > mSlideRange - mReleaseScrollSlop) {
- // Offset is very close to mSlideRange
- top += mSlideRange;
- } else if (mSlideOffsetPx > mIntermediateOffset - mReleaseScrollSlop) {
- // Offset is between mIntermediateOffset and mSlideRange.
- top += mIntermediateOffset;
- } else {
- // Offset is between 0 and mIntermediateOffset.
- }
- } else {
- // Not moving upwards or downwards. This case can only be triggered when directly
- // dragging the tabs. We don't bother to remember previous scroll direction
- // when directly dragging the tabs.
- if (0 <= mSlideOffsetPx && mSlideOffsetPx <= mIntermediateOffset / 2) {
- // Offset is between 0 and mIntermediateOffset, but closer to 0
- // Leave top unchanged
- } else if (mIntermediateOffset / 2 <= mSlideOffsetPx
- && mSlideOffsetPx <= (mIntermediateOffset + mSlideRange) / 2) {
- // Offset is closest to mIntermediateOffset
- top += mIntermediateOffset;
- } else {
- // Offset is between mIntermediateOffset and mSlideRange, but closer to
- // mSlideRange
- top += mSlideRange;
- }
- }
-
- mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
- invalidate();
- }
-
- @Override
- public int getViewVerticalDragRange(View child) {
- return mSlideRange;
- }
-
- @Override
- public int clampViewPositionHorizontal(View child, int left, int dx) {
- // Make sure we never move views horizontally.
- return child.getLeft();
- }
-
- @Override
- public int clampViewPositionVertical(View child, int top, int dy) {
- final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-
- final int newTop;
- int previousTop = top - dy;
- int topBound = getPaddingTop() + lp.topMargin;
- int bottomBound = topBound + (mPanelSlideCallbacks.isScrollableChildUnscrolled()
- || !mIsInNestedScroll ? mSlideRange : mIntermediateOffset);
- if (previousTop > bottomBound) {
- // We were previously below the bottomBound, so loosen the bottomBound so that this
- // makes sense. This can occur after the view was directly dragged by the tabs.
- bottomBound = Math.max(bottomBound, mSlideRange);
- }
- newTop = Math.min(Math.max(top, topBound), bottomBound);
-
- return newTop;
- }
-
- @Override
- public void onEdgeDragStarted(int edgeFlags, int pointerId) {
- mDragHelper.captureChildView(mSlideableView, pointerId);
- }
- }
-
- public static class LayoutParams extends ViewGroup.MarginLayoutParams {
- private static final int[] ATTRS = new int[] {
- android.R.attr.layout_weight
- };
-
- /**
- * The weighted proportion of how much of the leftover space
- * this child should consume after measurement.
- */
- public float weight = 0;
-
- /**
- * True if this pane is the slideable pane in the layout.
- */
- boolean slideable;
-
- public LayoutParams() {
- super(FILL_PARENT, FILL_PARENT);
- }
-
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- public LayoutParams(android.view.ViewGroup.LayoutParams source) {
- super(source);
- }
-
- public LayoutParams(MarginLayoutParams source) {
- super(source);
- }
-
- public LayoutParams(LayoutParams source) {
- super(source);
- this.weight = source.weight;
- }
-
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
-
- final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
- this.weight = a.getFloat(0, 0);
- a.recycle();
- }
-
- }
-
- static class SavedState extends BaseSavedState {
- boolean isOpen;
-
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- isOpen = in.readInt() != 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(isOpen ? 1 : 0);
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-
- class AccessibilityDelegate extends AccessibilityDelegateCompat {
- private final Rect mTmpRect = new Rect();
-
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
- super.onInitializeAccessibilityNodeInfo(host, superNode);
- copyNodeInfoNoChildren(info, superNode);
- superNode.recycle();
-
- info.setClassName(OverlappingPaneLayout.class.getName());
- info.setSource(host);
-
- final ViewParent parent = ViewCompat.getParentForAccessibility(host);
- if (parent instanceof View) {
- info.setParent((View) parent);
- }
-
- // This is a best-approximation of addChildrenForAccessibility()
- // that accounts for filtering.
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == View.VISIBLE) {
- // Force importance to "yes" since we can't read the value.
- ViewCompat.setImportantForAccessibility(
- child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
- info.addChild(child);
- }
- }
- }
-
- @Override
- public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(host, event);
-
- event.setClassName(OverlappingPaneLayout.class.getName());
- }
-
- /**
- * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
- * seem to be a few elements that are not easily cloneable using the underlying API.
- * Leave it private here as it's not general-purpose useful.
- */
- private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
- AccessibilityNodeInfoCompat src) {
- final Rect rect = mTmpRect;
-
- src.getBoundsInParent(rect);
- dest.setBoundsInParent(rect);
-
- src.getBoundsInScreen(rect);
- dest.setBoundsInScreen(rect);
-
- dest.setVisibleToUser(src.isVisibleToUser());
- dest.setPackageName(src.getPackageName());
- dest.setClassName(src.getClassName());
- dest.setContentDescription(src.getContentDescription());
-
- dest.setEnabled(src.isEnabled());
- dest.setClickable(src.isClickable());
- dest.setFocusable(src.isFocusable());
- dest.setFocused(src.isFocused());
- dest.setAccessibilityFocused(src.isAccessibilityFocused());
- dest.setSelected(src.isSelected());
- dest.setLongClickable(src.isLongClickable());
-
- dest.addAction(src.getActions());
-
- dest.setMovementGranularities(src.getMovementGranularities());
- }
- }
-}
diff --git a/src/com/android/dialerbind/ObjectFactory.java b/src/com/android/dialerbind/ObjectFactory.java
index 54a5821df..dfacd3f6d 100644
--- a/src/com/android/dialerbind/ObjectFactory.java
+++ b/src/com/android/dialerbind/ObjectFactory.java
@@ -42,15 +42,15 @@ public class ObjectFactory {
* @param context The context to use.
* @param callFetcher Instance of call fetcher to use.
* @param contactInfoHelper Instance of contact info helper class to use.
- * @param isCallLog Is this call log adapter being used on the call log?
* @return Instance of CallLogAdapter.
*/
- public static CallLogAdapter newCallLogAdapter(Context context,
- CallFetcher callFetcher, ContactInfoHelper contactInfoHelper,
- CallItemExpandedListener callItemExpandedListener,
- OnReportButtonClickListener onReportButtonClickListener, boolean isCallLog) {
- return new CallLogAdapter(context, callFetcher, contactInfoHelper,
- callItemExpandedListener, onReportButtonClickListener, isCallLog);
+ public static CallLogAdapter newCallLogAdapter(
+ Context context,
+ CallFetcher callFetcher,
+ ContactInfoHelper contactInfoHelper,
+ OnReportButtonClickListener onReportButtonClickListener) {
+ return new CallLogAdapter(
+ context, callFetcher, contactInfoHelper, onReportButtonClickListener);
}
public static DialogFragment getReportDialogFragment(String number) {