merge in nakasi-factoryrom-release history after reset to jb-dev
diff --git a/proguard.flags b/proguard.flags
index 79378e6..39784b1 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -9,10 +9,12 @@
public void *(android.view.MenuItem);
}
-# Any class or method annotated with NeededForTesting.
+# Any class or method annotated with NeededForTesting or NeededForReflection.
-keep @com.android.contacts.test.NeededForTesting class *
+-keep @com.android.contacts.test.NeededForReflection class *
-keepclassmembers class * {
@com.android.contacts.test.NeededForTesting *;
+@com.android.contacts.test.NeededForReflection *;
}
-verbose
diff --git a/res/drawable-hdpi/spinner_default_holo_dark.9.png b/res/drawable-hdpi/spinner_default_holo_dark.9.png
new file mode 100644
index 0000000..34a88df
--- /dev/null
+++ b/res/drawable-hdpi/spinner_default_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/spinner_default_holo_dark.9.png b/res/drawable-mdpi/spinner_default_holo_dark.9.png
new file mode 100644
index 0000000..48af192
--- /dev/null
+++ b/res/drawable-mdpi/spinner_default_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/spinner_default_holo_dark.9.png b/res/drawable-xhdpi/spinner_default_holo_dark.9.png
new file mode 100644
index 0000000..e94ce80
--- /dev/null
+++ b/res/drawable-xhdpi/spinner_default_holo_dark.9.png
Binary files differ
diff --git a/res/drawable/ab_dropdown_navigation_item_background.xml b/res/drawable/ab_dropdown_navigation_item_background.xml
new file mode 100644
index 0000000..c144996
--- /dev/null
+++ b/res/drawable/ab_dropdown_navigation_item_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/ab_dropdown_navigation_item_background_pressed"/>
+ <item android:drawable="@drawable/spinner_default_holo_dark" />
+</selector>
diff --git a/res/drawable/ab_dropdown_navigation_item_background_pressed.xml b/res/drawable/ab_dropdown_navigation_item_background_pressed.xml
new file mode 100644
index 0000000..d075a05
--- /dev/null
+++ b/res/drawable/ab_dropdown_navigation_item_background_pressed.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:drawable="@drawable/action_bar_item_pressed_holo_light" />
+ <item android:drawable="@drawable/spinner_default_holo_dark" />
+</layer-list>
diff --git a/res/drawable/action_bar_item_background.xml b/res/drawable/action_bar_item_background.xml
index 8dd8d9b..7ccc3a9 100644
--- a/res/drawable/action_bar_item_background.xml
+++ b/res/drawable/action_bar_item_background.xml
@@ -14,8 +14,7 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/action_bar_item_pressed_holo_light"/>
<item android:drawable="@android:color/transparent" />
</selector>
diff --git a/res/drawable/group_list_item_background.xml b/res/drawable/group_list_item_background.xml
index 345117f..70cd308 100644
--- a/res/drawable/group_list_item_background.xml
+++ b/res/drawable/group_list_item_background.xml
@@ -14,8 +14,7 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
<item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo_light" />
<item android:state_focused="true" android:drawable="@drawable/list_focused_holo" />
diff --git a/res/layout-sw580dp-w940dp/quickcontact_activity.xml b/res/layout-sw580dp-w940dp/quickcontact_activity.xml
new file mode 100644
index 0000000..5c38309
--- /dev/null
+++ b/res/layout-sw580dp-w940dp/quickcontact_activity.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<view
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.contacts.quickcontact.FloatingChildLayout"
+ android:id="@+id/floating_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="afterDescendants">
+ <LinearLayout
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="32dip"
+ android:orientation="horizontal">
+ <FrameLayout
+ android:layout_width="360dip"
+ android:layout_height="360dip">
+ <include layout="@layout/quickcontact_photo_container" />
+ </FrameLayout>
+ <LinearLayout
+ android:layout_width="360dip"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <include layout="@layout/quickcontact_track" />
+ <View
+ android:id="@+id/line_after_track"
+ android:layout_width="match_parent"
+ android:layout_height="2dip"
+ android:background="@color/quickcontact_tab_indicator" />
+ <android.support.v4.view.ViewPager
+ android:id="@+id/item_list_pager"
+ android:background="@color/quickcontact_list_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </LinearLayout>
+ </LinearLayout>
+</view>
diff --git a/res/layout-sw680dp-w1000dp/contact_detail_container.xml b/res/layout-sw680dp-w1000dp/contact_detail_container.xml
index 9e1eb33..8180e92 100644
--- a/res/layout-sw680dp-w1000dp/contact_detail_container.xml
+++ b/res/layout-sw680dp-w1000dp/contact_detail_container.xml
@@ -54,6 +54,7 @@
android:id="@+id/updates_fragment_container"
android:layout_width="0dip"
android:layout_weight="2"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/res/layout-sw680dp-w1000dp/quickcontact_activity.xml b/res/layout-sw680dp-w1000dp/quickcontact_activity.xml
new file mode 100644
index 0000000..5c38309
--- /dev/null
+++ b/res/layout-sw680dp-w1000dp/quickcontact_activity.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<view
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.contacts.quickcontact.FloatingChildLayout"
+ android:id="@+id/floating_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="afterDescendants">
+ <LinearLayout
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="32dip"
+ android:orientation="horizontal">
+ <FrameLayout
+ android:layout_width="360dip"
+ android:layout_height="360dip">
+ <include layout="@layout/quickcontact_photo_container" />
+ </FrameLayout>
+ <LinearLayout
+ android:layout_width="360dip"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <include layout="@layout/quickcontact_track" />
+ <View
+ android:id="@+id/line_after_track"
+ android:layout_width="match_parent"
+ android:layout_height="2dip"
+ android:background="@color/quickcontact_tab_indicator" />
+ <android.support.v4.view.ViewPager
+ android:id="@+id/item_list_pager"
+ android:background="@color/quickcontact_list_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </LinearLayout>
+ </LinearLayout>
+</view>
diff --git a/res/layout-sw680dp/quickcontact_activity.xml b/res/layout-sw680dp/quickcontact_activity.xml
new file mode 100644
index 0000000..8843fc9
--- /dev/null
+++ b/res/layout-sw680dp/quickcontact_activity.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<view
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
+ class="com.android.contacts.quickcontact.FloatingChildLayout"
+ android:id="@+id/floating_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="afterDescendants">
+ <LinearLayout
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="32dip"
+ android:orientation="vertical" >
+ <FrameLayout
+ android:layout_width="360dip"
+ android:layout_height="@dimen/quick_contact_photo_container_height">
+ <include layout="@layout/quickcontact_photo_container" />
+ </FrameLayout>
+ <include layout="@layout/quickcontact_track" />
+ <View
+ android:id="@+id/line_after_track"
+ android:layout_width="match_parent"
+ android:layout_height="2dip"
+ android:background="@color/quickcontact_tab_indicator" />
+ <android.support.v4.view.ViewPager
+ android:id="@+id/item_list_pager"
+ android:layout_width="match_parent"
+ android:layout_height="160dip" />
+ </LinearLayout>
+</view>
diff --git a/res/layout/contact_detail_updates_fragment_container.xml b/res/layout/contact_detail_updates_fragment_container.xml
index fb3a8ac..7414f61 100644
--- a/res/layout/contact_detail_updates_fragment_container.xml
+++ b/res/layout/contact_detail_updates_fragment_container.xml
@@ -22,4 +22,5 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/updates_fragment_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
\ No newline at end of file
+ android:layout_height="match_parent"
+ android:visibility="gone" />
diff --git a/res/values-sw580dp-w940dp/styles.xml b/res/values-sw580dp-w940dp/styles.xml
index 07ef912..d035240 100644
--- a/res/values-sw580dp-w940dp/styles.xml
+++ b/res/values-sw580dp-w940dp/styles.xml
@@ -35,6 +35,7 @@
<item name="list_item_gap_between_label_and_data">5dip</item>
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">4dip</item>
+ <item name="list_item_presence_icon_size">16dip</item>
<item name="list_item_photo_size">64dip</item>
<item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_prefix_highlight_color">@color/people_app_theme_color</item>
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index 7a247ae..a8d935b 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -35,6 +35,7 @@
<item name="list_item_gap_between_label_and_data">5dip</item>
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">4dip</item>
+ <item name="list_item_presence_icon_size">16dip</item>
<item name="list_item_photo_size">64dip</item>
<item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_prefix_highlight_color">@color/people_app_theme_color</item>
diff --git a/res/values-sw680dp/styles.xml b/res/values-sw680dp/styles.xml
index 3db3867..fb242d9 100644
--- a/res/values-sw680dp/styles.xml
+++ b/res/values-sw680dp/styles.xml
@@ -35,6 +35,7 @@
<item name="list_item_gap_between_label_and_data">5dip</item>
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">4dip</item>
+ <item name="list_item_presence_icon_size">16dip</item>
<item name="list_item_photo_size">64dip</item>
<item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_prefix_highlight_color">@color/people_app_theme_color</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 93c130f..e24a7c3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1573,6 +1573,9 @@
<item quantity="other"><xliff:g id="count">%1$d</xliff:g> Voicemails</item>
</plurals>
+ <!-- Used in the notification of a new voicemail for the action to play the voicemail. -->
+ <string name="notification_action_voicemail_play">Play</string>
+
<!-- Used to build a list of names or phone numbers, to indicate the callers who left
voicemails.
The first argument may be one or more callers, the most recent ones.
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1702585..829b207 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -176,6 +176,7 @@
</style>
<style name="ContactsActionBarDropDownStyle" parent="@android:style/Widget.Holo.Light.Spinner">
+ <item name="android:background">@drawable/ab_dropdown_navigation_item_background</item>
</style>
<style name="ContactsActionBarTheme" parent="@android:style/Theme.Holo">
@@ -351,14 +352,10 @@
<style name="PeopleNavigationDropDownTextAppearance">
<item name="android:textColor">#333333</item>
<item name="android:textSize">18sp</item>
- <item name="android:textStyle">normal</item>
- <item name="android:textAllCaps">false</item>
</style>
<style name="PeopleNavigationDropDownHeaderTextAppearance">
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
- <item name="android:textSize">14sp</item>
- <item name="android:textStyle">bold</item>
- <item name="android:textAllCaps">true</item>
+ <item name="android:textColor">#cdffffff</item>
+ <item name="android:textSize">18sp</item>
</style>
</resources>
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 1fac6c8..47441ce 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -97,6 +97,8 @@
public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI";
/** If we should immediately start playback of the voicemail, this extra will be set to true. */
public static final String EXTRA_VOICEMAIL_START_PLAYBACK = "EXTRA_VOICEMAIL_START_PLAYBACK";
+ /** If the activity was triggered from a notification. */
+ public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION";
private CallTypeHelper mCallTypeHelper;
private PhoneNumberHelper mPhoneNumberHelper;
@@ -273,6 +275,9 @@
mContactInfoHelper = new ContactInfoHelper(this, ContactsUtils.getCurrentCountryIso(this));
configureActionBar();
optionallyHandleVoicemail();
+ if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
+ closeSystemDialogs();
+ }
}
@Override
@@ -879,6 +884,10 @@
mPhoneNumberActionMode = startActionMode(new PhoneNumberActionModeCallback(targetView));
}
+ private void closeSystemDialogs() {
+ sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
private class PhoneNumberActionModeCallback implements ActionMode.Callback {
private final View mTargetView;
private final Drawable mOriginalViewBackground;
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 0502d4d..075c59f 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -150,7 +150,7 @@
private ContactsUnavailableFragment mContactsUnavailableFragment;
private ProviderStatusWatcher mProviderStatusWatcher;
- private int mProviderStatus;
+ private ProviderStatusWatcher.Status mProviderStatus;
private boolean mOptionsMenuContactsAvailable;
@@ -217,7 +217,8 @@
}
public boolean areContactsAvailable() {
- return mProviderStatus == ProviderStatus.STATUS_NORMAL;
+ return (mProviderStatus != null)
+ && mProviderStatus.status == ProviderStatus.STATUS_NORMAL;
}
private boolean areContactWritableAccountsAvailable() {
@@ -991,14 +992,15 @@
}
private void updateViewConfiguration(boolean forceUpdate) {
- int providerStatus = mProviderStatusWatcher.getProviderStatus();
- if (!forceUpdate && (providerStatus == mProviderStatus)) return;
+ ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus();
+ if (!forceUpdate && (mProviderStatus != null)
+ && (providerStatus.status == mProviderStatus.status)) return;
mProviderStatus = providerStatus;
View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
View mainView = findViewById(R.id.main_view);
- if (mProviderStatus == ProviderStatus.STATUS_NORMAL) {
+ if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) {
// Ensure that the mTabPager is visible; we may have made it invisible below.
contactsUnavailableView.setVisibility(View.GONE);
if (mTabPager != null) {
@@ -1033,9 +1035,8 @@
getFragmentManager().beginTransaction()
.replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
.commitAllowingStateLoss();
- } else {
- mContactsUnavailableFragment.update();
}
+ mContactsUnavailableFragment.updateStatus(mProviderStatus);
// Show the contactsUnavailableView, and hide the mTabPager so that we don't
// see it sliding in underneath the contactsUnavailableView at the edges.
diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
index 59dfcd4..1c2a595 100644
--- a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
@@ -152,14 +152,13 @@
// TODO: Use the photo of contact if all calls are from the same person.
final int icon = android.R.drawable.stat_notify_voicemail;
- Notification notification = new Notification.Builder(mContext)
+ Notification.Builder notificationBuilder = new Notification.Builder(mContext)
.setSmallIcon(icon)
.setContentTitle(title)
.setContentText(callers)
.setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0)
.setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
- .setAutoCancel(true)
- .getNotification();
+ .setAutoCancel(true);
// Determine the intent to fire when the notification is clicked on.
final Intent contentIntent;
@@ -169,19 +168,29 @@
contentIntent.setData(newCalls[0].callsUri);
contentIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
newCalls[0].voicemailUri);
+ Intent playIntent = new Intent(mContext, CallDetailActivity.class);
+ playIntent.setData(newCalls[0].callsUri);
+ playIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
+ newCalls[0].voicemailUri);
+ playIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, true);
+ playIntent.putExtra(CallDetailActivity.EXTRA_FROM_NOTIFICATION, true);
+ notificationBuilder.addAction(R.drawable.ic_play_holo_dark,
+ resources.getString(R.string.notification_action_voicemail_play),
+ PendingIntent.getActivity(mContext, 0, playIntent, 0));
} else {
// Open the call log.
contentIntent = new Intent(Intent.ACTION_VIEW, Calls.CONTENT_URI);
}
- notification.contentIntent = PendingIntent.getActivity(mContext, 0, contentIntent, 0);
+ notificationBuilder.setContentIntent(
+ PendingIntent.getActivity(mContext, 0, contentIntent, 0));
// The text to show in the ticker, describing the new event.
if (callToNotify != null) {
- notification.tickerText = resources.getString(
- R.string.notification_new_voicemail_ticker, names.get(callToNotify.number));
+ notificationBuilder.setTicker(resources.getString(
+ R.string.notification_new_voicemail_ticker, names.get(callToNotify.number)));
}
- mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+ mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notificationBuilder.build());
}
/** Creates a pending intent that marks all new voicemails as old. */
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 89585e8..745c044 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -73,6 +73,7 @@
import com.android.contacts.activities.DialtactsActivity;
import com.android.contacts.util.Constants;
import com.android.contacts.util.PhoneNumberFormatter;
+import com.android.contacts.util.StopWatch;
import com.android.internal.telephony.ITelephony;
import com.android.phone.CallLogAsync;
import com.android.phone.HapticFeedback;
@@ -468,17 +469,25 @@
public void onResume() {
super.onResume();
+ final StopWatch stopWatch = StopWatch.start("Dialpad.onResume");
+
// Query the last dialed number. Do it first because hitting
// the DB is 'slow'. This call is asynchronous.
queryLastOutgoingCall();
+ stopWatch.lap("qloc");
+
// retrieve the DTMF tone play back setting.
mDTMFToneEnabled = Settings.System.getInt(getActivity().getContentResolver(),
Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
+ stopWatch.lap("dtwd");
+
// Retrieve the haptic feedback setting.
mHaptic.checkSystemSetting();
+ stopWatch.lap("hptc");
+
// if the mToneGenerator creation fails, just continue without it. It is
// a local audio signal, and is not as important as the dtmf tone itself.
synchronized (mToneGeneratorLock) {
@@ -491,6 +500,7 @@
}
}
}
+ stopWatch.lap("tg");
// Prevent unnecessary confusion. Reset the press count anyway.
mDialpadPressCount = 0;
@@ -501,6 +511,8 @@
fillDigitsIfNecessary(parent.getIntent());
}
+ stopWatch.lap("fdin");
+
// While we're in the foreground, listen for phone state changes,
// purely so that we can take down the "dialpad chooser" if the
// phone becomes idle while the chooser UI is visible.
@@ -508,6 +520,8 @@
(TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+ stopWatch.lap("tm");
+
// Potentially show hint text in the mDigits field when the user
// hasn't typed any digits yet. (If there's already an active call,
// this hint text will remind the user that he's about to add a new
@@ -531,7 +545,13 @@
showDialpadChooser(false);
}
+ stopWatch.lap("hnt");
+
updateDialAndDeleteButtonEnabledState();
+
+ stopWatch.lap("bes");
+
+ stopWatch.stopAndLog(TAG, 50);
}
@Override
diff --git a/src/com/android/contacts/list/ContactsUnavailableFragment.java b/src/com/android/contacts/list/ContactsUnavailableFragment.java
index becc704..b645425 100644
--- a/src/com/android/contacts/list/ContactsUnavailableFragment.java
+++ b/src/com/android/contacts/list/ContactsUnavailableFragment.java
@@ -18,6 +18,7 @@
import com.android.contacts.R;
import android.app.Fragment;
+import android.content.Context;
import android.os.Bundle;
import android.provider.ContactsContract.ProviderStatus;
import android.view.Gravity;
@@ -35,8 +36,6 @@
*/
public class ContactsUnavailableFragment extends Fragment implements OnClickListener {
- private ProviderStatusWatcher mProviderStatusWatcher;
-
private View mView;
private TextView mMessageView;
private TextView mSecondaryMessageView;
@@ -51,10 +50,11 @@
private OnContactsUnavailableActionListener mListener;
+ private ProviderStatusWatcher.Status mProviderStatus;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mProviderStatusWatcher = ProviderStatusWatcher.getInstance(getActivity());
}
@Override
@@ -74,7 +74,11 @@
mRetryUpgradeButton = (Button) mView.findViewById(R.id.import_failure_retry_button);
mRetryUpgradeButton.setOnClickListener(this);
mProgress = (ProgressBar) mView.findViewById(R.id.progress);
- update();
+
+ if (mProviderStatus != null) {
+ updateStatus(mProviderStatus);
+ }
+
return mView;
}
@@ -83,9 +87,13 @@
mListener = listener;
}
- public void update() {
- int providerStatus = mProviderStatusWatcher.getProviderStatus();
- switch (providerStatus) {
+ public void updateStatus(ProviderStatusWatcher.Status providerStatus) {
+ mProviderStatus = providerStatus;
+ if (mView == null) {
+ // The view hasn't been inflated yet.
+ return;
+ }
+ switch (providerStatus.status) {
case ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS:
setMessageText(mNoContactsMsgResId, mNSecNoContactsMsgResId);
mCreateContactButton.setVisibility(View.VISIBLE);
@@ -122,7 +130,7 @@
case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
String message = getResources().getString(R.string.upgrade_out_of_memory,
- new Object[] { mProviderStatusWatcher.getProviderStatusData() });
+ new Object[] { providerStatus.data});
mMessageView.setText(message);
mMessageView.setGravity(Gravity.LEFT);
mMessageView.setVisibility(View.VISIBLE);
@@ -155,7 +163,10 @@
mListener.onFreeInternalStorageAction();
break;
case R.id.import_failure_retry_button:
- mProviderStatusWatcher.retryUpgrade();
+ final Context context = getActivity();
+ if (context != null) { // Just in case.
+ ProviderStatusWatcher.retryUpgrade(context);
+ }
break;
}
}
@@ -167,9 +178,8 @@
public void setMessageText(int resId, int secResId) {
mNoContactsMsgResId = resId;
mNSecNoContactsMsgResId = secResId;
- if (mMessageView != null &&
- mProviderStatusWatcher.getProviderStatus() ==
- ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS) {
+ if ((mMessageView != null) && (mProviderStatus != null) &&
+ (mProviderStatus.status == ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS)) {
if (resId != -1) {
mMessageView.setText(mNoContactsMsgResId);
mMessageView.setGravity(Gravity.CENTER_HORIZONTAL);
diff --git a/src/com/android/contacts/list/ProviderStatusWatcher.java b/src/com/android/contacts/list/ProviderStatusWatcher.java
index 3ce4b78..ae0b779 100644
--- a/src/com/android/contacts/list/ProviderStatusWatcher.java
+++ b/src/com/android/contacts/list/ProviderStatusWatcher.java
@@ -47,6 +47,19 @@
public void onProviderStatusChange();
}
+ public static class Status {
+ /** See {@link ProviderStatus#STATUS} */
+ public final int status;
+
+ /** See {@link ProviderStatus#DATA1} */
+ public final String data;
+
+ public Status(int status, String data) {
+ this.status = status;
+ this.data = data;
+ }
+ }
+
private static final String[] PROJECTION = new String[] {
ProviderStatus.STATUS,
ProviderStatus.DATA1
@@ -57,8 +70,6 @@
*/
private static final int LOAD_WAIT_TIMEOUT_MS = 1000;
- private static final int STATUS_UNKNOWN = -1;
-
private static ProviderStatusWatcher sInstance;
private final Context mContext;
@@ -71,10 +82,7 @@
private LoaderTask mLoaderTask;
/** Last known provider status. This can be changed on a worker thread. */
- private int mProviderStatus = STATUS_UNKNOWN;
-
- /** Last known provider status data. This can be changed on a worker thread. */
- private String mProviderData;
+ private Status mProviderStatus;
private final ArrayList<ProviderStatusListener> mListeners = Lists.newArrayList();
@@ -177,32 +185,18 @@
* (If {@link ProviderStatus#STATUS_UPGRADING} is returned, the app (should) shows an according
* message, like "contacts are being updated".)
*/
- public int getProviderStatus() {
+ public Status getProviderStatus() {
waitForLoaded();
- if (mProviderStatus == STATUS_UNKNOWN) {
- return ProviderStatus.STATUS_UPGRADING;
+ if (mProviderStatus == null) {
+ return new Status(ProviderStatus.STATUS_UPGRADING, null);
}
return mProviderStatus;
}
- /**
- * @return last known provider status data. See also {@link #getProviderStatus()}.
- */
- public String getProviderStatusData() {
- waitForLoaded();
-
- if (mProviderStatus == STATUS_UNKNOWN) {
- // STATUS_UPGRADING has no data.
- return "";
- }
-
- return mProviderData;
- }
-
private void waitForLoaded() {
- if (mProviderStatus == STATUS_UNKNOWN) {
+ if (mProviderStatus == null) {
if (mLoaderTask == null) {
// For some reason the loader couldn't load the status. Let's start it again.
startLoading();
@@ -238,8 +232,10 @@
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
- mProviderStatus = cursor.getInt(0);
- mProviderData = cursor.getString(1);
+ // Note here we can't just say "Status", as AsyncTask has the "Status"
+ // enum too.
+ mProviderStatus = new ProviderStatusWatcher.Status(
+ cursor.getInt(0), cursor.getString(1));
return true;
}
} finally {
@@ -291,14 +287,14 @@
/**
* Sends a provider status update, which will trigger a retry of database upgrade
*/
- public void retryUpgrade() {
+ public static void retryUpgrade(final Context context) {
Log.i(TAG, "retryUpgrade");
final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
ContentValues values = new ContentValues();
values.put(ProviderStatus.STATUS, ProviderStatus.STATUS_UPGRADING);
- mContext.getContentResolver().update(ProviderStatus.CONTENT_URI, values,
+ context.getContentResolver().update(ProviderStatus.CONTENT_URI, values,
null, null);
return null;
}
diff --git a/src/com/android/contacts/quickcontact/FloatingChildLayout.java b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
index dca738c..20a3c1e 100644
--- a/src/com/android/contacts/quickcontact/FloatingChildLayout.java
+++ b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
@@ -17,15 +17,17 @@
package com.android.contacts.quickcontact;
import com.android.contacts.R;
+import com.android.contacts.test.NeededForReflection;
+import com.android.contacts.util.SchedulingUtils;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -53,11 +55,13 @@
private View mChild;
private Rect mTargetScreen = new Rect();
private final int mAnimationDuration;
- private final TransitionDrawable mBackground;
/** The phase of the background dim. This is one of the values of {@link BackgroundPhase} */
private int mBackgroundPhase = BackgroundPhase.BEFORE;
+ private ObjectAnimator mBackgroundAnimator = ObjectAnimator.ofInt(this,
+ "backgroundColorAlpha", 0, DIM_BACKGROUND_ALPHA);
+
private interface BackgroundPhase {
public static final int BEFORE = 0;
public static final int APPEARING_OR_VISIBLE = 1;
@@ -76,7 +80,7 @@
}
// Black, 50% alpha as per the system default.
- private static final int DIM_BACKGROUND_COLOR = 0x7F000000;
+ private static final int DIM_BACKGROUND_ALPHA = 0x7F;
public FloatingChildLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -85,10 +89,7 @@
resources.getDimensionPixelOffset(R.dimen.quick_contact_top_position);
mAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime);
- final ColorDrawable[] drawables =
- { new ColorDrawable(0), new ColorDrawable(DIM_BACKGROUND_COLOR) };
- mBackground = new TransitionDrawable(drawables);
- super.setBackground(mBackground);
+ super.setBackground(new ColorDrawable(0));
}
@Override
@@ -178,17 +179,35 @@
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
+ @NeededForReflection
+ public void setBackgroundColorAlpha(int alpha) {
+ setBackgroundColor(alpha << 24);
+ }
+
public void fadeInBackground() {
if (mBackgroundPhase == BackgroundPhase.BEFORE) {
mBackgroundPhase = BackgroundPhase.APPEARING_OR_VISIBLE;
- mBackground.startTransition(mAnimationDuration);
+
+ createChildLayer();
+
+ SchedulingUtils.doAfterDraw(this, new Runnable() {
+ @Override
+ public void run() {
+ mBackgroundAnimator.setDuration(mAnimationDuration).start();
+ }
+ });
}
}
public void fadeOutBackground() {
if (mBackgroundPhase == BackgroundPhase.APPEARING_OR_VISIBLE) {
mBackgroundPhase = BackgroundPhase.DISAPPEARING_OR_GONE;
- mBackground.reverseTransition(mAnimationDuration);
+ if (mBackgroundAnimator.isRunning()) {
+ mBackgroundAnimator.reverse();
+ } else {
+ ObjectAnimator.ofInt(this, "backgroundColorAlpha", DIM_BACKGROUND_ALPHA, 0).
+ setDuration(mAnimationDuration).start();
+ }
}
}
@@ -212,6 +231,9 @@
if (mForegroundPhase == ForegroundPhase.APPEARING ||
mForegroundPhase == ForegroundPhase.IDLE) {
mForegroundPhase = ForegroundPhase.DISAPPEARING;
+
+ createChildLayer();
+
animateScale(true, onAnimationEndRunnable);
return true;
} else {
@@ -219,6 +241,12 @@
}
}
+ private void createChildLayer() {
+ mChild.invalidate();
+ mChild.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mChild.buildLayer();
+ }
+
/** Creates the open/close animation */
private void animateScale(
final boolean isExitAnimation,
@@ -231,7 +259,7 @@
: android.R.interpolator.decelerate_quint;
final float scaleTarget = isExitAnimation ? 0.5f : 1.0f;
- mChild.animate().withLayer()
+ mChild.animate()
.setDuration(mAnimationDuration)
.setInterpolator(AnimationUtils.loadInterpolator(getContext(), scaleInterpolator))
.scaleX(scaleTarget)
@@ -240,6 +268,7 @@
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ mChild.setLayerType(LAYER_TYPE_NONE, null);
if (isExitAnimation) {
if (mForegroundPhase == ForegroundPhase.DISAPPEARING) {
mForegroundPhase = ForegroundPhase.AFTER;
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 3c91716..9c71ee2 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -25,6 +25,7 @@
import com.android.contacts.util.DataStatus;
import com.android.contacts.util.ImageViewDrawableSetter;
import com.android.contacts.util.SchedulingUtils;
+import com.android.contacts.util.StopWatch;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
@@ -91,6 +92,8 @@
private static final boolean TRACE_LAUNCH = false;
private static final String TRACE_TAG = "quickcontact";
private static final int POST_DRAW_WAIT_DURATION = 60;
+ private static final boolean ENABLE_STOPWATCH = false;
+
@SuppressWarnings("deprecation")
private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
@@ -148,18 +151,49 @@
/** Id for the background loader */
private static final int LOADER_ID = 0;
+ private StopWatch mStopWatch = ENABLE_STOPWATCH
+ ? StopWatch.start("QuickContact") : StopWatch.getNullStopWatch();
+
@Override
protected void onCreate(Bundle icicle) {
+ mStopWatch.lap("c"); // create start
super.onCreate(icicle);
+ mStopWatch.lap("sc"); // super.onCreate
+
if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG);
+ // Parse intent
+ final Intent intent = getIntent();
+
+ Uri lookupUri = intent.getData();
+
+ // Check to see whether it comes from the old version.
+ if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
+ final long rawContactId = ContentUris.parseId(lookupUri);
+ lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+ }
+
+ mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
+
+ mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
+
+ mStopWatch.lap("i"); // intent parsed
+
+ mContactLoader = (ContactLoader) getLoaderManager().initLoader(
+ LOADER_ID, null, mLoaderCallbacks);
+
+ mStopWatch.lap("ld"); // loader started
+
// Show QuickContact in front of soft input
getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
setContentView(R.layout.quickcontact_activity);
+ mStopWatch.lap("l"); // layout inflated
+
mFloatingLayout = (FloatingChildLayout) findViewById(R.id.floating_layout);
mTrack = (ViewGroup) findViewById(R.id.track);
mTrackScroller = (HorizontalScrollView) findViewById(R.id.track_scroller);
@@ -192,35 +226,25 @@
mListPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
mListPager.setOnPageChangeListener(new PageChangeListener());
- final Intent intent = getIntent();
-
- Uri lookupUri = intent.getData();
-
- // Check to see whether it comes from the old version.
- if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
- final long rawContactId = ContentUris.parseId(lookupUri);
- lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
- ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
- }
-
- mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
-
- // Read requested parameters for displaying
final Rect sourceBounds = intent.getSourceBounds();
if (sourceBounds != null) {
mFloatingLayout.setChildTargetScreen(sourceBounds);
}
- mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
-
// find and prepare correct header view
mPhotoContainer = findViewById(R.id.photo_container);
setHeaderNameText(R.id.name, R.string.missing_name);
- mContactLoader = (ContactLoader) getLoaderManager().initLoader(
- LOADER_ID, null, mLoaderCallbacks);
+ mStopWatch.lap("v"); // view initialized
- mFloatingLayout.fadeInBackground();
+ SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
+ @Override
+ public void run() {
+ mFloatingLayout.fadeInBackground();
+ }
+ });
+
+ mStopWatch.lap("cf"); // onCreate finished
}
private void handleOutsideTouch() {
@@ -315,11 +339,16 @@
mDefaultsMap.clear();
+ mStopWatch.lap("atm"); // AccountTypeManager initialization start
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
context.getApplicationContext());
+ mStopWatch.lap("fatm"); // AccountTypeManager initialization finished
+
final ImageView photoView = (ImageView) mPhotoContainer.findViewById(R.id.photo);
mPhotoSetter.setupContactPhoto(data, photoView);
+ mStopWatch.lap("ph"); // Photo set
+
for (Entity entity : data.getEntities()) {
final ContentValues entityValues = entity.getEntityValues();
final String accountType = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
@@ -370,11 +399,15 @@
}
}
+ mStopWatch.lap("e"); // Entities inflated
+
// Collapse Action Lists (remove e.g. duplicate e-mail addresses from different sources)
for (List<Action> actionChildren : mActions.values()) {
Collapser.collapseList(actionChildren);
}
+ mStopWatch.lap("c"); // List collapsed
+
setHeaderNameText(R.id.name, data.getDisplayName());
// All the mime-types to add.
@@ -404,6 +437,8 @@
}
}
+ mStopWatch.lap("mt"); // Mime types initialized
+
// Add buttons for each mimetype
mTrack.removeAllViews();
for (String mimeType : mSortedActionMimeTypes) {
@@ -411,6 +446,8 @@
mTrack.addView(actionView);
}
+ mStopWatch.lap("mt"); // Buttons added
+
final boolean hasData = !mSortedActionMimeTypes.isEmpty();
mTrackScroller.setVisibility(hasData ? View.VISIBLE : View.GONE);
mSelectedTabRectangle.setVisibility(hasData ? View.VISIBLE : View.GONE);
@@ -476,6 +513,7 @@
@Override
public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
+ mStopWatch.lap("lf"); // onLoadFinished
if (isFinishing()) {
close(false);
return;
@@ -495,6 +533,8 @@
bindData(data);
+ mStopWatch.lap("bd"); // bindData finished
+
if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing();
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "QuickContact shown");
@@ -513,6 +553,8 @@
});
}
});
+ mStopWatch.stopAndLog(TAG, 0);
+ mStopWatch = StopWatch.getNullStopWatch(); // We're done with it.
}
@Override
diff --git a/src/com/android/contacts/test/NeededForReflection.java b/src/com/android/contacts/test/NeededForReflection.java
new file mode 100644
index 0000000..feacca5
--- /dev/null
+++ b/src/com/android/contacts/test/NeededForReflection.java
@@ -0,0 +1,30 @@
+/*
+ * 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.contacts.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the class, constructor, method or field is used by tests and therefore cannot be
+ * removed by tools like ProGuard.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
+public @interface NeededForReflection{}
diff --git a/src/com/android/contacts/util/StopWatch.java b/src/com/android/contacts/util/StopWatch.java
new file mode 100644
index 0000000..1accfe2
--- /dev/null
+++ b/src/com/android/contacts/util/StopWatch.java
@@ -0,0 +1,110 @@
+/*
+ * 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.contacts.util;
+
+import com.google.android.collect.Lists;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * A {@link StopWatch} records start, laps and stop, and print them to logcat.
+ */
+public class StopWatch {
+
+ private final String mLabel;
+
+ private final ArrayList<Long> mTimes = Lists.newArrayList();
+ private final ArrayList<String> mLapLabels = Lists.newArrayList();
+
+ private StopWatch(String label) {
+ mLabel = label;
+ lap("");
+ }
+
+ /**
+ * Create a new instance and start it.
+ */
+ public static StopWatch start(String label) {
+ return new StopWatch(label);
+ }
+
+ /**
+ * Record a lap.
+ */
+ public void lap(String lapLabel) {
+ mTimes.add(System.currentTimeMillis());
+ mLapLabels.add(lapLabel);
+ }
+
+ /**
+ * Stop it and log the result, if the total time >= {@code timeThresholdToLog}.
+ */
+ public void stopAndLog(String TAG, int timeThresholdToLog) {
+
+ lap("");
+
+ final long start = mTimes.get(0);
+ final long stop = mTimes.get(mTimes.size() - 1);
+
+ final long total = stop - start;
+ if (total < timeThresholdToLog) return;
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(mLabel);
+ sb.append(",");
+ sb.append(total);
+ sb.append(": ");
+
+ long last = start;
+ for (int i = 1; i < mTimes.size(); i++) {
+ final long current = mTimes.get(i);
+ sb.append(mLapLabels.get(i));
+ sb.append(",");
+ sb.append((current - last));
+ sb.append(" ");
+ last = current;
+ }
+ Log.v(TAG, sb.toString());
+ }
+
+ /**
+ * Return a dummy instance that does no operations.
+ */
+ public static StopWatch getNullStopWatch() {
+ return NullStopWatch.INSTANCE;
+ }
+
+ private static class NullStopWatch extends StopWatch {
+ public static final NullStopWatch INSTANCE = new NullStopWatch();
+
+ public NullStopWatch() {
+ super(null);
+ }
+
+ @Override
+ public void lap(String lapLabel) {
+ // noop
+ }
+
+ @Override
+ public void stopAndLog(String TAG, int timeThresholdToLog) {
+ // noop
+ }
+ }
+}